Salty.uproject (UE 5.8) from the Third Person template with class redirects, the SaltyCore and Salty modules, the three build targets, USaltyAssetManager calling InitGlobalData, Config/Tags with the 23 root namespaces, Scripts/run-tests.sh, build.sh and Authoring/create_gym.py, L_Gym, Git LFS attributes and the placeholder test. Template variants kept as reference (D-41). The four Fab packs stay out of the repository for now (~10 GB). The editor serves the engine MCP plugin on 127.0.0.1:8000 through DefaultEditorPerProjectUserSettings.ini. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
85 lines
3.4 KiB
Python
85 lines
3.4 KiB
Python
"""Creates /Game/Maps/L_Gym if it does not exist: a flat floor, a player start and daylight. Idempotent.
|
|
|
|
Step 1 wants an empty gym; step 3 extends this script with the greybox sections from Movement.md (stairs, slopes,
|
|
gaps, ledges, doorways, beams, corridor, surfaces, arena). Run headless:
|
|
UnrealEditor-Cmd.exe Salty.uproject -run=pythonscript -script=Scripts/Authoring/create_gym.py
|
|
or from the editor's Python console. Never hand-edit the .umap; rerun this.
|
|
"""
|
|
import unreal
|
|
|
|
LEVEL_PATH = "/Game/Maps/L_Gym"
|
|
FLOOR_HALF_SIZE_CM = 5000.0 # a 100 m square; the cube mesh is 100 cm, so scale = size / 100
|
|
CUBE = "/Engine/BasicShapes/Cube.Cube"
|
|
FLOOR_MATERIAL = "/Game/LevelPrototyping/Materials/MI_PrototypeGrid_Gray.MI_PrototypeGrid_Gray"
|
|
|
|
level_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
asset_lib = unreal.EditorAssetLibrary
|
|
|
|
|
|
def spawn(actor_class, label, location=unreal.Vector(0, 0, 0), rotation=unreal.Rotator(0, 0, 0)):
|
|
actor = actor_subsystem.spawn_actor_from_class(actor_class, location, rotation)
|
|
actor.set_actor_label(label)
|
|
return actor
|
|
|
|
|
|
def find_actor(label):
|
|
for actor in actor_subsystem.get_all_level_actors():
|
|
if actor.get_actor_label() == label:
|
|
return actor
|
|
return None
|
|
|
|
|
|
def ensure_floor():
|
|
if find_actor("Gym_Floor"):
|
|
return
|
|
floor = spawn(unreal.StaticMeshActor, "Gym_Floor", unreal.Vector(0, 0, -50))
|
|
mesh = unreal.load_asset(CUBE)
|
|
component = floor.static_mesh_component
|
|
component.set_editor_property("mobility", unreal.ComponentMobility.STATIC)
|
|
component.set_static_mesh(mesh)
|
|
scale = FLOOR_HALF_SIZE_CM * 2.0 / 100.0
|
|
floor.set_actor_scale3d(unreal.Vector(scale, scale, 1.0))
|
|
material = unreal.load_asset(FLOOR_MATERIAL) if asset_lib.does_asset_exist(FLOOR_MATERIAL) else None
|
|
if material:
|
|
component.set_material(0, material)
|
|
|
|
|
|
def ensure_daylight():
|
|
if not find_actor("Gym_Sun"):
|
|
sun = spawn(unreal.DirectionalLight, "Gym_Sun", unreal.Vector(0, 0, 500), unreal.Rotator(-45, 30, 0))
|
|
sun.light_component.set_editor_property("intensity", 8.0)
|
|
sun.light_component.set_editor_property("atmosphere_sun_light", True)
|
|
if not find_actor("Gym_Sky"):
|
|
spawn(unreal.SkyAtmosphere, "Gym_Sky", unreal.Vector(0, 0, 0))
|
|
if not find_actor("Gym_SkyLight"):
|
|
sky_light = spawn(unreal.SkyLight, "Gym_SkyLight", unreal.Vector(0, 0, 500))
|
|
sky_light.light_component.set_editor_property("real_time_capture", True)
|
|
if not find_actor("Gym_Fog"):
|
|
spawn(unreal.ExponentialHeightFog, "Gym_Fog", unreal.Vector(0, 0, 0))
|
|
|
|
|
|
def ensure_player_starts():
|
|
# Two starts so two PIE clients on a dedicated server both spawn without a collision warning.
|
|
for index, y in enumerate((-150, 150)):
|
|
label = f"Gym_PlayerStart_{index}"
|
|
if not find_actor(label):
|
|
spawn(unreal.PlayerStart, label, unreal.Vector(0, y, 100))
|
|
|
|
|
|
def main():
|
|
if asset_lib.does_asset_exist(LEVEL_PATH):
|
|
unreal.log(f"{LEVEL_PATH} exists; loading it to top up missing actors")
|
|
level_subsystem.load_level(LEVEL_PATH)
|
|
else:
|
|
unreal.log(f"creating {LEVEL_PATH}")
|
|
level_subsystem.new_level(LEVEL_PATH, False) # not world-partitioned: the gym is small and hand-laid
|
|
ensure_floor()
|
|
ensure_daylight()
|
|
ensure_player_starts()
|
|
level_subsystem.save_current_level()
|
|
unreal.log(f"{LEVEL_PATH} saved")
|
|
|
|
|
|
main()
|