Add Ground Zero environment asset variation

This commit is contained in:
2026-05-16 02:28:31 -07:00
parent 25ffbfc564
commit f78c552d30
6 changed files with 209 additions and 9 deletions
+133
View File
@@ -22,6 +22,13 @@ FOLIAGE_MESHES = {
"shrub": "/Game/LevelPrototyping/Meshes/SM_Cube",
"grass": "/Game/LevelPrototyping/Meshes/SM_Cylinder",
}
VARIATION_MESHES = {
"cube": "/Game/LevelPrototyping/Meshes/SM_Cube",
"chamfer_cube": "/Game/LevelPrototyping/Meshes/SM_ChamferCube",
"cylinder": "/Game/LevelPrototyping/Meshes/SM_Cylinder",
"quarter_cylinder": "/Game/LevelPrototyping/Meshes/SM_QuarterCylinder",
"plane": "/Game/LevelPrototyping/Meshes/SM_Plane",
}
MATERIAL_FOLDER = "/Game/Agrarian/Materials"
ENVIRONMENT_MATERIALS = {
"terrain": {
@@ -239,6 +246,108 @@ WATER_SOURCE_ACTORS = [
},
]
ENVIRONMENT_VARIATION_ACTORS = [
{
"label": "AGR_GZ_EnvVar_Tree_Canopy_01",
"mesh_key": "chamfer_cube",
"material_key": "tree",
"location_xy": unreal.Vector(-27500.0, 6900.0, 0.0),
"z_offset": 390.0,
"scale": unreal.Vector(2.5, 2.0, 1.6),
"rotation": unreal.Rotator(0.0, 32.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Tree_Trunk_01",
"mesh_key": "cylinder",
"material_key": "wood_resource",
"location_xy": unreal.Vector(-27500.0, 6900.0, 0.0),
"z_offset": 170.0,
"scale": unreal.Vector(0.35, 0.35, 3.6),
"rotation": unreal.Rotator(0.0, 32.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Tree_Canopy_02",
"mesh_key": "chamfer_cube",
"material_key": "tree",
"location_xy": unreal.Vector(17600.0, 31800.0, 0.0),
"z_offset": 430.0,
"scale": unreal.Vector(2.0, 2.8, 1.45),
"rotation": unreal.Rotator(0.0, -18.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Tree_Trunk_02",
"mesh_key": "cylinder",
"material_key": "wood_resource",
"location_xy": unreal.Vector(17600.0, 31800.0, 0.0),
"z_offset": 190.0,
"scale": unreal.Vector(0.3, 0.3, 4.1),
"rotation": unreal.Rotator(0.0, -18.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Bush_Rounded_01",
"mesh_key": "chamfer_cube",
"material_key": "shrub",
"location_xy": unreal.Vector(-33400.0, -15200.0, 0.0),
"z_offset": 70.0,
"scale": unreal.Vector(1.9, 1.4, 0.7),
"rotation": unreal.Rotator(0.0, 11.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Bush_Rounded_02",
"mesh_key": "chamfer_cube",
"material_key": "shrub",
"location_xy": unreal.Vector(30400.0, -3900.0, 0.0),
"z_offset": 75.0,
"scale": unreal.Vector(1.45, 2.1, 0.85),
"rotation": unreal.Rotator(0.0, 73.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Grass_Mat_01",
"mesh_key": "plane",
"material_key": "grass",
"location_xy": unreal.Vector(8200.0, -17100.0, 0.0),
"z_offset": 14.0,
"scale": unreal.Vector(3.6, 2.2, 1.0),
"rotation": unreal.Rotator(0.0, -34.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Grass_Mat_02",
"mesh_key": "plane",
"material_key": "grass",
"location_xy": unreal.Vector(-6200.0, 8300.0, 0.0),
"z_offset": 14.0,
"scale": unreal.Vector(2.7, 4.0, 1.0),
"rotation": unreal.Rotator(0.0, 41.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Rock_Slab_01",
"mesh_key": "chamfer_cube",
"material_key": "stone_resource",
"location_xy": unreal.Vector(22900.0, 18300.0, 0.0),
"z_offset": 45.0,
"scale": unreal.Vector(1.8, 1.05, 0.45),
"rotation": unreal.Rotator(0.0, 24.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Rock_Slab_02",
"mesh_key": "quarter_cylinder",
"material_key": "stone_resource",
"location_xy": unreal.Vector(38100.0, 29300.0, 0.0),
"z_offset": 55.0,
"scale": unreal.Vector(1.2, 1.8, 0.75),
"rotation": unreal.Rotator(0.0, -47.0, 0.0),
},
{
"label": "AGR_GZ_EnvVar_Water_Surface_01",
"mesh_key": "plane",
"material_key": "fresh_water",
"location_xy": unreal.Vector(-7200.0, 10400.0, 0.0),
"z_offset": 18.0,
"scale": unreal.Vector(2.6, 1.6, 1.0),
"rotation": unreal.Rotator(0.0, -15.0, 0.0),
},
]
FOLIAGE_ZONES = {
"trees": {
@@ -535,6 +644,27 @@ def spawn_demo_actor(spec, height_values, materials):
return actor
def spawn_environment_variation_actor(spec, height_values, materials):
location_xy = spec["location_xy"]
z = terrain_z_cm(height_values, location_xy.x, location_xy.y) + spec.get("z_offset", 0.0)
actor = unreal.AgrarianEditorAutomationLibrary.spawn_actor_in_editor_world(
unreal.StaticMeshActor,
unreal.Vector(location_xy.x, location_xy.y, z),
spec["rotation"],
spec["label"],
)
if not actor:
raise RuntimeError(f"Could not spawn {spec['label']}")
set_actor_label(actor, spec["label"])
actor.set_actor_scale3d(spec["scale"])
mesh_component = actor.static_mesh_component
mesh_component.set_editor_property("static_mesh", load_required_asset(VARIATION_MESHES[spec["mesh_key"]]))
mesh_component.set_material(0, materials[spec["material_key"]])
unreal.log(f"Placed {spec['label']} at {actor.get_actor_location()}")
return actor
def main():
if not unreal.EditorLevelLibrary.load_level(MAP_PATH):
raise RuntimeError(f"Could not load map: {MAP_PATH}")
@@ -543,6 +673,7 @@ def main():
labels.update(LEGACY_DEMO_LIGHTING_LABELS)
labels.update(spec["label"] for spec in BIOME_RESOURCE_ACTORS)
labels.update(spec["label"] for spec in WATER_SOURCE_ACTORS)
labels.update(spec["label"] for spec in ENVIRONMENT_VARIATION_ACTORS)
labels.add(FOLIAGE_LABEL)
remove_existing_demo_actors(labels)
@@ -554,6 +685,8 @@ def main():
spawn_demo_actor(spec, height_values, materials)
for spec in WATER_SOURCE_ACTORS:
spawn_demo_actor(spec, height_values, materials)
for spec in ENVIRONMENT_VARIATION_ACTORS:
spawn_environment_variation_actor(spec, height_values, materials)
for spec in DEMO_ACTORS:
spawn_demo_actor(spec, height_values, materials)
@@ -26,6 +26,16 @@ RESOURCE_MATERIALS = {
"AGR_DemoFiberResource": "fiber_resource",
"AGR_GZ_FreshWaterSource": "fresh_water",
}
VARIATION_PREFIX = "AGR_GZ_EnvVar_"
EXPECTED_VARIATION_COUNT = 11
EXPECTED_VARIATION_MATERIALS = {
"AGR_GZ_EnvVar_Tree_Canopy": "tree",
"AGR_GZ_EnvVar_Tree_Trunk": "wood_resource",
"AGR_GZ_EnvVar_Bush": "shrub",
"AGR_GZ_EnvVar_Grass": "grass",
"AGR_GZ_EnvVar_Rock": "stone_resource",
"AGR_GZ_EnvVar_Water": "fresh_water",
}
def get_actor_label(actor):
@@ -48,6 +58,13 @@ def material_key_for_label(label):
return None
def variation_material_key_for_label(label):
for prefix, material_key in EXPECTED_VARIATION_MATERIALS.items():
if label.startswith(prefix):
return material_key
return None
def assert_asset(path):
asset = unreal.EditorAssetLibrary.load_asset(path)
if not asset:
@@ -118,11 +135,50 @@ def main():
if checked_resource_actors < 10:
failures.append(f"expected at least 10 dressed resource/water actors, checked {checked_resource_actors}")
variation_actors = [actor for actor in actors if get_actor_label(actor).startswith(VARIATION_PREFIX)]
if len(variation_actors) != EXPECTED_VARIATION_COUNT:
failures.append(f"expected {EXPECTED_VARIATION_COUNT} environment variation actors, found {len(variation_actors)}")
variation_meshes = set()
variation_scales = set()
variation_material_keys = set()
for actor in variation_actors:
label = get_actor_label(actor)
expected_material_key = variation_material_key_for_label(label)
if not expected_material_key:
failures.append(f"{label} does not match a known variation material family")
continue
mesh_components = actor.get_components_by_class(unreal.StaticMeshComponent)
if not mesh_components:
failures.append(f"{label} has no static mesh component")
continue
component = mesh_components[0]
mesh = component.get_editor_property("static_mesh")
variation_meshes.add(mesh.get_path_name().split(".", 1)[0] if mesh else "")
scale = actor.get_actor_scale3d()
variation_scales.add((round(scale.x, 2), round(scale.y, 2), round(scale.z, 2)))
variation_material_keys.add(expected_material_key)
assigned = material_path(component.get_material(0))
expected = MATERIALS[expected_material_key]
if assigned != expected:
failures.append(f"{label} material expected {expected}, got {assigned}")
if len(variation_meshes) < 4:
failures.append(f"expected at least 4 variation mesh silhouettes, got {len(variation_meshes)}")
if len(variation_scales) < EXPECTED_VARIATION_COUNT:
failures.append("environment variation actors should use unique scale profiles")
if not {"tree", "wood_resource", "shrub", "grass", "stone_resource", "fresh_water"}.issubset(variation_material_keys):
failures.append("environment variation actors do not cover tree, shrub, grass, resource, and water families")
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"),
(docs, "asset variation"),
(roadmap, "[x] Replace grey-box environment presentation with an MVP natural environment pass"),
(roadmap, "[x] Add first-pass environment asset variation"),
]:
with open(path, "r", encoding="utf-8") as handle:
text = handle.read()
@@ -134,7 +190,8 @@ def main():
unreal.log(
"Ground Zero natural environment verification complete: "
f"{len(materials)} materials, {len(landscapes)} landscape(s), {checked_resource_actors} dressed resource/water actor(s)."
f"{len(materials)} materials, {len(landscapes)} landscape(s), "
f"{checked_resource_actors} dressed resource/water actor(s), {len(variation_actors)} variation actor(s)."
)