create_simple_game.py•26.2 kB
#!/usr/bin/env python
"""
Example script to create a simple game using MCP.
This script demonstrates how to use the Unreal MCP to create a simple game with:
- A player character that can move around
- Collectible items that disappear when the player touches them
- A score counter displayed on screen
"""
import logging
import sys
import os
import time
# Add the parent directory to the path so we can import the unreal_mcp_server module
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from unreal_mcp_server import get_unreal_connection
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger("SimpleGameExample")
def create_simple_game():
"""Create a simple game with a player character, collectibles, and score.
This function creates:
1. A player character Blueprint with movement controls
2. A collectible item Blueprint
3. A HUD with score display
4. Input mappings for player movement
5. A GameMode to tie everything together
Returns:
The response from the final operation, or None if there was an error.
"""
# Get the connection to Unreal Engine
conn = get_unreal_connection()
if not conn:
logger.error("Failed to connect to Unreal Engine")
return None
# Step 1: Create a new level
logger.info("Creating a new level")
response = conn.send_command("create_level", {
"level_name": "SimpleGameLevel",
"save_path": "/Game/Levels"
})
if response.get("status") == "error":
logger.error(f"Error creating level: {response.get('error')}")
return response
# Step 2: Create input mappings
logger.info("Creating input mappings")
response = conn.send_command("create_input_mapping", {
"mapping_name": "IM_SimpleGame",
"save_path": "/Game/Input",
"axis_mappings": [
{
"axis_name": "MoveForward",
"key": "W",
"scale": 1.0
},
{
"axis_name": "MoveForward",
"key": "S",
"scale": -1.0
},
{
"axis_name": "MoveRight",
"key": "D",
"scale": 1.0
},
{
"axis_name": "MoveRight",
"key": "A",
"scale": -1.0
}
],
"action_mappings": [
{
"action_name": "Jump",
"key": "SpaceBar"
}
]
})
if response.get("status") == "error":
logger.error(f"Error creating input mappings: {response.get('error')}")
return response
# Step 3: Create the player character Blueprint
logger.info("Creating player character Blueprint")
response = conn.send_command("create_blueprint", {
"name": "BP_SimplePlayer",
"parent_class": "Character",
"path": "/Game/Blueprints"
})
if response.get("status") == "error":
logger.error(f"Error creating player Blueprint: {response.get('error')}")
return response
player_bp_path = "/Game/Blueprints/BP_SimplePlayer"
# Step 4: Set up the player character components
logger.info("Setting up player character components")
# Add a camera component
response = conn.send_command("add_component_to_blueprint", {
"blueprint_name": player_bp_path,
"component_type": "CameraComponent",
"component_name": "PlayerCamera",
"location": [0, 0, 50], # Position the camera above the character
"rotation": [0, 0, 0],
"parent_component": "CapsuleComponent"
})
if response.get("status") == "error":
logger.error(f"Error adding camera component: {response.get('error')}")
return response
# Set the camera as the view target
response = conn.send_command("set_component_property", {
"blueprint_name": player_bp_path,
"component_name": "PlayerCamera",
"property_name": "bUsePawnControlRotation",
"property_value": True
})
if response.get("status") == "error":
logger.error(f"Error setting camera property: {response.get('error')}")
return response
# Step 5: Add a score variable to the player
logger.info("Adding score variable to player")
response = conn.send_command("add_blueprint_variable", {
"blueprint_name": player_bp_path,
"variable_name": "Score",
"variable_type": "Integer",
"default_value": 0,
"is_exposed": True,
"category": "Gameplay",
"tooltip": "Player's current score"
})
if response.get("status") == "error":
logger.error(f"Error adding score variable: {response.get('error')}")
return response
# Step 6: Set up player movement input
logger.info("Setting up player movement input")
# Add MoveForward input action
response = conn.send_command("add_blueprint_input_action_node", {
"blueprint_name": player_bp_path,
"input_name": "MoveForward",
"is_action": False # This is an axis mapping
})
if response.get("status") == "error":
logger.error(f"Error adding MoveForward input: {response.get('error')}")
return response
move_forward_node_id = response.get("node_id")
# Add Add Movement Input function
response = conn.send_command("add_blueprint_function_node", {
"blueprint_name": player_bp_path,
"function_name": "AddMovementInput",
"target": "self"
})
if response.get("status") == "error":
logger.error(f"Error adding AddMovementInput function: {response.get('error')}")
return response
add_movement_forward_node_id = response.get("node_id")
# Add Get Forward Vector function
response = conn.send_command("add_blueprint_function_node", {
"blueprint_name": player_bp_path,
"function_name": "GetActorForwardVector",
"target": "self"
})
if response.get("status") == "error":
logger.error(f"Error adding GetActorForwardVector function: {response.get('error')}")
return response
get_forward_vector_node_id = response.get("node_id")
# Connect MoveForward to AddMovementInput
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": player_bp_path,
"source_node_id": move_forward_node_id,
"source_pin": "OutputDelegate",
"target_node_id": add_movement_forward_node_id,
"target_pin": "execute"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Connect MoveForward value to AddMovementInput ScaleValue
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": player_bp_path,
"source_node_id": move_forward_node_id,
"source_pin": "AxisValue",
"target_node_id": add_movement_forward_node_id,
"target_pin": "ScaleValue"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Connect GetForwardVector to AddMovementInput WorldDirection
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": player_bp_path,
"source_node_id": get_forward_vector_node_id,
"source_pin": "ReturnValue",
"target_node_id": add_movement_forward_node_id,
"target_pin": "WorldDirection"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Repeat for MoveRight input
response = conn.send_command("add_blueprint_input_action_node", {
"blueprint_name": player_bp_path,
"input_name": "MoveRight",
"is_action": False # This is an axis mapping
})
if response.get("status") == "error":
logger.error(f"Error adding MoveRight input: {response.get('error')}")
return response
move_right_node_id = response.get("node_id")
# Add Add Movement Input function for right movement
response = conn.send_command("add_blueprint_function_node", {
"blueprint_name": player_bp_path,
"function_name": "AddMovementInput",
"target": "self"
})
if response.get("status") == "error":
logger.error(f"Error adding AddMovementInput function: {response.get('error')}")
return response
add_movement_right_node_id = response.get("node_id")
# Add Get Right Vector function
response = conn.send_command("add_blueprint_function_node", {
"blueprint_name": player_bp_path,
"function_name": "GetActorRightVector",
"target": "self"
})
if response.get("status") == "error":
logger.error(f"Error adding GetActorRightVector function: {response.get('error')}")
return response
get_right_vector_node_id = response.get("node_id")
# Connect MoveRight to AddMovementInput
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": player_bp_path,
"source_node_id": move_right_node_id,
"source_pin": "OutputDelegate",
"target_node_id": add_movement_right_node_id,
"target_pin": "execute"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Connect MoveRight value to AddMovementInput ScaleValue
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": player_bp_path,
"source_node_id": move_right_node_id,
"source_pin": "AxisValue",
"target_node_id": add_movement_right_node_id,
"target_pin": "ScaleValue"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Connect GetRightVector to AddMovementInput WorldDirection
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": player_bp_path,
"source_node_id": get_right_vector_node_id,
"source_pin": "ReturnValue",
"target_node_id": add_movement_right_node_id,
"target_pin": "WorldDirection"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Step 7: Create the collectible item Blueprint
logger.info("Creating collectible item Blueprint")
response = conn.send_command("create_blueprint", {
"name": "BP_Collectible",
"parent_class": "Actor",
"path": "/Game/Blueprints"
})
if response.get("status") == "error":
logger.error(f"Error creating collectible Blueprint: {response.get('error')}")
return response
collectible_bp_path = "/Game/Blueprints/BP_Collectible"
# Add a static mesh component to the collectible
response = conn.send_command("add_component_to_blueprint", {
"blueprint_name": collectible_bp_path,
"component_type": "StaticMeshComponent",
"component_name": "CollectibleMesh",
"location": [0, 0, 0],
"rotation": [0, 0, 0],
"scale": [0.5, 0.5, 0.5] # Make it smaller than default
})
if response.get("status") == "error":
logger.error(f"Error adding static mesh component: {response.get('error')}")
return response
# Set the static mesh to a sphere
response = conn.send_command("set_static_mesh_properties", {
"blueprint_name": collectible_bp_path,
"component_name": "CollectibleMesh",
"static_mesh": "/Engine/BasicShapes/Sphere.Sphere",
"materials": ["/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"]
})
if response.get("status") == "error":
logger.error(f"Error setting static mesh: {response.get('error')}")
return response
# Add a sphere collision component
response = conn.send_command("add_component_to_blueprint", {
"blueprint_name": collectible_bp_path,
"component_type": "SphereComponent",
"component_name": "CollisionSphere",
"location": [0, 0, 0],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"parent_component": "CollectibleMesh"
})
if response.get("status") == "error":
logger.error(f"Error adding collision component: {response.get('error')}")
return response
# Set the collision sphere radius
response = conn.send_command("set_component_property", {
"blueprint_name": collectible_bp_path,
"component_name": "CollisionSphere",
"property_name": "SphereRadius",
"property_value": 50.0
})
if response.get("status") == "error":
logger.error(f"Error setting collision sphere radius: {response.get('error')}")
return response
# Add OnComponentBeginOverlap event
response = conn.send_command("add_blueprint_event_node", {
"blueprint_name": collectible_bp_path,
"event_type": "ComponentBeginOverlap",
"component_name": "CollisionSphere"
})
if response.get("status") == "error":
logger.error(f"Error adding overlap event: {response.get('error')}")
return response
overlap_node_id = response.get("node_id")
# Add Cast to BP_SimplePlayer node
response = conn.send_command("add_blueprint_function_node", {
"blueprint_name": collectible_bp_path,
"function_name": "Cast",
"category": "Utilities|Casting",
"target_type": "BP_SimplePlayer"
})
if response.get("status") == "error":
logger.error(f"Error adding cast node: {response.get('error')}")
return response
cast_node_id = response.get("node_id")
# Connect overlap event to cast node
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": collectible_bp_path,
"source_node_id": overlap_node_id,
"source_pin": "OutputDelegate",
"target_node_id": cast_node_id,
"target_pin": "execute"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Connect Other Actor to cast node
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": collectible_bp_path,
"source_node_id": overlap_node_id,
"source_pin": "OtherActor",
"target_node_id": cast_node_id,
"target_pin": "Object"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Add Branch node (to check if cast succeeded)
response = conn.send_command("add_blueprint_function_node", {
"blueprint_name": collectible_bp_path,
"function_name": "Branch",
"category": "Flow Control"
})
if response.get("status") == "error":
logger.error(f"Error adding branch node: {response.get('error')}")
return response
branch_node_id = response.get("node_id")
# Connect cast node to branch
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": collectible_bp_path,
"source_node_id": cast_node_id,
"source_pin": "then",
"target_node_id": branch_node_id,
"target_pin": "execute"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Connect cast success to branch condition
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": collectible_bp_path,
"source_node_id": cast_node_id,
"source_pin": "CastSucceeded",
"target_node_id": branch_node_id,
"target_pin": "Condition"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Add Get Score node
response = conn.send_command("add_blueprint_get_variable_node", {
"blueprint_name": collectible_bp_path,
"variable_name": "Score",
"target": "BP_SimplePlayer"
})
if response.get("status") == "error":
logger.error(f"Error adding Get Score node: {response.get('error')}")
return response
get_score_node_id = response.get("node_id")
# Add integer + integer node
response = conn.send_command("add_blueprint_function_node", {
"blueprint_name": collectible_bp_path,
"function_name": "Add_IntInt",
"category": "Math|Integer",
"inputs": {
"B": 1 # Add 1 to the score
}
})
if response.get("status") == "error":
logger.error(f"Error adding Add_IntInt node: {response.get('error')}")
return response
add_score_node_id = response.get("node_id")
# Connect Get Score to Add
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": collectible_bp_path,
"source_node_id": get_score_node_id,
"source_pin": "ReturnValue",
"target_node_id": add_score_node_id,
"target_pin": "A"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Add Set Score node
response = conn.send_command("add_blueprint_set_variable_node", {
"blueprint_name": collectible_bp_path,
"variable_name": "Score",
"target": "BP_SimplePlayer"
})
if response.get("status") == "error":
logger.error(f"Error adding Set Score node: {response.get('error')}")
return response
set_score_node_id = response.get("node_id")
# Connect Branch True to Set Score
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": collectible_bp_path,
"source_node_id": branch_node_id,
"source_pin": "True",
"target_node_id": set_score_node_id,
"target_pin": "execute"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Connect Add Score to Set Score
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": collectible_bp_path,
"source_node_id": add_score_node_id,
"source_pin": "ReturnValue",
"target_node_id": set_score_node_id,
"target_pin": "Value"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Connect Cast result to Set Score target
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": collectible_bp_path,
"source_node_id": cast_node_id,
"source_pin": "As BP Simple Player",
"target_node_id": set_score_node_id,
"target_pin": "Target"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Add Destroy Actor node
response = conn.send_command("add_blueprint_function_node", {
"blueprint_name": collectible_bp_path,
"function_name": "DestroyActor",
"target": "self"
})
if response.get("status") == "error":
logger.error(f"Error adding DestroyActor node: {response.get('error')}")
return response
destroy_node_id = response.get("node_id")
# Connect Set Score to Destroy Actor
response = conn.send_command("connect_blueprint_nodes", {
"blueprint_name": collectible_bp_path,
"source_node_id": set_score_node_id,
"source_pin": "then",
"target_node_id": destroy_node_id,
"target_pin": "execute"
})
if response.get("status") == "error":
logger.error(f"Error connecting nodes: {response.get('error')}")
return response
# Step 8: Create a HUD Widget Blueprint
logger.info("Creating HUD Widget Blueprint")
response = conn.send_command("create_umg_widget_blueprint", {
"widget_name": "WBP_GameHUD",
"save_path": "/Game/UI"
})
if response.get("status") == "error":
logger.error(f"Error creating HUD widget: {response.get('error')}")
return response
hud_widget_path = "/Game/UI/WBP_GameHUD"
# Add a Text Block for the score
response = conn.send_command("add_umg_text_block", {
"widget_blueprint": hud_widget_path,
"text_block_name": "ScoreText",
"text": "Score: 0",
"position": [50, 50],
"size": [200, 50],
"color": [1, 1, 1, 1], # White
"font_size": 24
})
if response.get("status") == "error":
logger.error(f"Error adding text block: {response.get('error')}")
return response
# Step 9: Create a GameMode Blueprint
logger.info("Creating GameMode Blueprint")
response = conn.send_command("create_gamemode_blueprint", {
"gamemode_name": "BP_SimpleGameMode",
"save_path": "/Game/Blueprints",
"default_pawn_class": "/Game/Blueprints/BP_SimplePlayer",
"hud_class": "/Game/UI/WBP_GameHUD"
})
if response.get("status") == "error":
logger.error(f"Error creating GameMode: {response.get('error')}")
return response
# Step 10: Set the default GameMode
logger.info("Setting default GameMode")
response = conn.send_command("set_default_gamemode", {
"gamemode_class": "/Game/Blueprints/BP_SimpleGameMode"
})
if response.get("status") == "error":
logger.error(f"Error setting default GameMode: {response.get('error')}")
return response
# Step 11: Spawn some collectibles in the level
logger.info("Spawning collectibles in the level")
for i in range(10):
# Calculate a position in a circle
angle = i * 36 # 360 degrees / 10 items
radius = 500
x = radius * math.cos(math.radians(angle))
y = radius * math.sin(math.radians(angle))
response = conn.send_command("spawn_blueprint_actor", {
"blueprint_path": collectible_bp_path,
"actor_name": f"Collectible_{i}",
"location": [x, y, 100], # Positioned in a circle
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
})
if response.get("status") == "error":
logger.error(f"Error spawning collectible: {response.get('error')}")
return response
# Step 12: Add a floor to the level
logger.info("Adding a floor to the level")
response = conn.send_command("spawn_actor", {
"actor_name": "Floor",
"actor_type": "StaticMeshActor",
"location": [0, 0, 0],
"rotation": [0, 0, 0],
"scale": [10, 10, 0.1] # Make it wide and flat
})
if response.get("status") == "error":
logger.error(f"Error spawning floor: {response.get('error')}")
return response
floor_actor_name = response.get("actor_name")
# Set the floor's static mesh to a cube
response = conn.send_command("set_actor_property", {
"actor_name": floor_actor_name,
"property_name": "StaticMeshComponent.StaticMesh",
"property_value": "/Engine/BasicShapes/Cube.Cube"
})
if response.get("status") == "error":
logger.error(f"Error setting floor mesh: {response.get('error')}")
return response
# Step 13: Spawn the player character
logger.info("Spawning player character")
response = conn.send_command("spawn_blueprint_actor", {
"blueprint_path": player_bp_path,
"actor_name": "Player",
"location": [0, 0, 200], # Start above the floor
"rotation": [0, 0, 0],
"scale": [1, 1, 1]
})
if response.get("status") == "error":
logger.error(f"Error spawning player: {response.get('error')}")
return response
# Step 14: Save the level
logger.info("Saving the level")
response = conn.send_command("save_level", {
"level_path": "/Game/Levels/SimpleGameLevel"
})
if response.get("status") == "error":
logger.error(f"Error saving level: {response.get('error')}")
return response
# Step 15: Start Play-in-Editor mode
logger.info("Starting Play-in-Editor mode")
response = conn.send_command("start_play_in_editor", {})
if response.get("status") == "error":
logger.error(f"Error starting Play-in-Editor: {response.get('error')}")
return response
logger.info("Successfully created a simple game!")
return response
if __name__ == "__main__":
# Create the simple game
result = create_simple_game()
if result and result.get("status") != "error":
print("Successfully created a simple game!")
print("The game is now running in Play-in-Editor mode.")
print("Use WASD to move the character and collect the spheres.")
# Keep the script running for a while to allow playing
print("Press Ctrl+C to exit...")
try:
time.sleep(300) # Run for 5 minutes
except KeyboardInterrupt:
# Stop Play-in-Editor mode
conn = get_unreal_connection()
if conn:
conn.send_command("stop_play_in_editor", {})
print("Exiting...")
else:
print("Failed to create the game. Make sure Unreal Engine is running.")