"""
Auto-generate test level for UE-MCP pytest tests.
This script is executed via editor_execute_script MCP tool to create
a standardized test level with all required actors for integration tests.
The generated level includes:
- Floor (large StaticMeshActor for ground detection)
- PlayerStart (for PIE session spawn)
- Multiple BP_ThirdPersonCharacter (for actor tracing, PIE capture, and fuzzy matching)
- Multiple StaticMeshActors (for fuzzy matching tests)
- PhysicsSphere and PhysicsCube (physics-enabled objects that fall when game starts)
- DirectionalLight and SkyLight (for basic lighting)
- SkyAtmosphere (for realistic blue sky)
- VolumetricCloud (for white clouds)
- ExponentialHeightFog (for atmospheric depth)
Usage:
Called automatically by ensure_test_level fixture in conftest.py
"""
import json
import unreal
# Configuration
LEVEL_PATH = "/Game/Tests/AutoGeneratedTestLevel"
BP_CHARACTER_PATH = "/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"
def get_subsystems():
"""Get required editor subsystems."""
level_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
return level_subsystem, actor_subsystem
def check_level_exists():
"""Check if the test level already exists."""
return unreal.EditorAssetLibrary.does_asset_exist(LEVEL_PATH)
def create_floor(actor_subsystem):
"""Create a large floor for ground detection."""
floor = actor_subsystem.spawn_actor_from_class(
unreal.StaticMeshActor, unreal.Vector(0, 0, 0), unreal.Rotator(0, 0, 0)
)
floor.set_actor_label("Floor")
floor.set_actor_scale3d(unreal.Vector(100, 100, 1)) # 10000x10000x100 units
# Set cube mesh
mesh_comp = floor.get_component_by_class(unreal.StaticMeshComponent)
if mesh_comp:
cube = unreal.load_asset("/Engine/BasicShapes/Cube")
if cube:
mesh_comp.set_static_mesh(cube)
return floor
def create_player_start(actor_subsystem):
"""Create PlayerStart for PIE spawn point."""
player_start = actor_subsystem.spawn_actor_from_class(
unreal.PlayerStart, unreal.Vector(0, 0, 100), unreal.Rotator(0, 0, 0)
)
player_start.set_actor_label("PlayerStart")
return player_start
def create_characters(actor_subsystem, count=2):
"""Create multiple BP_ThirdPersonCharacter for tracing and fuzzy matching tests."""
bp_asset = unreal.load_asset(BP_CHARACTER_PATH)
if not bp_asset:
print(f"Warning: Could not load blueprint: {BP_CHARACTER_PATH}")
return []
# Get the generated class from the blueprint
bp_class = bp_asset.generated_class()
if not bp_class:
print(f"Warning: Blueprint has no generated class: {BP_CHARACTER_PATH}")
return []
characters = []
for i in range(count):
character = actor_subsystem.spawn_actor_from_class(
bp_class,
unreal.Vector(300 + i * 200, i * 100, 100),
unreal.Rotator(0, 180, 0), # Face origin
)
if character:
character.set_actor_label(f"BP_ThirdPersonCharacter_{i}")
characters.append(character)
return characters
def create_test_cubes(actor_subsystem, count=3):
"""Create multiple StaticMeshActors for fuzzy matching tests."""
cubes = []
cube_mesh = unreal.load_asset("/Engine/BasicShapes/Cube")
for i in range(count):
mesh_actor = actor_subsystem.spawn_actor_from_class(
unreal.StaticMeshActor, unreal.Vector(i * 200 - 200, -500, 50), unreal.Rotator(0, 0, 0)
)
mesh_actor.set_actor_label(f"TestCube_{i}")
# Set mesh
mesh_comp = mesh_actor.get_component_by_class(unreal.StaticMeshComponent)
if mesh_comp and cube_mesh:
mesh_comp.set_static_mesh(cube_mesh)
cubes.append(mesh_actor)
return cubes
def create_lighting(actor_subsystem):
"""Create basic lighting for the level."""
# Directional Light (Sun)
dir_light = actor_subsystem.spawn_actor_from_class(
unreal.DirectionalLight,
unreal.Vector(0, 0, 1000),
unreal.Rotator(-45, -30, 0), # Angled for realistic sun position
)
dir_light.set_actor_label("DirectionalLight")
# Configure directional light for atmosphere
light_comp = dir_light.get_component_by_class(unreal.DirectionalLightComponent)
if light_comp:
light_comp.set_editor_property("intensity", 10.0)
light_comp.set_editor_property("light_color", unreal.Color(255, 244, 214, 255))
# Enable atmosphere sun light for sky atmosphere interaction
light_comp.set_editor_property("atmosphere_sun_light", True)
# Sky Light (captures sky for ambient lighting)
sky_light = actor_subsystem.spawn_actor_from_class(
unreal.SkyLight, unreal.Vector(0, 0, 500), unreal.Rotator(0, 0, 0)
)
sky_light.set_actor_label("SkyLight")
# Configure sky light to capture from sky atmosphere
skylight_comp = sky_light.get_component_by_class(unreal.SkyLightComponent)
if skylight_comp:
skylight_comp.set_editor_property("real_time_capture", True)
return dir_light, sky_light
def create_sky_atmosphere(actor_subsystem):
"""Create sky atmosphere for realistic blue sky."""
sky_atmo = actor_subsystem.spawn_actor_from_class(
unreal.SkyAtmosphere, unreal.Vector(0, 0, 0), unreal.Rotator(0, 0, 0)
)
sky_atmo.set_actor_label("SkyAtmosphere")
return sky_atmo
def create_volumetric_cloud(actor_subsystem):
"""Create volumetric clouds for white cloud effect."""
vol_cloud = actor_subsystem.spawn_actor_from_class(
unreal.VolumetricCloud, unreal.Vector(0, 0, 0), unreal.Rotator(0, 0, 0)
)
vol_cloud.set_actor_label("VolumetricCloud")
return vol_cloud
def create_exponential_height_fog(actor_subsystem):
"""Create exponential height fog for atmospheric depth."""
fog = actor_subsystem.spawn_actor_from_class(
unreal.ExponentialHeightFog, unreal.Vector(0, 0, 100), unreal.Rotator(0, 0, 0)
)
fog.set_actor_label("ExponentialHeightFog")
# Configure fog for subtle atmospheric effect
fog_comp = fog.get_component_by_class(unreal.ExponentialHeightFogComponent)
if fog_comp:
fog_comp.set_editor_property("fog_density", 0.005)
fog_comp.set_editor_property("fog_height_falloff", 0.2)
# Note: property is fog_inscattering_luminance, not fog_inscattering_color
fog_comp.set_editor_property(
"fog_inscattering_luminance", unreal.LinearColor(0.45, 0.6, 0.85, 1.0)
)
# Note: property is enable_volumetric_fog, not volumetric_fog
fog_comp.set_editor_property("enable_volumetric_fog", True)
return fog
def create_physics_objects(actor_subsystem):
"""Create physics-enabled objects that fall when game starts.
Left side (Y=-150): Single sphere
Right side (Y=150): Stacked chain of 3 objects (bar + cube + sphere)
"""
physics_objects = []
cube_mesh = unreal.load_asset("/Engine/BasicShapes/Cube")
sphere_mesh = unreal.load_asset("/Engine/BasicShapes/Sphere")
# === Left side: Single sphere ===
sphere_actor = actor_subsystem.spawn_actor_from_class(
unreal.StaticMeshActor,
unreal.Vector(0, -150, 500), # Left of center, suspended in air
unreal.Rotator(0, 0, 0),
)
sphere_actor.set_actor_label("PhysicsSphere")
sphere_mesh_comp = sphere_actor.get_component_by_class(unreal.StaticMeshComponent)
if sphere_mesh_comp:
if sphere_mesh:
sphere_mesh_comp.set_static_mesh(sphere_mesh)
sphere_mesh_comp.set_mobility(unreal.ComponentMobility.MOVABLE)
sphere_mesh_comp.set_simulate_physics(True)
sphere_mesh_comp.set_collision_enabled(unreal.CollisionEnabled.QUERY_AND_PHYSICS)
physics_objects.append(sphere_actor)
# === Right side: Stacked chain of 3 objects ===
# Layout (from bottom to top at Y=150):
# 1. PhysicsBar: vertical bar (50x50x200), center at Z=600
# 2. PhysicsCube: cube (100x100x100), center at Z=750
# 3. PhysicsChainSphere: sphere (radius 50), center at Z=850
# 1. Bottom: Vertical bar (tall cube)
bar_actor = actor_subsystem.spawn_actor_from_class(
unreal.StaticMeshActor,
unreal.Vector(0, 150, 600), # Center of bar
unreal.Rotator(0, 0, 0),
)
bar_actor.set_actor_label("PhysicsBar")
bar_actor.set_actor_scale3d(unreal.Vector(0.5, 0.5, 2)) # 50x50x200
bar_mesh_comp = bar_actor.get_component_by_class(unreal.StaticMeshComponent)
if bar_mesh_comp:
if cube_mesh:
bar_mesh_comp.set_static_mesh(cube_mesh)
bar_mesh_comp.set_mobility(unreal.ComponentMobility.MOVABLE)
bar_mesh_comp.set_simulate_physics(True)
bar_mesh_comp.set_collision_enabled(unreal.CollisionEnabled.QUERY_AND_PHYSICS)
physics_objects.append(bar_actor)
# 2. Middle: Cube
cube_actor = actor_subsystem.spawn_actor_from_class(
unreal.StaticMeshActor,
unreal.Vector(0, 150, 750), # On top of bar
unreal.Rotator(0, 0, 0),
)
cube_actor.set_actor_label("PhysicsCube")
cube_mesh_comp = cube_actor.get_component_by_class(unreal.StaticMeshComponent)
if cube_mesh_comp:
if cube_mesh:
cube_mesh_comp.set_static_mesh(cube_mesh)
cube_mesh_comp.set_mobility(unreal.ComponentMobility.MOVABLE)
cube_mesh_comp.set_simulate_physics(True)
cube_mesh_comp.set_collision_enabled(unreal.CollisionEnabled.QUERY_AND_PHYSICS)
physics_objects.append(cube_actor)
# 3. Top: Sphere
chain_sphere_actor = actor_subsystem.spawn_actor_from_class(
unreal.StaticMeshActor,
unreal.Vector(0, 150, 850), # On top of cube
unreal.Rotator(0, 0, 0),
)
chain_sphere_actor.set_actor_label("PhysicsChainSphere")
chain_sphere_comp = chain_sphere_actor.get_component_by_class(unreal.StaticMeshComponent)
if chain_sphere_comp:
if sphere_mesh:
chain_sphere_comp.set_static_mesh(sphere_mesh)
chain_sphere_comp.set_mobility(unreal.ComponentMobility.MOVABLE)
chain_sphere_comp.set_simulate_physics(True)
chain_sphere_comp.set_collision_enabled(unreal.CollisionEnabled.QUERY_AND_PHYSICS)
physics_objects.append(chain_sphere_actor)
return physics_objects
def create_test_level():
"""Create and populate test level with all required actors."""
# Check if level already exists
if check_level_exists():
result = {
"success": True,
"level": LEVEL_PATH,
"created": False,
"message": "Level already exists",
}
print(f"__RESULT__{json.dumps(result)}")
return result
level_subsystem, actor_subsystem = get_subsystems()
# Create new level
# Note: new_level creates an empty level at the specified path
success = level_subsystem.new_level(LEVEL_PATH)
if not success:
result = {"success": False, "error": f"Failed to create level at {LEVEL_PATH}"}
print(f"__RESULT__{json.dumps(result)}")
return result
actors_created = []
# Create all actors
try:
# 1. Floor
floor = create_floor(actor_subsystem)
if floor:
actors_created.append("Floor")
# 2. PlayerStart
player_start = create_player_start(actor_subsystem)
if player_start:
actors_created.append("PlayerStart")
# 3. Multiple BP_ThirdPersonCharacter (for tracing and fuzzy matching)
characters = create_characters(actor_subsystem, 2)
actors_created.extend([f"BP_ThirdPersonCharacter_{i}" for i in range(len(characters))])
# 4. Multiple TestCubes (StaticMeshActors for fuzzy matching)
cubes = create_test_cubes(actor_subsystem, 3)
actors_created.extend([f"TestCube_{i}" for i in range(len(cubes))])
# 5. Lighting
dir_light, sky_light = create_lighting(actor_subsystem)
if dir_light:
actors_created.append("DirectionalLight")
if sky_light:
actors_created.append("SkyLight")
# 6. Sky Atmosphere (blue sky)
sky_atmo = create_sky_atmosphere(actor_subsystem)
if sky_atmo:
actors_created.append("SkyAtmosphere")
# 7. Volumetric Cloud (white clouds)
vol_cloud = create_volumetric_cloud(actor_subsystem)
if vol_cloud:
actors_created.append("VolumetricCloud")
# 8. Exponential Height Fog (atmospheric depth)
fog = create_exponential_height_fog(actor_subsystem)
if fog:
actors_created.append("ExponentialHeightFog")
# 9. Physics Objects (sphere and cube that fall when game starts)
physics_objects = create_physics_objects(actor_subsystem)
actors_created.extend([obj.get_actor_label() for obj in physics_objects if obj])
except Exception as e:
result = {
"success": False,
"error": f"Failed to create actors: {str(e)}",
"actors_created": actors_created,
}
print(f"__RESULT__{json.dumps(result)}")
return result
# Save the level
try:
level_subsystem.save_current_level()
except Exception as e:
result = {
"success": False,
"error": f"Failed to save level: {str(e)}",
"actors_created": actors_created,
}
print(f"__RESULT__{json.dumps(result)}")
return result
result = {
"success": True,
"level": LEVEL_PATH,
"created": True,
"actors_created": actors_created,
"message": f"Test level created with {len(actors_created)} actors",
}
print(f"__RESULT__{json.dumps(result)}")
return result
# Entry point when executed as script
if __name__ == "__main__":
create_test_level()