Add Ground Zero natural environment pass
This commit is contained in:
@@ -22,6 +22,57 @@ FOLIAGE_MESHES = {
|
||||
"shrub": "/Game/LevelPrototyping/Meshes/SM_Cube",
|
||||
"grass": "/Game/LevelPrototyping/Meshes/SM_Cylinder",
|
||||
}
|
||||
MATERIAL_FOLDER = "/Game/Agrarian/Materials"
|
||||
ENVIRONMENT_MATERIALS = {
|
||||
"terrain": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Terrain_CoastalScrub",
|
||||
"color": unreal.LinearColor(0.28, 0.24, 0.16, 1.0),
|
||||
"roughness": 0.92,
|
||||
},
|
||||
"tree": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Tree_CoastalOak",
|
||||
"color": unreal.LinearColor(0.18, 0.31, 0.16, 1.0),
|
||||
"roughness": 0.88,
|
||||
},
|
||||
"shrub": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Shrub_CoyoteBrush",
|
||||
"color": unreal.LinearColor(0.31, 0.39, 0.20, 1.0),
|
||||
"roughness": 0.9,
|
||||
},
|
||||
"grass": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Grass_DryCoastal",
|
||||
"color": unreal.LinearColor(0.47, 0.42, 0.23, 1.0),
|
||||
"roughness": 0.95,
|
||||
},
|
||||
"wood_resource": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Wood_Resource",
|
||||
"color": unreal.LinearColor(0.26, 0.16, 0.08, 1.0),
|
||||
"roughness": 0.86,
|
||||
},
|
||||
"fiber_resource": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Fiber_Resource",
|
||||
"color": unreal.LinearColor(0.55, 0.49, 0.28, 1.0),
|
||||
"roughness": 0.93,
|
||||
},
|
||||
"stone_resource": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_Stone_Sandstone",
|
||||
"color": unreal.LinearColor(0.43, 0.40, 0.35, 1.0),
|
||||
"roughness": 0.97,
|
||||
},
|
||||
"fresh_water": {
|
||||
"path": f"{MATERIAL_FOLDER}/M_AGR_GZ_FreshWater",
|
||||
"color": unreal.LinearColor(0.08, 0.28, 0.38, 1.0),
|
||||
"roughness": 0.35,
|
||||
},
|
||||
}
|
||||
RESOURCE_MATERIAL_BY_LABEL_PREFIX = {
|
||||
"AGR_GZ_Wood": "wood_resource",
|
||||
"AGR_GZ_Fiber": "fiber_resource",
|
||||
"AGR_GZ_Stone": "stone_resource",
|
||||
"AGR_DemoWoodResource": "wood_resource",
|
||||
"AGR_DemoFiberResource": "fiber_resource",
|
||||
"AGR_GZ_FreshWaterSource": "fresh_water",
|
||||
}
|
||||
|
||||
|
||||
DEMO_ACTORS = [
|
||||
@@ -245,6 +296,75 @@ def load_required_asset(path):
|
||||
return asset
|
||||
|
||||
|
||||
def ensure_environment_materials():
|
||||
if not unreal.EditorAssetLibrary.does_directory_exist(MATERIAL_FOLDER):
|
||||
unreal.EditorAssetLibrary.make_directory(MATERIAL_FOLDER)
|
||||
|
||||
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
||||
created_or_loaded = {}
|
||||
for key, spec in ENVIRONMENT_MATERIALS.items():
|
||||
material = unreal.EditorAssetLibrary.load_asset(spec["path"])
|
||||
if not material:
|
||||
asset_name = spec["path"].rsplit("/", 1)[-1]
|
||||
material = asset_tools.create_asset(asset_name, MATERIAL_FOLDER, unreal.Material, unreal.MaterialFactoryNew())
|
||||
if not material:
|
||||
raise RuntimeError(f"Could not create material: {spec['path']}")
|
||||
|
||||
base_color = unreal.MaterialEditingLibrary.create_material_expression(
|
||||
material, unreal.MaterialExpressionConstant3Vector, -420, -120
|
||||
)
|
||||
base_color.set_editor_property("constant", spec["color"])
|
||||
unreal.MaterialEditingLibrary.connect_material_property(
|
||||
base_color, "", unreal.MaterialProperty.MP_BASE_COLOR
|
||||
)
|
||||
|
||||
roughness = unreal.MaterialEditingLibrary.create_material_expression(
|
||||
material, unreal.MaterialExpressionConstant, -420, 80
|
||||
)
|
||||
roughness.set_editor_property("r", spec["roughness"])
|
||||
unreal.MaterialEditingLibrary.connect_material_property(
|
||||
roughness, "", unreal.MaterialProperty.MP_ROUGHNESS
|
||||
)
|
||||
|
||||
unreal.MaterialEditingLibrary.recompile_material(material)
|
||||
unreal.EditorAssetLibrary.save_asset(spec["path"])
|
||||
unreal.log(f"Created Ground Zero environment material: {spec['path']}")
|
||||
created_or_loaded[key] = material
|
||||
|
||||
return created_or_loaded
|
||||
|
||||
|
||||
def apply_material_to_actor_meshes(actor, material):
|
||||
applied_count = 0
|
||||
for component in actor.get_components_by_class(unreal.StaticMeshComponent):
|
||||
slot_count = component.get_num_materials()
|
||||
if slot_count == 0:
|
||||
component.set_material(0, material)
|
||||
applied_count += 1
|
||||
continue
|
||||
for slot_index in range(slot_count):
|
||||
component.set_material(slot_index, material)
|
||||
applied_count += 1
|
||||
return applied_count
|
||||
|
||||
|
||||
def material_key_for_actor_label(label):
|
||||
for prefix, material_key in RESOURCE_MATERIAL_BY_LABEL_PREFIX.items():
|
||||
if label.startswith(prefix):
|
||||
return material_key
|
||||
return None
|
||||
|
||||
|
||||
def apply_landscape_material(material):
|
||||
applied = 0
|
||||
for actor in unreal.EditorLevelLibrary.get_all_level_actors():
|
||||
if isinstance(actor, unreal.Landscape):
|
||||
actor.set_editor_property("landscape_material", material)
|
||||
applied += 1
|
||||
unreal.log(f"Applied Ground Zero terrain material to {applied} landscape actor(s).")
|
||||
return applied
|
||||
|
||||
|
||||
def load_heightmap():
|
||||
raw = HEIGHTMAP_PATH.read_bytes()
|
||||
expected_bytes = LANDSCAPE_SIZE * LANDSCAPE_SIZE * 2
|
||||
@@ -295,6 +415,18 @@ def configure_foliage_meshes(foliage_actor):
|
||||
component.set_editor_property("static_mesh", mesh)
|
||||
|
||||
|
||||
def apply_foliage_materials(foliage_actor, materials):
|
||||
component_materials = {
|
||||
"tree_instances": materials["tree"],
|
||||
"shrub_instances": materials["shrub"],
|
||||
"grass_instances": materials["grass"],
|
||||
}
|
||||
|
||||
for property_name, material in component_materials.items():
|
||||
component = foliage_actor.get_editor_property(property_name)
|
||||
component.set_material(0, material)
|
||||
|
||||
|
||||
def choose_foliage_points(height_values, zone, reserved_points, existing_points):
|
||||
rng = random.Random(FOLIAGE_RANDOM_SEED + len(existing_points) + int(zone["count"]))
|
||||
chosen = []
|
||||
@@ -325,7 +457,7 @@ def choose_foliage_points(height_values, zone, reserved_points, existing_points)
|
||||
return chosen
|
||||
|
||||
|
||||
def spawn_foliage_actor(height_values):
|
||||
def spawn_foliage_actor(height_values, materials):
|
||||
reserved_points = [
|
||||
spec["location_xy"]
|
||||
for spec in DEMO_ACTORS
|
||||
@@ -343,6 +475,7 @@ def spawn_foliage_actor(height_values):
|
||||
|
||||
set_actor_label(foliage_actor, FOLIAGE_LABEL)
|
||||
configure_foliage_meshes(foliage_actor)
|
||||
apply_foliage_materials(foliage_actor, materials)
|
||||
foliage_actor.clear_foliage()
|
||||
|
||||
existing_points = []
|
||||
@@ -375,7 +508,7 @@ def spawn_foliage_actor(height_values):
|
||||
return foliage_actor
|
||||
|
||||
|
||||
def spawn_demo_actor(spec, height_values):
|
||||
def spawn_demo_actor(spec, height_values, materials):
|
||||
location_xy = spec["location_xy"]
|
||||
z = spec.get("fixed_z")
|
||||
if z is None:
|
||||
@@ -395,6 +528,9 @@ def spawn_demo_actor(spec, height_values):
|
||||
raise RuntimeError(f"Could not spawn {spec['label']}")
|
||||
|
||||
set_actor_label(actor, spec["label"])
|
||||
material_key = material_key_for_actor_label(spec["label"])
|
||||
if material_key:
|
||||
apply_material_to_actor_meshes(actor, materials[material_key])
|
||||
unreal.log(f"Placed {spec['label']} at {actor.get_actor_location()}")
|
||||
return actor
|
||||
|
||||
@@ -410,14 +546,16 @@ def main():
|
||||
labels.add(FOLIAGE_LABEL)
|
||||
remove_existing_demo_actors(labels)
|
||||
|
||||
materials = ensure_environment_materials()
|
||||
apply_landscape_material(materials["terrain"])
|
||||
height_values = load_heightmap()
|
||||
spawn_foliage_actor(height_values)
|
||||
spawn_foliage_actor(height_values, materials)
|
||||
for spec in BIOME_RESOURCE_ACTORS:
|
||||
spawn_demo_actor(spec, height_values)
|
||||
spawn_demo_actor(spec, height_values, materials)
|
||||
for spec in WATER_SOURCE_ACTORS:
|
||||
spawn_demo_actor(spec, height_values)
|
||||
spawn_demo_actor(spec, height_values, materials)
|
||||
for spec in DEMO_ACTORS:
|
||||
spawn_demo_actor(spec, height_values)
|
||||
spawn_demo_actor(spec, height_values, materials)
|
||||
|
||||
unreal.EditorLevelLibrary.save_current_level()
|
||||
unreal.log("Ground Zero demo map setup complete.")
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import unreal
|
||||
|
||||
|
||||
MAP_PATH = "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test"
|
||||
FOLIAGE_LABEL = "AGR_GroundZeroFoliage_FirstPass"
|
||||
MATERIALS = {
|
||||
"terrain": "/Game/Agrarian/Materials/M_AGR_GZ_Terrain_CoastalScrub",
|
||||
"tree": "/Game/Agrarian/Materials/M_AGR_GZ_Tree_CoastalOak",
|
||||
"shrub": "/Game/Agrarian/Materials/M_AGR_GZ_Shrub_CoyoteBrush",
|
||||
"grass": "/Game/Agrarian/Materials/M_AGR_GZ_Grass_DryCoastal",
|
||||
"wood_resource": "/Game/Agrarian/Materials/M_AGR_GZ_Wood_Resource",
|
||||
"fiber_resource": "/Game/Agrarian/Materials/M_AGR_GZ_Fiber_Resource",
|
||||
"stone_resource": "/Game/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone",
|
||||
"fresh_water": "/Game/Agrarian/Materials/M_AGR_GZ_FreshWater",
|
||||
}
|
||||
EXPECTED_FOLIAGE_COUNTS = {
|
||||
"trees": 42,
|
||||
"shrubs": 96,
|
||||
"grass": 180,
|
||||
}
|
||||
RESOURCE_MATERIALS = {
|
||||
"AGR_GZ_Wood": "wood_resource",
|
||||
"AGR_GZ_Fiber": "fiber_resource",
|
||||
"AGR_GZ_Stone": "stone_resource",
|
||||
"AGR_DemoWoodResource": "wood_resource",
|
||||
"AGR_DemoFiberResource": "fiber_resource",
|
||||
"AGR_GZ_FreshWaterSource": "fresh_water",
|
||||
}
|
||||
|
||||
|
||||
def get_actor_label(actor):
|
||||
try:
|
||||
return actor.get_actor_label()
|
||||
except Exception:
|
||||
return actor.get_name()
|
||||
|
||||
|
||||
def material_path(material):
|
||||
if not material:
|
||||
return ""
|
||||
return material.get_path_name().split(".", 1)[0]
|
||||
|
||||
|
||||
def material_key_for_label(label):
|
||||
for prefix, material_key in RESOURCE_MATERIALS.items():
|
||||
if label.startswith(prefix):
|
||||
return material_key
|
||||
return None
|
||||
|
||||
|
||||
def assert_asset(path):
|
||||
asset = unreal.EditorAssetLibrary.load_asset(path)
|
||||
if not asset:
|
||||
raise RuntimeError(f"Missing required environment material: {path}")
|
||||
return asset
|
||||
|
||||
|
||||
def main():
|
||||
if not unreal.EditorLevelLibrary.load_level(MAP_PATH):
|
||||
raise RuntimeError(f"Could not load map: {MAP_PATH}")
|
||||
|
||||
materials = {key: assert_asset(path) for key, path in MATERIALS.items()}
|
||||
actors = unreal.EditorLevelLibrary.get_all_level_actors()
|
||||
failures = []
|
||||
|
||||
landscapes = [actor for actor in actors if isinstance(actor, unreal.Landscape)]
|
||||
if not landscapes:
|
||||
failures.append("no landscape actor found")
|
||||
for landscape in landscapes:
|
||||
assigned = material_path(landscape.get_editor_property("landscape_material"))
|
||||
expected = MATERIALS["terrain"]
|
||||
if assigned != expected:
|
||||
failures.append(f"landscape material expected {expected}, got {assigned}")
|
||||
|
||||
foliage_actors = [actor for actor in actors if get_actor_label(actor) == FOLIAGE_LABEL]
|
||||
if len(foliage_actors) != 1:
|
||||
failures.append(f"expected one {FOLIAGE_LABEL}, found {len(foliage_actors)}")
|
||||
else:
|
||||
foliage = foliage_actors[0]
|
||||
counts = {
|
||||
"trees": foliage.get_tree_instance_count(),
|
||||
"shrubs": foliage.get_shrub_instance_count(),
|
||||
"grass": foliage.get_grass_instance_count(),
|
||||
}
|
||||
for key, expected_count in EXPECTED_FOLIAGE_COUNTS.items():
|
||||
if counts[key] != expected_count:
|
||||
failures.append(f"{key} expected {expected_count}, got {counts[key]}")
|
||||
|
||||
component_expectations = {
|
||||
"tree_instances": "tree",
|
||||
"shrub_instances": "shrub",
|
||||
"grass_instances": "grass",
|
||||
}
|
||||
for property_name, material_key in component_expectations.items():
|
||||
component = foliage.get_editor_property(property_name)
|
||||
assigned = material_path(component.get_material(0))
|
||||
expected = MATERIALS[material_key]
|
||||
if assigned != expected:
|
||||
failures.append(f"{property_name} material expected {expected}, got {assigned}")
|
||||
|
||||
checked_resource_actors = 0
|
||||
for actor in actors:
|
||||
label = get_actor_label(actor)
|
||||
material_key = material_key_for_label(label)
|
||||
if not material_key:
|
||||
continue
|
||||
|
||||
checked_resource_actors += 1
|
||||
expected = MATERIALS[material_key]
|
||||
mesh_components = actor.get_components_by_class(unreal.StaticMeshComponent)
|
||||
if not mesh_components:
|
||||
failures.append(f"{label} has no static mesh component for material assignment")
|
||||
continue
|
||||
assigned_any = any(material_path(component.get_material(0)) == expected for component in mesh_components)
|
||||
if not assigned_any:
|
||||
failures.append(f"{label} did not use expected material {expected}")
|
||||
|
||||
if checked_resource_actors < 10:
|
||||
failures.append(f"expected at least 10 dressed resource/water actors, checked {checked_resource_actors}")
|
||||
|
||||
docs = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()) + "Docs/Terrain/GroundZeroNaturalEnvironmentPass.md"
|
||||
roadmap = unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_dir()) + "AGRARIAN_DEVELOPMENT_ROADMAP.md"
|
||||
for path, snippet in [
|
||||
(docs, "MVP natural environment pass"),
|
||||
(roadmap, "[x] Replace grey-box environment presentation with an MVP natural environment pass"),
|
||||
]:
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
text = handle.read()
|
||||
if snippet not in text:
|
||||
failures.append(f"{path} missing {snippet}")
|
||||
|
||||
if failures:
|
||||
raise RuntimeError("Ground Zero natural environment verification failed: " + "; ".join(failures))
|
||||
|
||||
unreal.log(
|
||||
"Ground Zero natural environment verification complete: "
|
||||
f"{len(materials)} materials, {len(landscapes)} landscape(s), {checked_resource_actors} dressed resource/water actor(s)."
|
||||
)
|
||||
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user