Skip to content
ClaudeUnreal
GitHub

Game Simulation

This content is not available in your language yet.

6 tools — 0 typed MCP · 6 via cu CLI.

  • Play In Editor (PIE) — 6 tools

Launch a Play In Editor session to test gameplay, then capture screenshots and logs at runtime.

@mcp.tool()
@showcase(
"Launch a Play In Editor session to test gameplay, then capture screenshots and logs at runtime.",
featured=True,
)
def start_play_in_editor(ctx: Context, mode: str = None) -> ToolResult:
"""[Sim] Start a Play In Editor (PIE) session.
Forces single-process PIE regardless of saved Standalone-Game preferences
so ``get_runtime_log`` can see log output (cross-process capture is broken).
Modes:
- "floating" (default): new editor window (was the only option pre-mode).
- "viewport": run in the active editor viewport — same camera, no window flip.
- "simulate": physics only, no PlayerController/PlayerStart. Editor camera
stays in control. Use this when you want physics to tick without
possessing a player (great for cinematic/demo captures).
Polls until PIE is confirmed running (up to 15 s).
Args:
ctx: The MCP context
mode: "floating" | "viewport" | "simulate". Default "floating".
Examples:
start_play_in_editor()
start_play_in_editor(mode="simulate")
"""
from claude_unreal_server import get_unreal_connection
if mode is None:
mode = "floating"
try:
unreal = get_unreal_connection()
if not unreal:
logger.error("Failed to connect to Unreal Engine")
return err("Failed to start PIE: no response from Unreal Engine")
response = unreal.send_command("start_pie", {"mode": mode})
if not response:
return err("Failed to start PIE: no response from Unreal Engine")
# If already running or error, return immediately
result = response.get("result", response)
status = result.get("status", "")
if status == "already_running":
return ok("PIE already running")
if status == "error":
error_message = result.get("error") or response.get("error") or "unknown error"
return err(f"Failed to start PIE: {error_message}", error=error_message)
# Poll get_pie_state until PIE is confirmed running
for _ in range(30): # 30 * 0.5s = 15s max
time.sleep(0.5)
try:
poll = unreal.send_command("get_pie_state", {})
if poll:
poll_result = poll.get("result", poll)
if poll_result.get("running", False):
return ok("Started PIE session")
except Exception:
pass # Connection hiccup during PIE startup, keep polling
return err("Failed to start PIE: did not start within 15s", error="timeout")
except Exception as e:
logger.error(f"Error in start_play_in_editor: {e}")
return err("Failed to start PIE", error=str(e))

Inject an Enhanced Input action into a running PIE session for autonomous UI/input testing.

@mcp.tool()
@showcase(
"Inject an Enhanced Input action into a running PIE session for autonomous UI/input testing.",
)
def inject_enhanced_input_action(
ctx: Context,
action_name: str,
value: bool = None,
player_index: int = 0,
) -> ToolResult:
"""[Sim] Inject an Enhanced Input action into the running PIE session.
Routes through UEnhancedPlayerInput::InjectInputForAction. PIE must be
running. value=True presses, False releases. Always pair both — omitting
release leaves trigger "held". Non-Boolean actions unsupported.
Anti-patterns: no PIE → error; "Key" console bypasses Enhanced Input;
value=None → validation error.
Example:
inject_enhanced_input_action("IA_ToggleInventory", value=True)
inject_enhanced_input_action("IA_ToggleInventory", value=False)
"""
from claude_unreal_server import get_unreal_connection
if value is None:
return err(
"'value' is required (Boolean — True=press, False=release)",
error="missing_value",
)
try:
unreal = get_unreal_connection()
if not unreal:
logger.error("Failed to connect to Unreal Engine")
return err("Failed to inject input: no response from Unreal Engine")
response = unreal.send_command(
"inject_enhanced_input_action",
{
"action_name": action_name,
"value": value,
"player_index": player_index,
},
)
if not response:
return err("Failed to inject input: no response from Unreal Engine")
if response.get("status") == "error":
return err(
"Failed to inject input",
error=response.get("error", "Unknown error"),
)
result = response.get("result", response)
return ok(result.get("message", "Injected input action"), **result)
except Exception as e:
logger.error(f"Error in inject_enhanced_input_action: {e}")
return err("Failed to inject input", error=str(e))