World: ground bundle — macro-blend ground materials, per-room dressing scatter, biome ground tint, base flora + worn paths

Phase 1.5b ground bundle (operator: "the ground is very plain"):
- Ground materials rebuilt: Grass/Sand base maps at real tiling (22x/26x, was a
  2x smear across 90-130u) + Synty normal maps + a generated 0.5-centered macro
  noise detail texture (LINEAR import — sRGB detail mul2x darkens 2.3x) with
  baked rim vignette. Meadow ground moved OFF the shared Synty asset onto
  project-owned Mat_Ground_Meadow.
- WorldAtmosphereSystem: per-room-biome ground tint on the expedition quads via
  MaterialPropertyBlock (Meadow green / Cavern blue / Blight purple; Arid sand
  default), lerped alongside the existing fog/ambient palettes.
- RoomDressingSystem + RoomDressingConfig (new, client-only observe-only):
  26 biome-flavoured cosmetic props per room, scattered inside the room's ACTUAL
  shape (same RoomLayoutMath authority as the server, distinct 0xD2E55 hash
  stream), deterministic from the replicated run seed, torn down with the room,
  colliders stripped. 47 curated prefab refs wired in Game.unity (build-safe).
- Base: 357 curated flora props re-enabled (bushes/wildflowers/grass clumps;
  buildings/pond/FX stay parked) + generated worn-dirt paths storage->warpgate
  and storage->base gate.

Live-verified: dressing 26/26 inside x[981,1023] z[-11,12] y=0, 11 varieties,
0 colliders; teardown on run end; Meadow tint converged to exact target; 470
EditMode green; console clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 22:47:44 -07:00
parent cf206d356a
commit 2fc5ae9a10
19 changed files with 1594 additions and 372 deletions
+732 -358
View File
File diff suppressed because it is too large Load Diff
@@ -25,6 +25,7 @@ Material:
m_Parent: {fileID: 0} m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0 m_ModifiedSerializedProperties: 0
m_ValidKeywords: m_ValidKeywords:
- _DETAIL_MULX2
- _NORMALMAP - _NORMALMAP
m_InvalidKeywords: [] m_InvalidKeywords: []
m_LightmapFlags: 4 m_LightmapFlags: 4
@@ -41,15 +42,15 @@ Material:
m_TexEnvs: m_TexEnvs:
- _BaseMap: - _BaseMap:
m_Texture: {fileID: 2800000, guid: c27220858ae0f6446aad43a6e7176710, type: 3} m_Texture: {fileID: 2800000, guid: c27220858ae0f6446aad43a6e7176710, type: 3}
m_Scale: {x: 2, y: 1} m_Scale: {x: 26, y: 26}
m_Offset: {x: 0, y: 0} m_Offset: {x: 0, y: 0}
- _BumpMap: - _BumpMap:
m_Texture: {fileID: 2800000, guid: c39c27cb220c80f40b13ba94a921d2fa, type: 3} m_Texture: {fileID: 2800000, guid: c39c27cb220c80f40b13ba94a921d2fa, type: 3}
m_Scale: {x: 1, y: 1} m_Scale: {x: 26, y: 26}
m_Offset: {x: 0, y: 0} m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap: - _DetailAlbedoMap:
m_Texture: {fileID: 0} m_Texture: {fileID: 2800000, guid: 15a6054fd7a19af41823672d9f02cf76, type: 3}
m_Scale: {x: 0.5, y: 1} m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0} m_Offset: {x: 0, y: 0}
- _DetailMask: - _DetailMask:
m_Texture: {fileID: 0} m_Texture: {fileID: 0}
@@ -65,7 +66,7 @@ Material:
m_Offset: {x: 0, y: 0} m_Offset: {x: 0, y: 0}
- _MainTex: - _MainTex:
m_Texture: {fileID: 2800000, guid: c27220858ae0f6446aad43a6e7176710, type: 3} m_Texture: {fileID: 2800000, guid: c27220858ae0f6446aad43a6e7176710, type: 3}
m_Scale: {x: 2, y: 1} m_Scale: {x: 26, y: 26}
m_Offset: {x: 0, y: 0} m_Offset: {x: 0, y: 0}
- _MetallicGlossMap: - _MetallicGlossMap:
m_Texture: {fileID: 0} m_Texture: {fileID: 0}
@@ -102,7 +103,7 @@ Material:
- _AlphaToMask: 0 - _AlphaToMask: 0
- _Blend: 0 - _Blend: 0
- _BlendModePreserveSpecular: 1 - _BlendModePreserveSpecular: 1
- _BumpScale: 1.5 - _BumpScale: 1.15
- _ClearCoatMask: 0 - _ClearCoatMask: 0
- _ClearCoatSmoothness: 0 - _ClearCoatSmoothness: 0
- _Cull: 2 - _Cull: 2
@@ -132,8 +133,8 @@ Material:
- _XRMotionVectorsPass: 1 - _XRMotionVectorsPass: 1
- _ZWrite: 1 - _ZWrite: 1
m_Colors: m_Colors:
- _BaseColor: {r: 0.72, g: 0.68, b: 0.63, a: 1} - _BaseColor: {r: 0.6, g: 0.56, b: 0.5, a: 1}
- _Color: {r: 0.72, g: 0.68, b: 0.63, a: 1} - _Color: {r: 0.6, g: 0.56, b: 0.5, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1} - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1} - _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
m_BuildTextureStacks: [] m_BuildTextureStacks: []
@@ -0,0 +1,139 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Mat_Ground_Meadow
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _DETAIL_MULX2
- _NORMALMAP
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap:
RenderType: Opaque
disabledShaderPasses:
- MOTIONVECTORS
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BaseMap:
m_Texture: {fileID: 2800000, guid: 6064e4e3e79a3a1408ee2059beaa066b, type: 3}
m_Scale: {x: 22, y: 22}
m_Offset: {x: 0, y: 0}
- _BumpMap:
m_Texture: {fileID: 2800000, guid: cab96a62a647e024a9868ca6bb815a34, type: 3}
m_Scale: {x: 22, y: 22}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 2800000, guid: 2e0b30d8eb26f084fb73e0ec24c93869, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 2800000, guid: 6064e4e3e79a3a1408ee2059beaa066b, type: 3}
m_Scale: {x: 22, y: 22}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _SpecGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_Lightmaps:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_LightmapsInd:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_ShadowMasks:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _AddPrecomputedVelocity: 0
- _AlphaClip: 0
- _AlphaToMask: 0
- _Blend: 0
- _BlendModePreserveSpecular: 1
- _BumpScale: 1.6
- _ClearCoatMask: 0
- _ClearCoatSmoothness: 0
- _Cull: 2
- _Cutoff: 0.5
- _DetailAlbedoMapScale: 1
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _DstBlendAlpha: 0
- _EnvironmentReflections: 1
- _GlossMapScale: 0
- _Glossiness: 0
- _GlossyReflections: 0
- _Metallic: 0
- _OcclusionStrength: 1
- _Parallax: 0.005
- _QueueOffset: 0
- _ReceiveShadows: 1
- _Smoothness: 0.08
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _SrcBlendAlpha: 1
- _Surface: 0
- _WorkflowMode: 1
- _XRMotionVectorsPass: 1
- _ZWrite: 1
m_Colors:
- _BaseColor: {r: 0.47, g: 0.54, b: 0.5, a: 1}
- _Color: {r: 0.4699999, g: 0.53999996, b: 0.5, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
m_BuildTextureStacks: []
m_AllowLocking: 1
--- !u!114 &73486229325987942
MonoBehaviour:
m_ObjectHideFlags: 11
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
version: 10
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a76b2980e09200542bcff27cf58490dd
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
+138
View File
@@ -0,0 +1,138 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &-8049590104176229237
MonoBehaviour:
m_ObjectHideFlags: 11
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
version: 10
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Mat_WornPath
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _SURFACE_TYPE_TRANSPARENT
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: 3000
stringTagMap:
RenderType: Transparent
disabledShaderPasses:
- MOTIONVECTORS
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BaseMap:
m_Texture: {fileID: 2800000, guid: ccf08596575193a4ea09cfcf46aa319d, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _SpecGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_Lightmaps:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_LightmapsInd:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- unity_ShadowMasks:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _AddPrecomputedVelocity: 0
- _AlphaClip: 0
- _AlphaToMask: 0
- _Blend: 0
- _BlendModePreserveSpecular: 1
- _BumpScale: 1
- _ClearCoatMask: 0
- _ClearCoatSmoothness: 0
- _Cull: 2
- _Cutoff: 0.5
- _DetailAlbedoMapScale: 1
- _DetailNormalMapScale: 1
- _DstBlend: 10
- _DstBlendAlpha: 0
- _EnvironmentReflections: 1
- _GlossMapScale: 0
- _Glossiness: 0
- _GlossyReflections: 0
- _Metallic: 0
- _OcclusionStrength: 1
- _Parallax: 0.005
- _QueueOffset: 0
- _ReceiveShadows: 1
- _Smoothness: 0.02
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 5
- _SrcBlendAlpha: 1
- _Surface: 1
- _WorkflowMode: 1
- _XRMotionVectorsPass: 1
- _ZWrite: 0
m_Colors:
- _BaseColor: {r: 1, g: 1, b: 1, a: 1}
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
m_BuildTextureStacks: []
m_AllowLocking: 1
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 732961b7d12fa3548a41f7d0bf1d636e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,34 @@
using UnityEngine;
namespace ProjectM.Client
{
/// <summary>
/// Live-tunable knobs + build-safe prefab references for <see cref="RoomDressingSystem"/> — the Phase 1.5b
/// per-room cosmetic dressing scatter. Mirrors the <see cref="AmbientMotionConfig"/>/<see cref="VFXConfig"/>
/// bridge idiom: a MonoBehaviour in the gameplay scene with a static <see cref="Instance"/> the client
/// presentation system reads. Prefab arrays are serialized scene references (never Resources.Load — that is
/// build-stripped). Absent config or an empty set = no dressing (the feature is soft-off).
/// </summary>
public sealed class RoomDressingConfig : MonoBehaviour
{
public static RoomDressingConfig Instance;
[Header("Master")]
public bool Enabled = true;
[Min(0)] public int CountPerRoom = 26;
[Min(0.1f)] public float ScaleMin = 0.85f;
[Min(0.1f)] public float ScaleMax = 1.35f;
[Header("Per-biome cosmetic prop sets (RoomBiomeId: Meadow/Arid/Cavern/Blight)")]
public GameObject[] Meadow;
public GameObject[] Arid;
public GameObject[] Cavern;
public GameObject[] Blight;
void OnEnable() { Instance = this; }
void OnDisable() { if (Instance == this) Instance = null; }
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetStatics() { Instance = null; }
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4ef612b567d6f6746bc03f764274740e
@@ -0,0 +1,111 @@
using ProjectM.Simulation;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
namespace ProjectM.Client
{
/// <summary>
/// Phase 1.5b (ground bundle) — client-only, observe-only PER-ROOM DRESSING SCATTER. A managed
/// <see cref="SystemBase"/> in <see cref="PresentationSystemGroup"/> that OBSERVES replicated
/// <see cref="RunInfo"/> and instantiates biome-flavoured cosmetic ground props (pebbles, tufts, bones,
/// mushrooms) inside the room's ACTUAL shape — the same <see cref="RoomLayoutMath"/> authority the server
/// scatter uses, on a DISTINCT hash stream so dressing never co-locates with nodes (0x0DE) or clutter
/// (0xC17). Deterministic from the replicated run seed: every co-op client (and a late-joiner) sees the
/// same floor. Torn down when the room changes or the run ends. Colliders + rigidbodies are stripped on
/// spawn — the aim-reticle raycast and the physics world must never see dressing. Never mutates the sim.
/// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class RoomDressingSystem : SystemBase
{
Transform _root;
uint _activeKey;
protected override void OnStartRunning()
{
if (_root == null) _root = new GameObject("~RoomDressing").transform;
}
protected override void OnDestroy()
{
if (_root != null) Object.Destroy(_root.gameObject);
}
protected override void OnUpdate()
{
if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != "Game") return;
if (_root == null) return;
var cfg = RoomDressingConfig.Instance;
bool inRoom = SystemAPI.TryGetSingleton<RunInfo>(out var ri)
&& (ri.Lifecycle == RunLifecycle.InRoom
|| ri.Lifecycle == RunLifecycle.RoomReward
|| ri.Lifecycle == RunLifecycle.RoomExplore);
bool on = cfg != null && cfg.Enabled && cfg.CountPerRoom > 0;
if (!inRoom || !on || !SystemAPI.TryGetSingleton<BaseAnchor>(out var anchor))
{
Clear();
return;
}
uint key = RunMapMath.Hash(ri.RunSeed, (uint)(ri.CurrentRoom + 1), 0xD2E5u);
if (key == _activeKey) return;
Clear();
Build(in ri, in anchor, cfg);
_activeKey = key;
}
void Clear()
{
if (_activeKey == 0 || _root == null) { _activeKey = 0; return; }
for (int i = _root.childCount - 1; i >= 0; i--)
Object.Destroy(_root.GetChild(i).gameObject);
_activeKey = 0;
}
void Build(in RunInfo ri, in BaseAnchor anchor, RoomDressingConfig cfg)
{
GameObject[] set = ri.CurrentBiome switch
{
RoomBiomeId.Meadow => cfg.Meadow,
RoomBiomeId.Cavern => cfg.Cavern,
RoomBiomeId.Blight => cfg.Blight,
_ => cfg.Arid,
};
if (set == null || set.Length == 0) return;
float3 baseCenter = BaseGridMath.PlotCenter(anchor);
byte subSlot = (byte)(ri.CurrentRoom & 1);
float3 origin = RegionMath.ExpeditionRoomOrigin(baseCenter, subSlot);
float3 portal = RegionMath.ExpeditionPortalPos(baseCenter, subSlot);
var map = RunMapMath.Generate(ri.RunSeed);
var node = map.NodeAt(RunMap.NodeId(ri.CurrentRoom, ri.CurrentCol));
var plan = RoomLayoutMath.Plan(node, ri.CurrentRoom, ri.RoomCount);
var rng = new Unity.Mathematics.Random(RunMapMath.Hash(ri.RunSeed, (uint)(ri.CurrentRoom + 1), 0xD2E55u) | 1u);
int count = cfg.CountPerRoom;
for (int i = 0; i < count; i++)
{
float3 pos = RoomLayoutMath.ScatterInShape(plan.ShapeId, origin, i, count, ref rng);
for (int attempt = 0; attempt < 4; attempt++)
{
bool nearLanding = math.distancesq(pos.xz, origin.xz) < 9f; // party lands at the origin
bool nearPortal = math.distancesq(pos.xz, portal.xz) < 6.25f; // keep the exit beacon clear
if (!nearLanding && !nearPortal) break;
pos = RoomLayoutMath.ScatterInShape(plan.ShapeId, origin, i, count, ref rng);
}
var prefab = set[rng.NextInt(0, set.Length)];
if (prefab == null) continue;
var go = Object.Instantiate(prefab, _root, false);
go.transform.SetPositionAndRotation(
new Vector3(pos.x, 0f, pos.z), // terrain y=0 (origin.y is the CC capsule plane, 1 u up)
Quaternion.Euler(0f, rng.NextFloat(0f, 360f), 0f));
go.transform.localScale = Vector3.one * rng.NextFloat(cfg.ScaleMin, cfg.ScaleMax);
foreach (var col in go.GetComponentsInChildren<Collider>(true)) Object.Destroy(col);
foreach (var rb in go.GetComponentsInChildren<Rigidbody>(true)) Object.Destroy(rb);
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6eb0d1fcbd0a69244842b4eb2fbcd49f
@@ -13,13 +13,18 @@ namespace ProjectM.Client
/// the simulation, no ECS query (camera + RenderSettings only). Knobs live in /// the simulation, no ECS query (camera + RenderSettings only). Knobs live in
/// <see cref="WorldAtmosphereConfig"/> (falls back to baked defaults when absent). Guarded to the gameplay /// <see cref="WorldAtmosphereConfig"/> (falls back to baked defaults when absent). Guarded to the gameplay
/// "Game" scene so it never restyles the menu. Fog enable/mode + equator/ground ambient are baked into the /// "Game" scene so it never restyles the menu. Fog enable/mode + equator/ground ambient are baked into the
/// scene; this only drives the per-region deltas. /// scene; this only drives the per-region deltas. Phase 1.5b: also drives a per-room-biome tint on the
/// expedition ground quads (MaterialPropertyBlock — the material asset is never touched).
/// </summary> /// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)] [WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(PresentationSystemGroup))] [UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class WorldAtmosphereSystem : SystemBase public partial class WorldAtmosphereSystem : SystemBase
{ {
Camera _cam; Camera _cam;
MeshRenderer[] _ground;
MaterialPropertyBlock _mpb;
Color _groundCur;
static readonly int BaseColorId = Shader.PropertyToID("_BaseColor");
protected override void OnUpdate() protected override void OnUpdate()
{ {
@@ -41,29 +46,32 @@ namespace ProjectM.Client
// (RunInfo.CurrentBiome) so every room re-themes — the camera-X blend still owns the base↔run fade. // (RunInfo.CurrentBiome) so every room re-themes — the camera-X blend still owns the base↔run fade.
// Palettes are code consts (cosmetic; per-biome config knobs can layer on later without churn). // Palettes are code consts (cosmetic; per-biome config knobs can layer on later without churn).
// Phase 1.5: all palettes moved to the dark-ambient set (dynamic lights carry readability). // Phase 1.5: all palettes moved to the dark-ambient set (dynamic lights carry readability).
if (SystemAPI.TryGetSingleton<ProjectM.Simulation.RunInfo>(out var runInfo) // Ground tints are ABSOLUTE _BaseColor values (the MPB replaces the authored arid sand tint).
&& runInfo.Lifecycle != ProjectM.Simulation.RunLifecycle.Staging) Color groundTarget = new Color(0.60f, 0.56f, 0.50f, 1f); // authored arid sand (Mat_Ground_Arid_Dark)
bool haveRun = SystemAPI.TryGetSingleton<ProjectM.Simulation.RunInfo>(out var runInfo);
if (haveRun && runInfo.Lifecycle != ProjectM.Simulation.RunLifecycle.Staging)
{ {
switch (runInfo.CurrentBiome) switch (runInfo.CurrentBiome)
{ {
case ProjectM.Simulation.RoomBiomeId.Meadow: case ProjectM.Simulation.RoomBiomeId.Meadow:
expFog = new Color(0.10f, 0.16f, 0.10f, 1f); expDen = 0.012f; expFog = new Color(0.10f, 0.16f, 0.10f, 1f); expDen = 0.012f;
expAmb = new Color(0.30f, 0.42f, 0.32f, 1f); expAmb = new Color(0.30f, 0.42f, 0.32f, 1f);
groundTarget = new Color(0.47f, 0.55f, 0.43f, 1f);
break; break;
case ProjectM.Simulation.RoomBiomeId.Cavern: case ProjectM.Simulation.RoomBiomeId.Cavern:
expFog = new Color(0.06f, 0.08f, 0.12f, 1f); expDen = 0.020f; expFog = new Color(0.06f, 0.08f, 0.12f, 1f); expDen = 0.020f;
expAmb = new Color(0.24f, 0.28f, 0.38f, 1f); expAmb = new Color(0.24f, 0.28f, 0.38f, 1f);
groundTarget = new Color(0.42f, 0.46f, 0.55f, 1f);
break; break;
case ProjectM.Simulation.RoomBiomeId.Blight: case ProjectM.Simulation.RoomBiomeId.Blight:
expFog = new Color(0.13f, 0.07f, 0.15f, 1f); expDen = 0.022f; expFog = new Color(0.13f, 0.07f, 0.15f, 1f); expDen = 0.022f;
expAmb = new Color(0.32f, 0.24f, 0.38f, 1f); expAmb = new Color(0.32f, 0.24f, 0.38f, 1f);
groundTarget = new Color(0.52f, 0.42f, 0.58f, 1f);
break; break;
// Arid keeps the config/default burnt-dusk orange above. // Arid keeps the config/default burnt-dusk orange + sand ground above.
} }
} }
float x = _cam.transform.position.x; float x = _cam.transform.position.x;
float t = Mathf.Clamp01((x - (boundary - half)) / (2f * half)); float t = Mathf.Clamp01((x - (boundary - half)) / (2f * half));
t = t * t * (3f - 2f * t); // smoothstep t = t * t * (3f - 2f * t); // smoothstep
@@ -71,6 +79,27 @@ namespace ProjectM.Client
RenderSettings.fogColor = Color.Lerp(baseFog, expFog, t); RenderSettings.fogColor = Color.Lerp(baseFog, expFog, t);
RenderSettings.fogDensity = Mathf.Lerp(baseDen, expDen, t); RenderSettings.fogDensity = Mathf.Lerp(baseDen, expDen, t);
RenderSettings.ambientSkyColor = Color.Lerp(baseAmb, expAmb, t); RenderSettings.ambientSkyColor = Color.Lerp(baseAmb, expAmb, t);
// Per-room-biome ground tint (expedition quads only; the meadow base keeps its authored material).
if (_ground == null)
{
var found = new System.Collections.Generic.List<MeshRenderer>();
foreach (var mr in Object.FindObjectsByType<MeshRenderer>(FindObjectsSortMode.None))
if (mr.gameObject.name == "Ground_Arid") found.Add(mr);
if (found.Count > 0)
{
_ground = found.ToArray();
_mpb = new MaterialPropertyBlock();
_groundCur = groundTarget; // snap on first sight — no lerp from black
}
}
if (_ground != null)
{
_groundCur = Color.Lerp(_groundCur, groundTarget, 1f - Mathf.Exp(-2.5f * UnityEngine.Time.deltaTime));
_mpb.SetColor(BaseColorId, _groundCur);
for (int i = 0; i < _ground.Length; i++)
if (_ground[i] != null) _ground[i].SetPropertyBlock(_mpb);
}
} }
} }
} }
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 72c44276f3323c74d8621e87402a67f3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e494af549e3fab64a86c942b1a379df9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 15a6054fd7a19af41823672d9f02cf76
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 2e0b30d8eb26f084fb73e0ec24c93869
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: ccf08596575193a4ea09cfcf46aa319d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant: