Fixing MCPServer startup for claude code
This commit is contained in:
1 parent
afabda86cf
commit
cb3e50d7c7
33 files changed
+7090
-1
No files matched your search
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Test script for component reference in blueprints via MCP.
|
||||
This tests the fix for the component reference node issue.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import socket
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
# Add the parent directory to the path so we can import the server module
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger("TestComponentReference")
|
||||
|
||||
def send_command(sock: socket.socket, command: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""Send a command to the Unreal MCP server and get the response."""
|
||||
try:
|
||||
# Create command object
|
||||
command_obj = {
|
||||
"type": command,
|
||||
"params": params
|
||||
}
|
||||
|
||||
# Convert to JSON and send
|
||||
command_json = json.dumps(command_obj)
|
||||
logger.info(f"Sending command: {command_json}")
|
||||
sock.sendall(command_json.encode('utf-8'))
|
||||
|
||||
# Receive response
|
||||
chunks = []
|
||||
while True:
|
||||
chunk = sock.recv(4096)
|
||||
if not chunk:
|
||||
break
|
||||
chunks.append(chunk)
|
||||
|
||||
# Try parsing to see if we have a complete response
|
||||
try:
|
||||
data = b''.join(chunks)
|
||||
json.loads(data.decode('utf-8'))
|
||||
# If we can parse it, we have the complete response
|
||||
break
|
||||
except json.JSONDecodeError:
|
||||
# Not a complete JSON object yet, continue receiving
|
||||
continue
|
||||
|
||||
# Parse response
|
||||
data = b''.join(chunks)
|
||||
response = json.loads(data.decode('utf-8'))
|
||||
logger.info(f"Received response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending command: {e}")
|
||||
return None
|
||||
|
||||
def main():
|
||||
"""Test component reference node creation and connection."""
|
||||
try:
|
||||
# Connect to the server
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 1: Create a blueprint
|
||||
bp_params = {
|
||||
"name": "TestCompRefBP",
|
||||
"parent_class": "Actor"
|
||||
}
|
||||
|
||||
response = send_command(sock, "create_blueprint", bp_params)
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to create blueprint: {response}")
|
||||
return
|
||||
|
||||
logger.info("Blueprint created successfully!")
|
||||
|
||||
# Step 2: Add a static mesh component
|
||||
component_params = {
|
||||
"blueprint_name": "TestCompRefBP",
|
||||
"component_type": "StaticMesh",
|
||||
"component_name": "TestMesh",
|
||||
"location": [0.0, 0.0, 0.0],
|
||||
"rotation": [0.0, 0.0, 0.0],
|
||||
"scale": [1.0, 1.0, 1.0]
|
||||
}
|
||||
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
response = send_command(sock, "add_component_to_blueprint", component_params)
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add component: {response}")
|
||||
return
|
||||
|
||||
logger.info("Static mesh component added successfully!")
|
||||
|
||||
# Step 3: Add an event (BeginPlay)
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
begin_play_params = {
|
||||
"blueprint_name": "TestCompRefBP",
|
||||
"event_type": "BeginPlay",
|
||||
"node_position": [0, 0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_event_node", begin_play_params)
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add BeginPlay event: {response}")
|
||||
return
|
||||
|
||||
begin_play_node_id = response.get("result", {}).get("node_id")
|
||||
logger.info(f"BeginPlay event node added successfully with ID: {begin_play_node_id}")
|
||||
|
||||
# Step 4: Create component reference node
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
get_component_params = {
|
||||
"blueprint_name": "TestCompRefBP",
|
||||
"component_name": "TestMesh",
|
||||
"node_position": [200, 0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_get_self_component_reference", get_component_params)
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add component reference node: {response}")
|
||||
return
|
||||
|
||||
comp_ref_node_id = response.get("result", {}).get("node_id")
|
||||
logger.info(f"Component reference node added successfully with ID: {comp_ref_node_id}")
|
||||
|
||||
# Step 5: Add AddForce function node
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
function_params = {
|
||||
"blueprint_name": "TestCompRefBP",
|
||||
"function_name": "AddForce",
|
||||
"params": {
|
||||
"Force": [0, 0, 1000]
|
||||
},
|
||||
"node_position": [400, 0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_function_node", function_params)
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add AddForce function node: {response}")
|
||||
return
|
||||
|
||||
function_node_id = response.get("result", {}).get("node_id")
|
||||
logger.info(f"AddForce function node added successfully with ID: {function_node_id}")
|
||||
|
||||
# Step 6: Connect BeginPlay to AddForce (execution)
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
connect_exec_params = {
|
||||
"blueprint_name": "TestCompRefBP",
|
||||
"source_node_id": begin_play_node_id,
|
||||
"source_pin": "Then", # Execution pin on BeginPlay
|
||||
"target_node_id": function_node_id,
|
||||
"target_pin": "Execute" # Execution pin on function
|
||||
}
|
||||
|
||||
response = send_command(sock, "connect_blueprint_nodes", connect_exec_params)
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to connect execution pins: {response}")
|
||||
return
|
||||
|
||||
logger.info("Connected BeginPlay to AddForce execution pins!")
|
||||
|
||||
# Step 7: Connect component reference to AddForce target
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# In UE5.5, the output pin of a component reference is named after the component itself
|
||||
component_name = "TestMesh" # Use the same name as defined in the component
|
||||
connect_target_params = {
|
||||
"blueprint_name": "TestCompRefBP",
|
||||
"source_node_id": comp_ref_node_id,
|
||||
"source_pin": component_name, # Use component name as pin name
|
||||
"target_node_id": function_node_id,
|
||||
"target_pin": "Target" # Target pin on AddForce
|
||||
}
|
||||
|
||||
response = send_command(sock, "connect_blueprint_nodes", connect_target_params)
|
||||
logger.warning(f"Pin connection response: {response}")
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success", False):
|
||||
logger.error(f"Failed to connect component reference: {response}")
|
||||
# Try alternate pin names if needed
|
||||
fallback_pin_names = ["Value", "ReturnValue"]
|
||||
for pin_name in fallback_pin_names:
|
||||
logger.info(f"Trying with alternative pin name: '{pin_name}'")
|
||||
connect_target_params["source_pin"] = pin_name
|
||||
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
response = send_command(sock, "connect_blueprint_nodes", connect_target_params)
|
||||
if response and response.get("status") == "success" and response.get("result", {}).get("success", False):
|
||||
logger.info(f"Successfully connected using pin name: '{pin_name}'")
|
||||
break
|
||||
else:
|
||||
logger.error("Failed to connect with all pin name options")
|
||||
return
|
||||
|
||||
logger.info("Connected component reference to AddForce target!")
|
||||
|
||||
# Step 8: Compile Blueprint
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
compile_params = {
|
||||
"blueprint_name": "TestCompRefBP"
|
||||
}
|
||||
|
||||
response = send_command(sock, "compile_blueprint", compile_params)
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to compile blueprint: {response}")
|
||||
return
|
||||
|
||||
logger.info("Blueprint compiled successfully!")
|
||||
|
||||
# Step 9: Spawn the actor
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
spawn_params = {
|
||||
"blueprint_name": "TestCompRefBP",
|
||||
"actor_name": "TestCompRefActor",
|
||||
"location": [0.0, 0.0, 100.0],
|
||||
"rotation": [0.0, 0.0, 0.0],
|
||||
"scale": [1.0, 1.0, 1.0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "spawn_blueprint_actor", spawn_params)
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to spawn actor: {response}")
|
||||
return
|
||||
|
||||
logger.info("Actor spawned successfully! The mesh should move up on BeginPlay.")
|
||||
|
||||
sock.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,618 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Test script for blueprint node tools via MCP.
|
||||
This script creates a simple flapping bird Blueprint with basic physics and input handling.
|
||||
|
||||
Commands used:
|
||||
1. create_blueprint - Creates a new Blueprint class inheriting from Pawn
|
||||
2. add_component_to_blueprint - Adds a StaticMeshComponent named "BirdMesh"
|
||||
3. set_physics_properties - Configures physics properties for the mesh
|
||||
4. add_blueprint_variable - Adds a Float variable "FlapStrength"
|
||||
5. set_static_mesh_properties - Sets the mesh to a sphere
|
||||
6. create_input_mapping - Creates a "Flap" action mapped to SpaceBar
|
||||
7. find_blueprint_nodes - Searches for existing nodes (e.g., BeginPlay)
|
||||
8. add_blueprint_event_node - Creates event nodes (BeginPlay)
|
||||
9. add_blueprint_input_action_node - Creates input action node for Flap
|
||||
10. add_blueprint_get_self_component_reference - Creates component reference node
|
||||
11. add_blueprint_function_node - Creates function nodes (AddImpulse, GetActorOfClass, etc.)
|
||||
12. connect_blueprint_nodes - Connects nodes together
|
||||
13. compile_blueprint - Compiles the Blueprint
|
||||
14. set_pawn_properties - Sets auto-possess properties
|
||||
15. spawn_blueprint_actor - Spawns the bird in the level
|
||||
16. create_actor - Creates a camera actor
|
||||
|
||||
Blueprint Graph Layout:
|
||||
```
|
||||
[BeginPlay] ──────┐
|
||||
│
|
||||
[InputAction] ───┼─── [BirdMesh] ─── [AddImpulse]
|
||||
│
|
||||
[GetActorOfClass] ─── [SetViewTargetWithBlend]
|
||||
│
|
||||
└── [GetPlayerController]
|
||||
```
|
||||
|
||||
Node Positions:
|
||||
- BeginPlay: [-400, 0]
|
||||
- InputAction: [-400, 300]
|
||||
- BirdMesh: [0, 300]
|
||||
- AddImpulse: [400, 300]
|
||||
- GetActorOfClass: [0, -200]
|
||||
- SetViewTargetWithBlend: [400, -200]
|
||||
- GetPlayerController: [0, -100]
|
||||
|
||||
Connections:
|
||||
1. InputAction.Pressed -> AddImpulse.Execute
|
||||
2. BirdMesh -> AddImpulse.self
|
||||
3. BeginPlay.Then -> GetActorOfClass.Execute
|
||||
4. GetActorOfClass.Then -> SetViewTargetWithBlend.Execute
|
||||
5. GetActorOfClass.ReturnValue -> SetViewTargetWithBlend.NewViewTarget
|
||||
6. GetPlayerController.ReturnValue -> SetViewTargetWithBlend.self
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import socket
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
# Add the parent directory to the path so we can import the server module
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger("TestBlueprintNodes")
|
||||
|
||||
def send_command(sock: socket.socket, command: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""Send a command to the Unreal MCP server and get the response."""
|
||||
try:
|
||||
# Create command object
|
||||
command_obj = {
|
||||
"type": command,
|
||||
"params": params
|
||||
}
|
||||
|
||||
# Convert to JSON and send
|
||||
command_json = json.dumps(command_obj)
|
||||
logger.info(f"Sending command: {command_json}")
|
||||
sock.sendall(command_json.encode('utf-8'))
|
||||
|
||||
# Receive response
|
||||
chunks = []
|
||||
while True:
|
||||
chunk = sock.recv(4096)
|
||||
if not chunk:
|
||||
break
|
||||
chunks.append(chunk)
|
||||
|
||||
# Try parsing to see if we have a complete response
|
||||
try:
|
||||
data = b''.join(chunks)
|
||||
json.loads(data.decode('utf-8'))
|
||||
# If we can parse it, we have the complete response
|
||||
break
|
||||
except json.JSONDecodeError:
|
||||
# Not a complete JSON object yet, continue receiving
|
||||
continue
|
||||
|
||||
# Parse response
|
||||
data = b''.join(chunks)
|
||||
response = json.loads(data.decode('utf-8'))
|
||||
logger.info(f"Received response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending command: {e}")
|
||||
return None
|
||||
|
||||
def send_mcp_command(command: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""Send a command to the Unreal MCP server with automatic socket lifecycle management."""
|
||||
sock = None
|
||||
try:
|
||||
# Create a new socket for each command
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Send the command and get the response
|
||||
return send_command(sock, command, params)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in socket communication: {e}")
|
||||
return None
|
||||
finally:
|
||||
# Always close the socket when done
|
||||
if sock:
|
||||
sock.close()
|
||||
|
||||
def main():
|
||||
"""Main function to test blueprint node tools."""
|
||||
try:
|
||||
# Step 1: Create a blueprint for the bird
|
||||
bp_params = {
|
||||
"name": "BirdBP",
|
||||
"parent_class": "Pawn"
|
||||
}
|
||||
|
||||
response = send_mcp_command("create_blueprint", bp_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to create blueprint: {response}")
|
||||
return
|
||||
|
||||
# Check if blueprint already existed
|
||||
if response.get("result", {}).get("already_exists"):
|
||||
logger.info(f"Blueprint 'BirdBP' already exists, reusing it")
|
||||
else:
|
||||
logger.info("Blueprint created successfully!")
|
||||
|
||||
# Step 2: Add a static mesh component
|
||||
component_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"component_type": "StaticMeshComponent",
|
||||
"component_name": "BirdMesh",
|
||||
"location": [0.0, 0.0, 0.0],
|
||||
"rotation": [0.0, 0.0, 0.0],
|
||||
"scale": [0.5, 0.5, 0.5] # Smaller bird
|
||||
}
|
||||
|
||||
response = send_mcp_command("add_component_to_blueprint", component_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add component: {response}")
|
||||
return
|
||||
|
||||
logger.info("Bird mesh component added successfully!")
|
||||
|
||||
# Step 3: Add physics properties to the mesh
|
||||
physics_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"component_name": "BirdMesh",
|
||||
"simulate_physics": True,
|
||||
"gravity_enabled": True,
|
||||
"mass": 2.0, # Light bird
|
||||
"linear_damping": 0.5, # Some air resistance
|
||||
"angular_damping": 0.5 # Prevent too much spinning
|
||||
}
|
||||
|
||||
response = send_mcp_command("set_physics_properties", physics_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to set physics properties: {response}")
|
||||
return
|
||||
|
||||
logger.info("Physics properties set successfully!")
|
||||
|
||||
# Step 4: Add variables for tracking bird state
|
||||
response = send_mcp_command("add_blueprint_variable", {
|
||||
"blueprint_name": "BirdBP",
|
||||
"variable_name": "FlapStrength",
|
||||
"variable_type": "Float",
|
||||
"default_value": 500.0,
|
||||
"is_exposed": True
|
||||
})
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add variable: {response}")
|
||||
return
|
||||
|
||||
logger.info("FlapStrength variable added successfully!")
|
||||
|
||||
# Step 4b: Set the static mesh of the BirdMesh component to a sphere
|
||||
response = send_mcp_command("set_static_mesh_properties", {
|
||||
"blueprint_name": "BirdBP",
|
||||
"component_name": "BirdMesh",
|
||||
"static_mesh": "/Engine/BasicShapes/Sphere.Sphere"
|
||||
})
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to set static mesh: {response}")
|
||||
return
|
||||
|
||||
logger.info("Setting Static Mesh Component of Bird to sphere successfully!")
|
||||
|
||||
# Step 5: Create input mapping for flap action
|
||||
response = send_mcp_command("create_input_mapping", {
|
||||
"action_name": "Flap",
|
||||
"key": "SpaceBar",
|
||||
"input_type": "Action"
|
||||
})
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to create input mapping: {response}")
|
||||
return
|
||||
|
||||
logger.info("Flap input mapping created successfully!")
|
||||
|
||||
# Step 6: Create BeginPlay event node - check if it exists first
|
||||
begin_play_response = None
|
||||
|
||||
# First try to find existing BeginPlay nodes
|
||||
find_begin_play_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"node_type": "Event",
|
||||
"event_name": "ReceiveBeginPlay" # Use the exact Unreal Engine event name
|
||||
}
|
||||
|
||||
response = send_mcp_command("find_blueprint_nodes", find_begin_play_params)
|
||||
|
||||
if response and response.get("status") == "success":
|
||||
# Look for BeginPlay nodes in the response
|
||||
nodes = response.get("result", {}).get("node_guids", [])
|
||||
if nodes:
|
||||
logger.info(f"Found existing ReceiveBeginPlay node, reusing it")
|
||||
# Use the first BeginPlay node we find
|
||||
begin_play_node_id = nodes[0]
|
||||
begin_play_response = {"result": {"success": True, "node_id": begin_play_node_id}}
|
||||
|
||||
# Only create a new BeginPlay node if we didn't find one
|
||||
if begin_play_response is None:
|
||||
begin_play_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"event_name": "ReceiveBeginPlay",
|
||||
"node_position": [-400, 0] # Move BeginPlay further left
|
||||
}
|
||||
|
||||
response = send_mcp_command("add_blueprint_event_node", begin_play_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add ReceiveBeginPlay event node: {response}")
|
||||
return
|
||||
|
||||
logger.info("ReceiveBeginPlay event node added successfully!")
|
||||
|
||||
# Save the node ID for later connections
|
||||
begin_play_node_id = begin_play_response.get("result", {}).get("node_id")
|
||||
|
||||
# Step 7: Create input action event node
|
||||
input_action_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"action_name": "Flap",
|
||||
"node_position": [-400, 300] # Move input action down and left
|
||||
}
|
||||
|
||||
# Create the InputAction event node using the dedicated function
|
||||
response = send_mcp_command("add_blueprint_input_action_node", input_action_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add Input action node: {response}")
|
||||
return
|
||||
|
||||
logger.info("Input action node added successfully")
|
||||
|
||||
# Save the node ID for later connections
|
||||
input_node_id = response.get("result", {}).get("node_id")
|
||||
|
||||
# Step 8: Add a get component reference node for BirdMesh
|
||||
get_component_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"component_name": "BirdMesh",
|
||||
"node_position": [0, 300] # Center the component reference
|
||||
}
|
||||
|
||||
response = send_mcp_command("add_blueprint_get_self_component_reference", get_component_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add component reference node: {response}")
|
||||
return
|
||||
|
||||
get_component_node_id = response.get("result", {}).get("node_id")
|
||||
|
||||
# Step 9: Add function node to apply impulse on flap
|
||||
function_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"function_name": "AddImpulse",
|
||||
"target": "UPrimitiveComponent",
|
||||
"params": {
|
||||
"Impulse": [0, 0, 1000]
|
||||
},
|
||||
"node_position": [400, 300] # Move AddImpulse to the right
|
||||
}
|
||||
|
||||
response = send_mcp_command("add_blueprint_function_node", function_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add AddImpulse function node: {response}")
|
||||
# If UPrimitiveComponent fails, try alternatives
|
||||
targets_to_try = ["PrimitiveComponent", "SceneComponent", "USceneComponent"]
|
||||
|
||||
for target in targets_to_try:
|
||||
logger.info(f"Trying alternative class for AddImpulse: {target}")
|
||||
function_params["target"] = target
|
||||
|
||||
response = send_mcp_command("add_blueprint_function_node", function_params)
|
||||
if response and response.get("status") == "success":
|
||||
logger.info(f"Successfully added AddImpulse using target class: {target}")
|
||||
break
|
||||
else:
|
||||
logger.error("All attempts to add AddImpulse function failed")
|
||||
return
|
||||
|
||||
logger.info("AddImpulse function node added successfully!")
|
||||
|
||||
# Save the node ID for later connections
|
||||
add_impulse_node_id = response.get("result", {}).get("node_id")
|
||||
|
||||
# Step 10: Connect input event to add impulse function
|
||||
connect_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"source_node_id": input_node_id,
|
||||
"source_pin": "Pressed", # Execute pin on input action event node
|
||||
"target_node_id": add_impulse_node_id,
|
||||
"target_pin": "Execute" # Execute pin on function node
|
||||
}
|
||||
|
||||
response = send_mcp_command("connect_blueprint_nodes", connect_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to connect nodes: {response}")
|
||||
return
|
||||
|
||||
logger.info("Input node connected to AddImpulse successfully!")
|
||||
|
||||
# Step 11: Connect get component to add impulse function (target connection)
|
||||
connect_target_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"source_node_id": get_component_node_id,
|
||||
"source_pin": "BirdMesh", # Use component name as the output pin name (UE5.5 convention)
|
||||
"target_node_id": add_impulse_node_id,
|
||||
"target_pin": "self" # Change from "Target" to "self" - this is the actual pin name in UE5.5
|
||||
}
|
||||
|
||||
response = send_mcp_command("connect_blueprint_nodes", connect_target_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to connect component to target pin: {response}")
|
||||
return
|
||||
|
||||
logger.info("Component target connected successfully!")
|
||||
|
||||
# Step 12: Compile the blueprint
|
||||
response = send_mcp_command("compile_blueprint", {
|
||||
"blueprint_name": "BirdBP"
|
||||
})
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to compile blueprint: {response}")
|
||||
return
|
||||
|
||||
logger.info("Blueprint compiled successfully!")
|
||||
|
||||
# Step 13: Set pawn properties using the new utility function
|
||||
response = send_mcp_command("set_pawn_properties", {
|
||||
"blueprint_name": "BirdBP",
|
||||
"auto_possess_player": "Player0" # Use short enum name as per reflection docs
|
||||
})
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to set pawn properties: {response}")
|
||||
return
|
||||
|
||||
logger.info("Pawn properties set successfully!")
|
||||
|
||||
# Step 14: Add GetActorOfClass node to BirdBP's BeginPlay
|
||||
# Note: In UE5.5, class references must use the full path format: /Script/ModuleName.ClassName
|
||||
# This format is the standard way Unreal Engine references classes internally:
|
||||
# - /Script/ prefix indicates this is a C++ (native) class
|
||||
# - ModuleName is the module where the class is defined (e.g., Engine, Game)
|
||||
# - ClassName is the actual class name
|
||||
# Examples:
|
||||
# - /Script/Engine.CameraActor for the camera actor class
|
||||
# - /Script/Engine.PlayerController for the player controller class
|
||||
get_camera_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"function_name": "GetActorOfClass",
|
||||
"target": "UGameplayStatics",
|
||||
"params": {
|
||||
"ActorClass": "/Script/Engine.CameraActor"
|
||||
},
|
||||
"node_position": [0, -200] # Move camera setup nodes down
|
||||
}
|
||||
|
||||
response = send_mcp_command("add_blueprint_function_node", get_camera_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add GetActorOfClass node: {response}")
|
||||
return
|
||||
|
||||
logger.info("GetActorOfClass node added successfully!")
|
||||
get_camera_node_id = response.get("result", {}).get("node_id")
|
||||
|
||||
# Step 15 (formerly 17): Add SetViewTargetWithBlend node
|
||||
# Add SetViewTargetWithBlend function node
|
||||
set_view_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"function_name": "SetViewTargetWithBlend",
|
||||
"target": "PlayerController",
|
||||
"params": {
|
||||
"BlendTime": 0.0,
|
||||
"BlendFunc": "VTBlend_EaseInOut",
|
||||
"LockOutgoing": True
|
||||
},
|
||||
"node_position": [400, -200] # Align with GetActorOfClass
|
||||
}
|
||||
|
||||
response = send_mcp_command("add_blueprint_function_node", set_view_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add SetViewTargetWithBlend node: {response}")
|
||||
return
|
||||
|
||||
logger.info("SetViewTargetWithBlend node added successfully!")
|
||||
set_view_node_id = response.get("result", {}).get("node_id")
|
||||
|
||||
# Step 16 (formerly 18): Connect BeginPlay to the SetViewTargetWithBlend node
|
||||
# Try to find existing BeginPlay node first
|
||||
find_begin_play_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"node_type": "Event",
|
||||
"event_name": "ReceiveBeginPlay" # Use the exact Unreal Engine event name
|
||||
}
|
||||
|
||||
response = send_mcp_command("find_blueprint_nodes", find_begin_play_params)
|
||||
|
||||
if response and response.get("status") == "success":
|
||||
# Use existing BeginPlay node if found
|
||||
nodes = response.get("result", {}).get("nodes", [])
|
||||
if nodes:
|
||||
logger.info(f"Found existing ReceiveBeginPlay node, reusing it")
|
||||
begin_play_node_id = nodes[0].get("node_id")
|
||||
else:
|
||||
# Create a new BeginPlay node only if none exists
|
||||
begin_play_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"event_name": "ReceiveBeginPlay", # Use the exact Unreal Engine event name
|
||||
"node_position": [-400, 0] # Move BeginPlay further left
|
||||
}
|
||||
|
||||
response = send_mcp_command("add_blueprint_event_node", begin_play_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to get/create ReceiveBeginPlay node: {response}")
|
||||
return
|
||||
|
||||
begin_play_node_id = response.get("result", {}).get("node_id")
|
||||
else:
|
||||
logger.error(f"Failed to search for ReceiveBeginPlay nodes: {response}")
|
||||
return
|
||||
|
||||
# Connect BeginPlay to GetActorOfClass (instead of directly to SetViewTargetWithBlend)
|
||||
connect_begin_play_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"source_node_id": begin_play_node_id,
|
||||
"source_pin": "Then",
|
||||
"target_node_id": get_camera_node_id,
|
||||
"target_pin": "Execute" # Connect to GetActorOfClass's execute pin (capital E)
|
||||
}
|
||||
|
||||
response = send_mcp_command("connect_blueprint_nodes", connect_begin_play_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to connect BeginPlay to GetActorOfClass: {response}")
|
||||
return
|
||||
|
||||
logger.info("Connected BeginPlay to GetActorOfClass successfully!")
|
||||
|
||||
# Then connect GetActorOfClass to SetViewTargetWithBlend
|
||||
connect_camera_exec_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"source_node_id": get_camera_node_id,
|
||||
"source_pin": "Then", # Output execution pin from GetActorOfClass (capital T)
|
||||
"target_node_id": set_view_node_id,
|
||||
"target_pin": "Execute" # Input execution pin on SetViewTargetWithBlend (capital E)
|
||||
}
|
||||
|
||||
response = send_mcp_command("connect_blueprint_nodes", connect_camera_exec_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to connect GetActorOfClass to SetViewTargetWithBlend execution: {response}")
|
||||
return
|
||||
|
||||
logger.info("Connected GetActorOfClass execution to SetViewTargetWithBlend successfully!")
|
||||
|
||||
# Now connect GetActorOfClass result to SetViewTargetWithBlend's target parameter
|
||||
connect_camera_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"source_node_id": get_camera_node_id,
|
||||
"source_pin": "ReturnValue",
|
||||
"target_node_id": set_view_node_id,
|
||||
"target_pin": "NewViewTarget"
|
||||
}
|
||||
|
||||
response = send_mcp_command("connect_blueprint_nodes", connect_camera_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to connect camera to SetViewTargetWithBlend: {response}")
|
||||
return
|
||||
|
||||
logger.info("Connected GetActorOfClass to SetViewTargetWithBlend successfully!")
|
||||
|
||||
# Step 18 (formerly 20): Get Player Controller for the SetViewTargetWithBlend function
|
||||
# Add Get Player Controller node
|
||||
get_pc_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"function_name": "GetPlayerController",
|
||||
"target": "UGameplayStatics",
|
||||
"params": {
|
||||
"PlayerIndex": 0
|
||||
},
|
||||
"node_position": [0, -100] # Place between GetActorOfClass and SetViewTarget
|
||||
}
|
||||
|
||||
response = send_mcp_command("add_blueprint_function_node", get_pc_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to add GetPlayerController node: {response}")
|
||||
return
|
||||
|
||||
logger.info("GetPlayerController node added successfully!")
|
||||
get_pc_node_id = response.get("result", {}).get("node_id")
|
||||
|
||||
# Connect Player Controller to SetViewTargetWithBlend
|
||||
connect_pc_params = {
|
||||
"blueprint_name": "BirdBP",
|
||||
"source_node_id": get_pc_node_id,
|
||||
"source_pin": "ReturnValue",
|
||||
"target_node_id": set_view_node_id,
|
||||
"target_pin": "self"
|
||||
}
|
||||
|
||||
response = send_mcp_command("connect_blueprint_nodes", connect_pc_params)
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to connect player controller to SetViewTargetWithBlend: {response}")
|
||||
return
|
||||
|
||||
logger.info("Connected PlayerController to SetViewTargetWithBlend target successfully!")
|
||||
|
||||
# Step 19 (formerly 21): Compile the blueprint with the new camera view setup
|
||||
response = send_mcp_command("compile_blueprint", {
|
||||
"blueprint_name": "BirdBP"
|
||||
})
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to compile blueprint: {response}")
|
||||
return
|
||||
|
||||
logger.info("Blueprint with camera view setup compiled successfully!")
|
||||
|
||||
# Step 20 (formerly 14): Spawn the bird in the level
|
||||
response = send_mcp_command("spawn_blueprint_actor", {
|
||||
"blueprint_name": "BirdBP",
|
||||
"actor_name": "Bird",
|
||||
"location": [0.0, 0.0, 200.0], # 200 units up
|
||||
"rotation": [0.0, 0.0, 0.0],
|
||||
"scale": [1.0, 1.0, 1.0]
|
||||
})
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to spawn blueprint actor: {response}")
|
||||
return
|
||||
|
||||
logger.info("Bird spawned successfully!")
|
||||
|
||||
# Step 21 (formerly 15): Add a camera to the level
|
||||
# Create a camera actor
|
||||
response = send_mcp_command("create_actor", {
|
||||
"name": "GameCamera",
|
||||
"type": "CameraActor",
|
||||
"location": [500.0, 0.0, 250.0], # Position camera to view the bird from a distance
|
||||
"rotation": [0.0, 180.0, 0.0], # Point camera at bird's spawn location
|
||||
"scale": [1.0, 1.0, 1.0]
|
||||
})
|
||||
|
||||
if not response or response.get("status") != "success":
|
||||
logger.error(f"Failed to create camera actor: {response}")
|
||||
return
|
||||
|
||||
logger.info("Camera actor created successfully!")
|
||||
|
||||
logger.info("You can now press spacebar to make the bird flap! The camera will automatically view the bird.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,366 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Test script for blueprint input mapping via MCP.
|
||||
This script creates input mappings and connects them to functions in a Blueprint.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import socket
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
# Add the parent directory to the path so we can import the server module
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger("TestInputMapping")
|
||||
|
||||
def send_command(sock: socket.socket, command: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""Send a command to the Unreal MCP server and get the response."""
|
||||
try:
|
||||
# Create command object
|
||||
command_obj = {
|
||||
"type": command,
|
||||
"params": params
|
||||
}
|
||||
|
||||
# Convert to JSON and send
|
||||
command_json = json.dumps(command_obj)
|
||||
logger.info(f"Sending command: {command_json}")
|
||||
sock.sendall(command_json.encode('utf-8'))
|
||||
|
||||
# Receive response
|
||||
chunks = []
|
||||
while True:
|
||||
chunk = sock.recv(4096)
|
||||
if not chunk:
|
||||
break
|
||||
chunks.append(chunk)
|
||||
|
||||
# Try parsing to see if we have a complete response
|
||||
try:
|
||||
data = b''.join(chunks)
|
||||
json.loads(data.decode('utf-8'))
|
||||
# If we can parse it, we have the complete response
|
||||
break
|
||||
except json.JSONDecodeError:
|
||||
# Not a complete JSON object yet, continue receiving
|
||||
continue
|
||||
|
||||
# Parse response
|
||||
data = b''.join(chunks)
|
||||
response = json.loads(data.decode('utf-8'))
|
||||
logger.info(f"Received response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending command: {e}")
|
||||
return None
|
||||
|
||||
def setup_input_mapping(sock: socket.socket, action_name: str, key: str, input_type: str = "Action") -> bool:
|
||||
"""Helper function to set up an input mapping."""
|
||||
input_params = {
|
||||
"action_name": action_name,
|
||||
"key": key,
|
||||
"input_type": input_type
|
||||
}
|
||||
|
||||
response = send_command(sock, "create_input_mapping", input_params)
|
||||
|
||||
success = (response and
|
||||
response.get("status") == "success" and
|
||||
response.get("result", {}).get("success"))
|
||||
|
||||
if success:
|
||||
logger.info(f"Input mapping '{action_name}' created with key '{key}'")
|
||||
else:
|
||||
logger.error(f"Failed to create input mapping: {response}")
|
||||
|
||||
return success
|
||||
|
||||
def main():
|
||||
"""Main function to test input mappings in blueprints."""
|
||||
try:
|
||||
# Step 1: Create a controller blueprint
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
bp_params = {
|
||||
"name": "InputControllerBP",
|
||||
"parent_class": "Actor"
|
||||
}
|
||||
|
||||
response = send_command(sock, "create_blueprint", bp_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to create blueprint: {response}")
|
||||
return
|
||||
|
||||
# Check if blueprint already existed
|
||||
if response.get("result", {}).get("already_exists"):
|
||||
logger.info(f"Blueprint 'InputControllerBP' already exists, reusing it")
|
||||
else:
|
||||
logger.info("Controller blueprint created successfully!")
|
||||
|
||||
# Close and reopen connection for each command
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 2: Add variables to track state
|
||||
var_params_list = [
|
||||
{
|
||||
"blueprint_name": "InputControllerBP",
|
||||
"variable_name": "Score",
|
||||
"variable_type": "Integer",
|
||||
"default_value": 0,
|
||||
"is_exposed": True
|
||||
},
|
||||
{
|
||||
"blueprint_name": "InputControllerBP",
|
||||
"variable_name": "IsGameActive",
|
||||
"variable_type": "Boolean",
|
||||
"default_value": True,
|
||||
"is_exposed": True
|
||||
},
|
||||
{
|
||||
"blueprint_name": "InputControllerBP",
|
||||
"variable_name": "PlayerName",
|
||||
"variable_type": "String",
|
||||
"default_value": "Player1",
|
||||
"is_exposed": True
|
||||
}
|
||||
]
|
||||
|
||||
for var_params in var_params_list:
|
||||
response = send_command(sock, "add_blueprint_variable", var_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add variable: {response}")
|
||||
return
|
||||
|
||||
logger.info(f"Variable {var_params['variable_name']} added successfully!")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 3: Set up input mappings for a simple game controller
|
||||
input_mappings = [
|
||||
("Jump", "SpaceBar", "Action"),
|
||||
("Pause", "P", "Action"),
|
||||
("Restart", "R", "Action"),
|
||||
("MoveForward", "W", "Axis"),
|
||||
("MoveRight", "D", "Axis")
|
||||
]
|
||||
|
||||
for action_name, key, input_type in input_mappings:
|
||||
success = setup_input_mapping(sock, action_name, key, input_type)
|
||||
if not success:
|
||||
return
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 4: Add event nodes for BeginPlay and input actions
|
||||
event_node_ids = {}
|
||||
|
||||
# BeginPlay event
|
||||
begin_play_params = {
|
||||
"blueprint_name": "InputControllerBP",
|
||||
"event_type": "BeginPlay",
|
||||
"node_position": [0, 0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_event_node", begin_play_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add BeginPlay event node: {response}")
|
||||
return
|
||||
|
||||
logger.info("BeginPlay event node added successfully!")
|
||||
event_node_ids["BeginPlay"] = response.get("result", {}).get("node_id")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 5: Add function nodes for different actions
|
||||
function_node_ids = {}
|
||||
|
||||
# Print String function for BeginPlay
|
||||
function_params = {
|
||||
"blueprint_name": "InputControllerBP",
|
||||
"target": "self",
|
||||
"function_name": "PrintString",
|
||||
"params": {
|
||||
"InString": "Input Controller Initialized",
|
||||
"Duration": 5.0
|
||||
},
|
||||
"node_position": [250, 0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_function_node", function_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add PrintString function node: {response}")
|
||||
return
|
||||
|
||||
logger.info("PrintString function node added successfully!")
|
||||
function_node_ids["PrintInit"] = response.get("result", {}).get("node_id")
|
||||
|
||||
# For each action, add a event node and function node
|
||||
action_positions = {
|
||||
"Jump": [0, 150],
|
||||
"Pause": [0, 300],
|
||||
"Restart": [0, 450],
|
||||
"MoveForward": [0, 600],
|
||||
"MoveRight": [0, 750]
|
||||
}
|
||||
|
||||
function_positions = {
|
||||
"Jump": [250, 150],
|
||||
"Pause": [250, 300],
|
||||
"Restart": [250, 450],
|
||||
"MoveForward": [250, 600],
|
||||
"MoveRight": [250, 750]
|
||||
}
|
||||
|
||||
# Since we can't directly create InputAction nodes, we'll simulate with BeginPlay
|
||||
for action_name in ["Jump", "Pause", "Restart"]:
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Create placeholder event node
|
||||
event_params = {
|
||||
"blueprint_name": "InputControllerBP",
|
||||
"event_type": "BeginPlay", # Using BeginPlay as a placeholder for InputAction
|
||||
"node_position": action_positions[action_name]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_event_node", event_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add event node for {action_name}: {response}")
|
||||
return
|
||||
|
||||
logger.info(f"Event node for {action_name} added (simulated)")
|
||||
event_node_ids[action_name] = response.get("result", {}).get("node_id")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Create function node to print what action was performed
|
||||
function_params = {
|
||||
"blueprint_name": "InputControllerBP",
|
||||
"target": "self",
|
||||
"function_name": "PrintString",
|
||||
"params": {
|
||||
"InString": f"{action_name} Action Triggered",
|
||||
"Duration": 2.0
|
||||
},
|
||||
"node_position": function_positions[action_name]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_function_node", function_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add function node for {action_name}: {response}")
|
||||
return
|
||||
|
||||
logger.info(f"Function node for {action_name} added successfully!")
|
||||
function_node_ids[action_name] = response.get("result", {}).get("node_id")
|
||||
|
||||
# Step 6: Connect nodes
|
||||
for action_name in ["BeginPlay"] + list(action_positions.keys())[:3]: # BeginPlay + first 3 actions
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Connect appropriate function based on event type
|
||||
if action_name == "BeginPlay":
|
||||
target_function = "PrintInit"
|
||||
else:
|
||||
target_function = action_name
|
||||
|
||||
connect_params = {
|
||||
"blueprint_name": "InputControllerBP",
|
||||
"source_node_id": event_node_ids[action_name],
|
||||
"source_pin": "Then", # Execute pin on event
|
||||
"target_node_id": function_node_ids[target_function],
|
||||
"target_pin": "execute" # Execute pin on function
|
||||
}
|
||||
|
||||
response = send_command(sock, "connect_blueprint_nodes", connect_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to connect nodes for {action_name}: {response}")
|
||||
return
|
||||
|
||||
logger.info(f"Connected {action_name} event to function successfully!")
|
||||
|
||||
# Step 7: Compile the blueprint
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
compile_params = {
|
||||
"blueprint_name": "InputControllerBP"
|
||||
}
|
||||
|
||||
response = send_command(sock, "compile_blueprint", compile_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to compile blueprint: {response}")
|
||||
return
|
||||
|
||||
logger.info("Blueprint compiled successfully!")
|
||||
|
||||
# Step 8: Spawn the controller in the level
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
spawn_params = {
|
||||
"blueprint_name": "InputControllerBP",
|
||||
"actor_name": "InputController",
|
||||
"location": [0.0, 0.0, 100.0],
|
||||
"rotation": [0.0, 0.0, 0.0],
|
||||
"scale": [1.0, 1.0, 1.0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "spawn_blueprint_actor", spawn_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to spawn blueprint actor: {response}")
|
||||
return
|
||||
|
||||
logger.info("Input controller spawned successfully!")
|
||||
logger.info("The controller will run the BeginPlay event and show a message.")
|
||||
logger.info("The following input mappings have been set up:")
|
||||
|
||||
for action_name, key, input_type in input_mappings:
|
||||
logger.info(f" - {action_name}: {key} ({input_type})")
|
||||
|
||||
# Close final connection
|
||||
sock.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,390 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Test script for blueprint variables and physics via MCP.
|
||||
This script creates a physical obstacle with configurable variables controlling its behavior.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import socket
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
# Add the parent directory to the path so we can import the server module
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger("TestPhysicsVariables")
|
||||
|
||||
def send_command(sock: socket.socket, command: str, params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""Send a command to the Unreal MCP server and get the response."""
|
||||
try:
|
||||
# Create command object
|
||||
command_obj = {
|
||||
"type": command,
|
||||
"params": params
|
||||
}
|
||||
|
||||
# Convert to JSON and send
|
||||
command_json = json.dumps(command_obj)
|
||||
logger.info(f"Sending command: {command_json}")
|
||||
sock.sendall(command_json.encode('utf-8'))
|
||||
|
||||
# Receive response
|
||||
chunks = []
|
||||
while True:
|
||||
chunk = sock.recv(4096)
|
||||
if not chunk:
|
||||
break
|
||||
chunks.append(chunk)
|
||||
|
||||
# Try parsing to see if we have a complete response
|
||||
try:
|
||||
data = b''.join(chunks)
|
||||
json.loads(data.decode('utf-8'))
|
||||
# If we can parse it, we have the complete response
|
||||
break
|
||||
except json.JSONDecodeError:
|
||||
# Not a complete JSON object yet, continue receiving
|
||||
continue
|
||||
|
||||
# Parse response
|
||||
data = b''.join(chunks)
|
||||
response = json.loads(data.decode('utf-8'))
|
||||
logger.info(f"Received response: {response}")
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending command: {e}")
|
||||
return None
|
||||
|
||||
def main():
|
||||
"""Main function to test physics variables in blueprints."""
|
||||
try:
|
||||
# Step 1: Create blueprint for a physics-based obstacle
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
bp_params = {
|
||||
"name": "PhysicsObstacleBP",
|
||||
"parent_class": "Actor"
|
||||
}
|
||||
|
||||
response = send_command(sock, "create_blueprint", bp_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to create blueprint: {response}")
|
||||
return
|
||||
|
||||
# Check if blueprint already existed
|
||||
if response.get("result", {}).get("already_exists"):
|
||||
logger.info(f"Blueprint 'PhysicsObstacleBP' already exists, reusing it")
|
||||
else:
|
||||
logger.info("Blueprint created successfully!")
|
||||
|
||||
# Close and reopen connection for each command
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 2: Add variables to control physics behavior
|
||||
var_params_list = [
|
||||
{
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"variable_name": "Mass",
|
||||
"variable_type": "Float",
|
||||
"default_value": 10.0,
|
||||
"is_exposed": True
|
||||
},
|
||||
{
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"variable_name": "RotationSpeed",
|
||||
"variable_type": "Float",
|
||||
"default_value": 100.0,
|
||||
"is_exposed": True
|
||||
},
|
||||
{
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"variable_name": "BounceFactor",
|
||||
"variable_type": "Float",
|
||||
"default_value": 0.8,
|
||||
"is_exposed": True
|
||||
},
|
||||
{
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"variable_name": "IsTrigger",
|
||||
"variable_type": "Boolean",
|
||||
"default_value": False,
|
||||
"is_exposed": True
|
||||
}
|
||||
]
|
||||
|
||||
for var_params in var_params_list:
|
||||
response = send_command(sock, "add_blueprint_variable", var_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add variable: {response}")
|
||||
return
|
||||
|
||||
logger.info(f"Variable {var_params['variable_name']} added successfully!")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 3: Add a static mesh component for the obstacle
|
||||
component_params = {
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"component_type": "StaticMesh",
|
||||
"component_name": "ObstacleMesh",
|
||||
"location": [0.0, 0.0, 0.0],
|
||||
"rotation": [0.0, 0.0, 0.0],
|
||||
"scale": [1.0, 1.0, 1.0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_component_to_blueprint", component_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add component: {response}")
|
||||
return
|
||||
|
||||
logger.info("Obstacle mesh component added successfully!")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 4: Set physics properties using the variables
|
||||
physics_params = {
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"component_name": "ObstacleMesh",
|
||||
"simulate_physics": True,
|
||||
"gravity_enabled": True
|
||||
}
|
||||
|
||||
response = send_command(sock, "set_physics_properties", physics_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to set physics properties: {response}")
|
||||
return
|
||||
|
||||
logger.info("Physics properties set successfully!")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 5: Add BeginPlay event node
|
||||
begin_play_params = {
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"event_type": "BeginPlay",
|
||||
"node_position": [0, 0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_event_node", begin_play_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add BeginPlay event node: {response}")
|
||||
return
|
||||
|
||||
logger.info("BeginPlay event node added successfully!")
|
||||
|
||||
# Save the node ID for later connections
|
||||
begin_play_node_id = response.get("result", {}).get("node_id")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 6: Add Tick event node
|
||||
tick_params = {
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"event_type": "Tick",
|
||||
"node_position": [0, 200]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_event_node", tick_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add Tick event node: {response}")
|
||||
return
|
||||
|
||||
logger.info("Tick event node added successfully!")
|
||||
|
||||
# Save the node ID for later connections
|
||||
tick_node_id = response.get("result", {}).get("node_id")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 7: Add function node to set mesh physics settings from variables
|
||||
function_params = {
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"target": "ObstacleMesh",
|
||||
"function_name": "SetMassScale",
|
||||
"params": {
|
||||
"BoneName": "None",
|
||||
"InMassScale": 10.0 # This will be replaced by the Mass variable dynamically
|
||||
},
|
||||
"node_position": [300, 0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_function_node", function_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add function node: {response}")
|
||||
return
|
||||
|
||||
logger.info("SetMassScale function node added successfully!")
|
||||
|
||||
# Save the node ID for later connections
|
||||
set_mass_node_id = response.get("result", {}).get("node_id")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 8: Add function node to rotate the obstacle
|
||||
function_params = {
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"target": "ObstacleMesh",
|
||||
"function_name": "AddTorqueInRadians",
|
||||
"params": {
|
||||
"Torque": [0, 0, 100.0], # This should be connected to the RotationSpeed variable
|
||||
"BoneName": "None",
|
||||
"bAccelChange": True
|
||||
},
|
||||
"node_position": [300, 200]
|
||||
}
|
||||
|
||||
response = send_command(sock, "add_blueprint_function_node", function_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to add function node: {response}")
|
||||
return
|
||||
|
||||
logger.info("AddTorqueInRadians function node added successfully!")
|
||||
|
||||
# Save the node ID for later connections
|
||||
add_torque_node_id = response.get("result", {}).get("node_id")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 9: Connect BeginPlay to SetMassScale
|
||||
connect_params = {
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"source_node_id": begin_play_node_id,
|
||||
"source_pin": "Then", # Execute pin on BeginPlay event
|
||||
"target_node_id": set_mass_node_id,
|
||||
"target_pin": "execute" # Execute pin on function
|
||||
}
|
||||
|
||||
response = send_command(sock, "connect_blueprint_nodes", connect_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to connect nodes: {response}")
|
||||
return
|
||||
|
||||
logger.info("BeginPlay connected to SetMassScale successfully!")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 10: Connect Tick to AddTorqueInRadians
|
||||
connect_params = {
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"source_node_id": tick_node_id,
|
||||
"source_pin": "Then", # Execute pin on Tick event
|
||||
"target_node_id": add_torque_node_id,
|
||||
"target_pin": "execute" # Execute pin on function
|
||||
}
|
||||
|
||||
response = send_command(sock, "connect_blueprint_nodes", connect_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to connect nodes: {response}")
|
||||
return
|
||||
|
||||
logger.info("Tick connected to AddTorqueInRadians successfully!")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 11: Compile the blueprint
|
||||
compile_params = {
|
||||
"blueprint_name": "PhysicsObstacleBP"
|
||||
}
|
||||
|
||||
response = send_command(sock, "compile_blueprint", compile_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to compile blueprint: {response}")
|
||||
return
|
||||
|
||||
logger.info("Blueprint compiled successfully!")
|
||||
|
||||
# Close and reopen connection
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
# Step 12: Spawn multiple instances of the obstacle at different positions
|
||||
positions = [
|
||||
[100.0, 0.0, 200.0],
|
||||
[0.0, 100.0, 200.0],
|
||||
[-100.0, 0.0, 200.0],
|
||||
[0.0, -100.0, 200.0]
|
||||
]
|
||||
|
||||
for i, position in enumerate(positions):
|
||||
spawn_params = {
|
||||
"blueprint_name": "PhysicsObstacleBP",
|
||||
"actor_name": f"Obstacle_{i+1}",
|
||||
"location": position,
|
||||
"rotation": [0.0, 0.0, 45.0 * i], # Different rotations
|
||||
"scale": [1.0, 1.0, 1.0]
|
||||
}
|
||||
|
||||
response = send_command(sock, "spawn_blueprint_actor", spawn_params)
|
||||
|
||||
if not response or response.get("status") != "success" or not response.get("result", {}).get("success"):
|
||||
logger.error(f"Failed to spawn blueprint actor {i+1}: {response}")
|
||||
return
|
||||
|
||||
logger.info(f"Obstacle {i+1} spawned successfully!")
|
||||
|
||||
# Close and reopen connection
|
||||
if i < len(positions) - 1: # Don't reopen if this is the last one
|
||||
sock.close()
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(("127.0.0.1", 55557))
|
||||
|
||||
logger.info("Physics obstacles created successfully!")
|
||||
logger.info("The obstacles should start rotating due to the Tick event connection")
|
||||
|
||||
# Close final connection
|
||||
sock.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user