Fixing MCPServer startup for claude code

This commit is contained in:
ange committed 2026-04-24 09:56:25 +02:00
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()