Tooling
This commit is contained in:
@@ -25,49 +25,11 @@ HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
sys.path.insert(0, os.path.join(HERE, ".pylib"))
|
||||
import heightmap_io # noqa: E402
|
||||
import rocky_meadows # noqa: E402
|
||||
from world_manifest import LAYER_FILES, load_manifest # noqa: E402
|
||||
|
||||
PACK = "/Game/Elite_RockyMeadows"
|
||||
LANDSCAPE_MATERIAL = f"{PACK}/Materials/M_Landscape_Main_Inst_RockyMeadows02"
|
||||
LAYER_INFOS = { # paint layer name -> the pack's layer info asset; the weightmap file comes from the manifest module
|
||||
"Base_Layer": f"{PACK}/Materials/Material_Layers/Base_Layer_LayerInfo",
|
||||
"Layer_02": f"{PACK}/Materials/Material_Layers/Layer_02_LayerInfo",
|
||||
"Layer_03": f"{PACK}/Materials/Material_Layers/Layer_03_LayerInfo",
|
||||
}
|
||||
SKYBOX_MESH = f"{PACK}/Materials/Skybox/Skybox_Mesh"
|
||||
SKYBOX_MATERIAL = f"{PACK}/Materials/Skybox/M_Skybox_Inst_RockyMeadows"
|
||||
CLOUD_SHADOWS = f"{PACK}/Materials/Light_Material/M_Cloud_Shadows_Inst02"
|
||||
SEA_MESH = "/Engine/BasicShapes/Plane"
|
||||
SEA_MATERIAL = "/Engine/EngineMaterials/WaterMaterial"
|
||||
|
||||
# The pack's Rocky_Meadows_01 demo map, as dump_level.py read it. Distances are scaled up where the demo's
|
||||
# 8 km scene would otherwise cut the effect short on a 14 km world.
|
||||
PACK_SUN = {
|
||||
"rotation": unreal.Rotator(roll=-51.273, pitch=-31.342, yaw=36.413), # keyword arguments: positional order is roll, pitch, yaw
|
||||
"intensity": 9.2368,
|
||||
"light_color": unreal.Color(r=223, g=245, b=255, a=255),
|
||||
"light_function_scale": unreal.Vector(1024.0, 1024.0, 1024.0),
|
||||
"light_function_fade_distance": 2000000.0, # the demo fades its cloud shadows out at 2 km; keep them to 20 km here
|
||||
"dynamic_shadow_distance_movable_light": 200000.0,
|
||||
"cascade_distribution_exponent": 3.0,
|
||||
"light_source_angle": 0.5357,
|
||||
"shadow_bias": 0.5,
|
||||
}
|
||||
PACK_SKY_LIGHT = {"intensity": 1.5, "lower_hemisphere_color": unreal.LinearColor(0.0, 0.0, 0.0, 1.0), "sky_distance_threshold": 150000.0}
|
||||
PACK_FOG = {
|
||||
"fog_density": 0.027143,
|
||||
"fog_height_falloff": 0.039076,
|
||||
"fog_inscattering_luminance": unreal.LinearColor(0.238715, 0.329426, 0.458333, 1.0),
|
||||
"directional_inscattering_luminance": unreal.LinearColor(0.25, 0.20832, 0.154948, 1.0),
|
||||
"directional_inscattering_exponent": 4.0,
|
||||
"directional_inscattering_start_distance": 10000.0,
|
||||
}
|
||||
PACK_POST_PROCESS = { # FPostProcessSettings field -> value; the override flag of each is set alongside
|
||||
"auto_exposure_min_brightness": 1.0,
|
||||
"auto_exposure_max_brightness": 1.0,
|
||||
"auto_exposure_bias": 0.263034,
|
||||
"color_saturation": unreal.Vector4(1.0, 1.0, 1.0, 1.25),
|
||||
}
|
||||
# The pack's kit — its landscape material, layer infos, sun, skybox, fog and grade — is in rocky_meadows.py,
|
||||
# shared with create_region_world.py so one set of numbers dresses both worlds.
|
||||
|
||||
manifest = load_manifest()
|
||||
LEVEL_PATH = manifest.level
|
||||
@@ -78,27 +40,6 @@ editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
||||
asset_lib = unreal.EditorAssetLibrary
|
||||
|
||||
|
||||
def load_or_raise(asset_path):
|
||||
asset = unreal.load_asset(asset_path)
|
||||
if not asset:
|
||||
raise RuntimeError(f"{asset_path} not found; is the pack in Content/?")
|
||||
return asset
|
||||
|
||||
|
||||
def set_properties(target, values):
|
||||
for name, value in values.items():
|
||||
target.set_editor_property(name, value)
|
||||
|
||||
|
||||
def keep_always_loaded(actor):
|
||||
"""World partition streams actors by their bounds; the sky dome and the sea are the whole world and must
|
||||
not stream at all."""
|
||||
try:
|
||||
actor.set_editor_property("is_spatially_loaded", False)
|
||||
except Exception as error:
|
||||
unreal.log_warning(f"{actor.get_actor_label()}: could not clear is_spatially_loaded ({error}); it will stream by bounds")
|
||||
|
||||
|
||||
def ensure_heightmaps():
|
||||
files = [manifest.heightmap_path] + [manifest.weightmap_path(name) for name in LAYER_FILES]
|
||||
reason = None
|
||||
@@ -185,13 +126,8 @@ def sweep_stale_actor_packages():
|
||||
|
||||
|
||||
def create_landscape():
|
||||
material = load_or_raise(LANDSCAPE_MATERIAL)
|
||||
weightmaps = []
|
||||
for layer_name, asset_path in LAYER_INFOS.items():
|
||||
entry = unreal.LandscapeAuthoringWeightmap()
|
||||
entry.set_editor_property("layer_info", load_or_raise(asset_path))
|
||||
entry.set_editor_property("file", manifest.weightmap_path(layer_name))
|
||||
weightmaps.append(entry)
|
||||
material = rocky_meadows.load_or_raise(rocky_meadows.LANDSCAPE_MATERIAL)
|
||||
weightmaps = rocky_meadows.weightmap_entries(manifest.weightmap_path)
|
||||
|
||||
world = editor_subsystem.get_editor_world()
|
||||
unreal.log(f"landscape: {manifest.describe()}")
|
||||
@@ -217,59 +153,6 @@ def ground_height_at(x, y, fallback):
|
||||
return fallback
|
||||
|
||||
|
||||
def dress_with_rocky_meadows():
|
||||
"""The pack's sky, sun, fog and grade, so L_World reads like its demo maps."""
|
||||
sun = spawn(unreal.DirectionalLight, "World_Sun", unreal.Vector(0, 0, 50000), PACK_SUN["rotation"])
|
||||
light = sun.light_component
|
||||
light.set_editor_property("mobility", unreal.ComponentMobility.MOVABLE)
|
||||
set_properties(light, {k: v for k, v in PACK_SUN.items() if k != "rotation"})
|
||||
light.set_editor_property("light_function_material", load_or_raise(CLOUD_SHADOWS))
|
||||
|
||||
sky_light = spawn(unreal.SkyLight, "World_SkyLight", unreal.Vector(0, 0, 50000))
|
||||
sky_light.light_component.set_editor_property("mobility", unreal.ComponentMobility.MOVABLE)
|
||||
set_properties(sky_light.light_component, PACK_SKY_LIGHT)
|
||||
|
||||
# The pack's skybox is a textured dome mesh, not a sky atmosphere. Scale it so the whole world sits inside
|
||||
# with room to spare, and sink its centre so the dome's equator is below the sea from any shore.
|
||||
mesh = load_or_raise(SKYBOX_MESH)
|
||||
native_radius = max(mesh.get_bounds().sphere_radius, 1.0)
|
||||
radius = manifest.side_m * 100.0 * 1.1
|
||||
skybox = spawn(unreal.StaticMeshActor, "World_Skybox", unreal.Vector(0.0, 0.0, -radius * 0.25))
|
||||
skybox.static_mesh_component.set_editor_property("mobility", unreal.ComponentMobility.MOVABLE)
|
||||
skybox.static_mesh_component.set_static_mesh(mesh)
|
||||
skybox.static_mesh_component.set_material(0, load_or_raise(SKYBOX_MATERIAL))
|
||||
skybox.static_mesh_component.set_editor_property("cast_shadow", False)
|
||||
skybox.static_mesh_component.set_collision_enabled(unreal.CollisionEnabled.NO_COLLISION)
|
||||
skybox.set_actor_scale3d(unreal.Vector(radius / native_radius, radius / native_radius, radius / native_radius))
|
||||
keep_always_loaded(skybox)
|
||||
unreal.log(f"skybox dome radius {radius / 100000:.1f} km (mesh radius {native_radius:g} cm, scale {radius / native_radius:g})")
|
||||
|
||||
fog = spawn(unreal.ExponentialHeightFog, "World_Fog", unreal.Vector(0.0, 0.0, manifest.sea_level_z_cm))
|
||||
set_properties(fog.component, PACK_FOG)
|
||||
|
||||
post = spawn(unreal.PostProcessVolume, "World_PostProcess")
|
||||
post.set_editor_property("unbound", True)
|
||||
settings = post.get_editor_property("settings")
|
||||
for field, value in PACK_POST_PROCESS.items():
|
||||
settings.set_editor_property(f"override_{field}", True)
|
||||
settings.set_editor_property(field, value)
|
||||
post.set_editor_property("settings", settings)
|
||||
|
||||
|
||||
def ensure_sea():
|
||||
"""A flat plane at sea level with the engine's water material: enough to read as sea until a water body
|
||||
replaces it. It keeps collision so a walk off the coast is a walk, not a fall to the sea floor."""
|
||||
mesh = load_or_raise(SEA_MESH)
|
||||
material = unreal.load_asset(SEA_MATERIAL) or load_or_raise("/Engine/BasicShapes/BasicShapeMaterial")
|
||||
side = manifest.side_m * 100.0 * 1.5 / 100.0 # the plane is 100 cm; cover the world and the sea beyond its edge
|
||||
sea = spawn(unreal.StaticMeshActor, "World_Sea_Proto", unreal.Vector(0.0, 0.0, manifest.sea_level_z_cm))
|
||||
sea.static_mesh_component.set_static_mesh(mesh)
|
||||
sea.static_mesh_component.set_material(0, material)
|
||||
sea.static_mesh_component.set_editor_property("cast_shadow", False)
|
||||
sea.set_actor_scale3d(unreal.Vector(side, side, 1.0))
|
||||
keep_always_loaded(sea)
|
||||
|
||||
|
||||
def ensure_player_starts():
|
||||
# The heightmap has a flat pad at its centre; two starts so two PIE clients spawn without a warning.
|
||||
fallback = manifest.sea_level_z_cm + 15000.0
|
||||
@@ -282,8 +165,8 @@ def main():
|
||||
ensure_heightmaps()
|
||||
prepare_level()
|
||||
landscape = create_landscape()
|
||||
dress_with_rocky_meadows()
|
||||
ensure_sea()
|
||||
rocky_meadows.dress(spawn, manifest)
|
||||
rocky_meadows.ensure_sea(spawn, manifest)
|
||||
ensure_player_starts()
|
||||
if not level_subsystem.save_current_level():
|
||||
raise RuntimeError(f"saving {LEVEL_PATH} failed; see the log above")
|
||||
|
||||
Reference in New Issue
Block a user