Feel: Phase 1.5 lighting/atmosphere pass — dark ambient + dynamic lights

Dark-ambient, dynamic-light-first look (RoR2/Death Scourges reference),
built filter-agnostic so the pixel style composes on top:

- New DynamicLightSystem (client-only, PresentationSystemGroup): pooled
  point lights follow projectile ghosts; pulsing portal light during
  RoomExplore (same ExpeditionPortalPos authority as the beacon); soft
  resource-node glow that fades with harvest-shrink; short impact
  FLASHES fed by CombatFeedbackSystem's burst funnel (EmitColored +
  SpawnVfx -> RequestFlash). Knobs in DynamicLightConfig (scene object,
  code defaults when absent).
- Atmosphere darkened: WorldAtmosphereConfig/System palettes moved to
  the dark set (base cool dusk, arid burnt dusk, per-room biome consts
  darkened); scene fog switched Linear -> ExponentialSquared (the
  system's per-region DENSITY writes were dead in Linear mode);
  trilight ambient lowered; directional 1.9 -> 1.05 slightly warm.
- 6 landmark mood lights (warpgate cyan, artefact violet x2, survey
  camp warm x2, cabin warm); URP additional-lights-per-object 4 -> 8
  (was starving the existing Aether lights).
- Art-look gate material: Assets/Screenshots/artlook_{base,room}_
  pixel{ON,OFF}.png over the lit scene.

Verified: lights live in Play (20 static at base incl. new landmarks;
pooled dynamics active in-room: node glows + impact flashes), fog/
ambient values confirmed live, 466/466 EditMode, console clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 20:37:50 -07:00
parent 30ddb6fbd0
commit 414f9ff62a
17 changed files with 1466 additions and 35 deletions
+698 -14
View File
@@ -15,14 +15,14 @@ RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 10
m_Fog: 1
m_FogColor: {r: 0.07, g: 0.09, b: 0.1, a: 1}
m_FogMode: 1
m_FogDensity: 0.006
m_FogColor: {r: 0.09, g: 0.13, b: 0.14, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 55
m_LinearFogEnd: 180
m_AmbientSkyColor: {r: 0.2, g: 0.24, b: 0.29, a: 1}
m_AmbientEquatorColor: {r: 0.36, g: 0.4, b: 0.44, a: 1}
m_AmbientGroundColor: {r: 0.26, g: 0.28, b: 0.3, a: 1}
m_AmbientSkyColor: {r: 0.32, g: 0.38, b: 0.48, a: 1}
m_AmbientEquatorColor: {r: 0.22, g: 0.24, b: 0.27, a: 1}
m_AmbientGroundColor: {r: 0.13, g: 0.14, b: 0.15, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 1
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
@@ -3442,6 +3442,69 @@ Transform:
m_CorrespondingSourceObject: {fileID: 3382370562072349170, guid: be29130b15f5d6b4782a79790d86b12c, type: 3}
m_PrefabInstance: {fileID: 70139539}
m_PrefabAsset: {fileID: 0}
--- !u!1 &70737664
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 70737666}
- component: {fileID: 70737665}
m_Layer: 0
m_Name: DynamicLights
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &70737665
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 70737664}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 29d6b79f028929744bebd1580378691c, type: 3}
m_Name:
m_EditorClassIdentifier: ProjectM.Client::ProjectM.Client.DynamicLightConfig
Enabled: 1
MaxProjectileLights: 24
ProjectileColor: {r: 0.45, g: 0.85, b: 1, a: 1}
ProjectileIntensity: 2.6
ProjectileRange: 7
PortalColor: {r: 0.55, g: 0.95, b: 1, a: 1}
PortalIntensity: 3.5
PortalPulseAmp: 1.2
PortalRange: 13
NodeGlow: 1
MaxNodeLights: 12
NodeColor: {r: 0.4, g: 1, b: 0.75, a: 1}
NodeIntensity: 1.3
NodeRange: 4.5
MaxFlashLights: 8
FlashDefaultColor: {r: 1, g: 0.85, b: 0.55, a: 1}
FlashIntensity: 3.2
FlashRange: 6.5
FlashDuration: 0.18
--- !u!4 &70737666
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 70737664}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &71716685
PrefabInstance:
m_ObjectHideFlags: 0
@@ -7951,6 +8014,103 @@ MeshFilter:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 203489229}
m_Mesh: {fileID: -8892877181006153405, guid: 60c2fa501544e6a47ab77906245d80db, type: 3}
--- !u!1 &205208180
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 205208181}
- component: {fileID: 205208182}
m_Layer: 0
m_Name: SurveyCampLight_S1
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &205208181
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 205208180}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 1538, y: 2.5, z: 23}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1276835916}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!108 &205208182
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 205208180}
m_Enabled: 1
serializedVersion: 13
m_Type: 2
m_Color: {r: 1, g: 0.72, b: 0.4, a: 1}
m_Intensity: 3.5
m_Range: 12
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize2D: {x: 0.5, y: 0.5}
m_Shadows:
m_Type: 0
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShapeRadius: 0.025
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!1001 &207855813
PrefabInstance:
m_ObjectHideFlags: 0
@@ -15467,8 +15627,8 @@ Light:
m_Enabled: 1
serializedVersion: 13
m_Type: 1
m_Color: {r: 0.92, g: 0.94, b: 1, a: 1}
m_Intensity: 1.9
m_Color: {r: 0.93, g: 0.91, b: 0.88, a: 1}
m_Intensity: 1.05
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
@@ -26884,6 +27044,103 @@ Transform:
m_CorrespondingSourceObject: {fileID: 3865605783607592755, guid: f3ab8793d314fa84793c1c2086c914df, type: 3}
m_PrefabInstance: {fileID: 663552286}
m_PrefabAsset: {fileID: 0}
--- !u!1 &663963125
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 663963126}
- component: {fileID: 663963127}
m_Layer: 0
m_Name: ArtefactLight
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &663963126
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 663963125}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 1030, y: 3.5, z: -8}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1276835916}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!108 &663963127
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 663963125}
m_Enabled: 1
serializedVersion: 13
m_Type: 2
m_Color: {r: 0.75, g: 0.45, b: 1, a: 1}
m_Intensity: 4.5
m_Range: 14
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize2D: {x: 0.5, y: 0.5}
m_Shadows:
m_Type: 0
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShapeRadius: 0.025
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!1001 &667197736
PrefabInstance:
m_ObjectHideFlags: 0
@@ -44050,6 +44307,103 @@ Transform:
m_CorrespondingSourceObject: {fileID: 5826526793267013502, guid: 1649a4e629b575d458a0b13e637a7468, type: 3}
m_PrefabInstance: {fileID: 1118540215}
m_PrefabAsset: {fileID: 0}
--- !u!1 &1120306465
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1120306466}
- component: {fileID: 1120306467}
m_Layer: 0
m_Name: SurveyCampLight
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1120306466
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1120306465}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 1038, y: 2.5, z: 23}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1276835916}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!108 &1120306467
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1120306465}
m_Enabled: 1
serializedVersion: 13
m_Type: 2
m_Color: {r: 1, g: 0.72, b: 0.4, a: 1}
m_Intensity: 3.5
m_Range: 12
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize2D: {x: 0.5, y: 0.5}
m_Shadows:
m_Type: 0
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShapeRadius: 0.025
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!1001 &1120488715
PrefabInstance:
m_ObjectHideFlags: 0
@@ -49423,6 +49777,43 @@ Transform:
m_CorrespondingSourceObject: {fileID: 2231112379584141163, guid: bd945678d6dae86468a64e5a59496b5b, type: 3}
m_PrefabInstance: {fileID: 1276569853}
m_PrefabAsset: {fileID: 0}
--- !u!1 &1276835915
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1276835916}
m_Layer: 0
m_Name: LandmarkLights
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1276835916
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1276835915}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1915626474}
- {fileID: 663963126}
- {fileID: 1501947283}
- {fileID: 1120306466}
- {fileID: 205208181}
- {fileID: 2045829909}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1001 &1280065368
PrefabInstance:
m_ObjectHideFlags: 0
@@ -56243,6 +56634,103 @@ Transform:
m_CorrespondingSourceObject: {fileID: 591573043354113928, guid: bafd1387c024a3a459afa35dd24cec25, type: 3}
m_PrefabInstance: {fileID: 1492696656}
m_PrefabAsset: {fileID: 0}
--- !u!1 &1501947282
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1501947283}
- component: {fileID: 1501947284}
m_Layer: 0
m_Name: ArtefactLight_S1
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1501947283
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1501947282}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 1530, y: 3.5, z: -8}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1276835916}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!108 &1501947284
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1501947282}
m_Enabled: 1
serializedVersion: 13
m_Type: 2
m_Color: {r: 0.75, g: 0.45, b: 1, a: 1}
m_Intensity: 4.5
m_Range: 14
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize2D: {x: 0.5, y: 0.5}
m_Shadows:
m_Type: 0
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShapeRadius: 0.025
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!1001 &1502093507
PrefabInstance:
m_ObjectHideFlags: 0
@@ -63364,12 +63852,12 @@ MonoBehaviour:
m_EditorClassIdentifier: ProjectM.Client::ProjectM.Client.WorldAtmosphereConfig
BoundaryX: 500
BlendHalfWidth: 80
BaseFogColor: {r: 0.4, g: 0.5, b: 0.54, a: 1}
BaseFogDensity: 0.006
BaseAmbientSky: {r: 0.46, g: 0.52, b: 0.6, a: 1}
ExpeditionFogColor: {r: 0.8509804, g: 0.54901963, b: 0.22745098, a: 1}
ExpeditionFogDensity: 0.01
ExpeditionAmbientSky: {r: 0.91, g: 0.769, b: 0.604, a: 1}
BaseFogColor: {r: 0.09, g: 0.13, b: 0.14, a: 1}
BaseFogDensity: 0.01
BaseAmbientSky: {r: 0.32, g: 0.38, b: 0.48, a: 1}
ExpeditionFogColor: {r: 0.24, g: 0.13, b: 0.06, a: 1}
ExpeditionFogDensity: 0.016
ExpeditionAmbientSky: {r: 0.45, g: 0.31, b: 0.2, a: 1}
--- !u!4 &1709609454
Transform:
m_ObjectHideFlags: 0
@@ -72274,6 +72762,103 @@ Transform:
- {fileID: 397367818}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1915626473
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1915626474}
- component: {fileID: 1915626475}
m_Layer: 0
m_Name: WarpgateLight
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1915626474
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1915626473}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 3, z: -21.5}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1276835916}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!108 &1915626475
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1915626473}
m_Enabled: 1
serializedVersion: 13
m_Type: 2
m_Color: {r: 0.35, g: 0.85, b: 1, a: 1}
m_Intensity: 4
m_Range: 14
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize2D: {x: 0.5, y: 0.5}
m_Shadows:
m_Type: 0
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShapeRadius: 0.025
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!1001 &1920089972
PrefabInstance:
m_ObjectHideFlags: 0
@@ -76591,6 +77176,103 @@ Transform:
m_CorrespondingSourceObject: {fileID: 7730081809800521909, guid: 8de936dd6b78d5d409ee63fa6cb9d111, type: 3}
m_PrefabInstance: {fileID: 2045499181}
m_PrefabAsset: {fileID: 0}
--- !u!1 &2045829908
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2045829909}
- component: {fileID: 2045829910}
m_Layer: 0
m_Name: CabinLight
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2045829909
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2045829908}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 22, y: 2.5, z: 14}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1276835916}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!108 &2045829910
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2045829908}
m_Enabled: 1
serializedVersion: 13
m_Type: 2
m_Color: {r: 1, g: 0.75, b: 0.45, a: 1}
m_Intensity: 3
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
m_CookieSize2D: {x: 0.5, y: 0.5}
m_Shadows:
m_Type: 0
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_CullingMatrixOverride:
e00: 1
e01: 0
e02: 0
e03: 0
e10: 0
e11: 1
e12: 0
e13: 0
e20: 0
e21: 0
e22: 1
e23: 0
e30: 0
e31: 0
e32: 0
e33: 1
m_UseCullingMatrixOverride: 0
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingLayerMask: 1
m_Lightmapping: 4
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
m_ShapeRadius: 0.025
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
--- !u!1001 &2047680188
PrefabInstance:
m_ObjectHideFlags: 0
@@ -80609,3 +81291,5 @@ SceneRoots:
- {fileID: 433101879}
- {fileID: 1709609454}
- {fileID: 1851179016}
- {fileID: 70737666}
- {fileID: 1276835916}
Binary file not shown.
@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 6f3266883d21bd64bbf1e3e2186714c0
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: 0
wrapV: 0
wrapW: 0
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: 3421ac579188db74e8c5395631b3e5d1
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: 0
wrapV: 0
wrapW: 0
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: ec34d2abf204a2241b3308a818f8d92d
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: 0
wrapV: 0
wrapW: 0
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: 752448759c514e74aa9613130eb8f8d4
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: 0
wrapV: 0
wrapW: 0
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:
+1 -1
View File
@@ -45,7 +45,7 @@ MonoBehaviour:
m_MainLightShadowsSupported: 1
m_MainLightShadowmapResolution: 2048
m_AdditionalLightsRenderingMode: 1
m_AdditionalLightsPerObjectLimit: 4
m_AdditionalLightsPerObjectLimit: 8
m_AdditionalLightShadowsSupported: 1
m_AdditionalLightsShadowmapResolution: 2048
m_AdditionalLightsShadowResolutionTierLow: 256
@@ -517,6 +517,8 @@ namespace ProjectM.Client
if (ps == null || count <= 0) return;
var ep = new ParticleSystem.EmitParams { position = pos, startColor = color };
ps.Emit(ep, count);
// Phase 1.5 lighting: every colored burst also throws a short point-light flash (dark-ambient look).
DynamicLightSystem.RequestFlash(pos, color, Mathf.Clamp(count / 18f, 0.4f, 1.4f));
}
void Burst(ParticleSystem fallback, GameObject prefab, Vector3 pos, int count)
@@ -535,6 +537,8 @@ namespace ProjectM.Client
var systems = go.GetComponentsInChildren<ParticleSystem>();
for (int i = 0; i < systems.Length; i++) systems[i].Play();
_activeVfx.Add(new TimedVfx { Go = go, Kill = SystemAPI.Time.ElapsedTime + VfxLifetime(go) });
// Phase 1.5 lighting: authored VFX impacts flash too (a==0 -> config default colour).
DynamicLightSystem.RequestFlash(pos, new Color(0f, 0f, 0f, 0f), 1f);
}
void PruneVfx()
@@ -0,0 +1,51 @@
using UnityEngine;
namespace ProjectM.Client
{
/// <summary>
/// Live-tunable knobs for <see cref="DynamicLightSystem"/> — the Phase 1.5 "dynamic-light-first" layer
/// (RoR2 trick: dark ambient + moving point lights carry the readability). Mirrors the
/// <see cref="WorldAtmosphereConfig"/> bridge idiom: a MonoBehaviour placed in the gameplay scene with a
/// static <see cref="Instance"/> the client presentation system reads (falling back to these same
/// defaults when absent). Pure presentation — never touches the simulation. Colours are sRGB.
/// </summary>
public sealed class DynamicLightConfig : MonoBehaviour
{
public static DynamicLightConfig Instance;
[Header("Master")]
public bool Enabled = true;
[Header("Projectile lights (pooled, follow every live projectile ghost)")]
[Min(0)] public int MaxProjectileLights = 24;
public Color ProjectileColor = new Color(0.45f, 0.85f, 1f, 1f);
[Min(0f)] public float ProjectileIntensity = 2.6f;
[Min(0.5f)] public float ProjectileRange = 7f;
[Header("Portal light (RoomExplore loot window — same position authority as the beacon)")]
public Color PortalColor = new Color(0.55f, 0.95f, 1f, 1f);
[Min(0f)] public float PortalIntensity = 3.5f;
[Min(0f)] public float PortalPulseAmp = 1.2f;
[Min(0.5f)] public float PortalRange = 13f;
[Header("Resource-node glow (soft pickup readability)")]
public bool NodeGlow = true;
[Min(0)] public int MaxNodeLights = 12;
public Color NodeColor = new Color(0.40f, 1f, 0.75f, 1f);
[Min(0f)] public float NodeIntensity = 1.3f;
[Min(0.5f)] public float NodeRange = 4.5f;
[Header("Impact flashes (fed by CombatFeedbackSystem's burst funnel)")]
[Min(0)] public int MaxFlashLights = 8;
public Color FlashDefaultColor = new Color(1f, 0.85f, 0.55f, 1f);
[Min(0f)] public float FlashIntensity = 3.2f;
[Min(0.5f)] public float FlashRange = 6.5f;
[Min(0.02f)] public float FlashDuration = 0.18f;
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: 29d6b79f028929744bebd1580378691c
@@ -0,0 +1,206 @@
using System.Collections.Generic;
using ProjectM.Simulation;
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;
namespace ProjectM.Client
{
/// <summary>
/// Client-only DYNAMIC LIGHT layer (Phase 1.5 lighting pass — dark ambient, dynamic-light-first): pooled
/// point lights that follow projectile ghosts, a pulsing portal light during the RoomExplore loot window
/// (same <see cref="RegionMath.ExpeditionPortalPos"/> authority the beacon + HUD prompt use), a soft glow
/// on resource nodes, and short impact FLASHES fed by CombatFeedbackSystem's burst funnel via
/// <see cref="RequestFlash"/>. Observe-only <see cref="SystemBase"/> in
/// <see cref="PresentationSystemGroup"/> (once per frame, no rollback double-fire); never mutates the sim;
/// Entity keys are pruned every frame (a pruned ghost = despawn); lights are pooled GameObjects under a
/// private root, NEVER scene-saved. Knobs live in <see cref="DynamicLightConfig"/> (defaults when absent).
/// Wall-clock time is fine here (presentation only). URP: point lights, shadows off.
/// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class DynamicLightSystem : SystemBase
{
struct FlashRequest { public Vector3 Pos; public Color Color; public float Scale; }
struct ActiveFlash { public Light Light; public float EndTime; public float BaseIntensity; public float Duration; }
// Static request queue: CombatFeedbackSystem (same thread, PresentationSystemGroup) enqueues, we drain.
static readonly List<FlashRequest> Pending = new List<FlashRequest>();
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetStatics() { Pending.Clear(); }
/// <summary>Queue a short impact flash. <paramref name="color"/> with a==0 means "use the config
/// default colour"; <paramref name="scale"/> scales intensity+range (clamped by the drainer).</summary>
public static void RequestFlash(Vector3 pos, Color color, float scale)
{
if (Pending.Count < 64) Pending.Add(new FlashRequest { Pos = pos, Color = color, Scale = scale });
}
GameObject _root;
readonly Dictionary<Entity, Light> _projectileLights = new Dictionary<Entity, Light>();
readonly Dictionary<Entity, Light> _nodeLights = new Dictionary<Entity, Light>();
readonly HashSet<Entity> _seen = new HashSet<Entity>();
readonly List<Entity> _dead = new List<Entity>();
readonly Stack<Light> _pool = new Stack<Light>();
readonly List<ActiveFlash> _flashes = new List<ActiveFlash>();
Light _portalLight;
protected override void OnDestroy()
{
if (_root != null) Object.Destroy(_root);
}
protected override void OnUpdate()
{
if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != "Game") return;
var cfg = DynamicLightConfig.Instance;
bool enabled = cfg == null || cfg.Enabled;
if (!enabled)
{
Pending.Clear();
if (_root != null && _root.activeSelf) _root.SetActive(false);
return;
}
if (_root == null)
{
_root = new GameObject("~DynamicLights");
Object.DontDestroyOnLoad(_root);
}
if (!_root.activeSelf) _root.SetActive(true);
// ---- projectile lights (pooled follow-lights) ----
int projCap = cfg != null ? cfg.MaxProjectileLights : 24;
Color projColor = cfg != null ? cfg.ProjectileColor : new Color(0.45f, 0.85f, 1f, 1f);
float projIntensity = cfg != null ? cfg.ProjectileIntensity : 2.6f;
float projRange = cfg != null ? cfg.ProjectileRange : 7f;
_seen.Clear();
foreach (var (lt, entity) in SystemAPI.Query<RefRO<LocalTransform>>().WithAll<Projectile>().WithEntityAccess())
{
_seen.Add(entity);
if (!_projectileLights.TryGetValue(entity, out var light))
{
if (_projectileLights.Count >= projCap) continue;
light = Rent();
_projectileLights[entity] = light;
}
light.color = projColor;
light.intensity = projIntensity;
light.range = projRange;
var p = lt.ValueRO.Position;
light.transform.position = new Vector3(p.x, p.y + 0.4f, p.z);
}
PruneUnseen(_projectileLights);
// ---- resource-node glow ----
bool nodeGlow = cfg == null || cfg.NodeGlow;
int nodeCap = cfg != null ? cfg.MaxNodeLights : 12;
Color nodeColor = cfg != null ? cfg.NodeColor : new Color(0.40f, 1f, 0.75f, 1f);
float nodeIntensity = cfg != null ? cfg.NodeIntensity : 1.3f;
float nodeRange = cfg != null ? cfg.NodeRange : 4.5f;
_seen.Clear();
if (nodeGlow)
{
foreach (var (lt, entity) in SystemAPI.Query<RefRO<LocalTransform>>().WithAll<ResourceNode>().WithEntityAccess())
{
_seen.Add(entity);
if (!_nodeLights.TryGetValue(entity, out var light))
{
if (_nodeLights.Count >= nodeCap) continue;
light = Rent();
_nodeLights[entity] = light;
}
light.color = nodeColor;
// scale the glow with the node's harvest-shrink so it fades as the node depletes
light.intensity = nodeIntensity * Mathf.Clamp01(lt.ValueRO.Scale);
light.range = nodeRange;
var p = lt.ValueRO.Position;
light.transform.position = new Vector3(p.x, p.y + 1.1f, p.z);
}
}
PruneUnseen(_nodeLights);
// ---- portal light (RoomExplore only) ----
bool portalOn = false;
if (SystemAPI.TryGetSingleton<RunInfo>(out var runInfo)
&& runInfo.Lifecycle == RunLifecycle.RoomExplore
&& SystemAPI.TryGetSingleton<BaseAnchor>(out var anchor))
{
portalOn = true;
var pos = RegionMath.ExpeditionPortalPos(BaseGridMath.PlotCenter(anchor), (byte)(runInfo.CurrentRoom & 1));
if (_portalLight == null) _portalLight = Rent();
float baseI = cfg != null ? cfg.PortalIntensity : 3.5f;
float amp = cfg != null ? cfg.PortalPulseAmp : 1.2f;
_portalLight.color = cfg != null ? cfg.PortalColor : new Color(0.55f, 0.95f, 1f, 1f);
_portalLight.range = cfg != null ? cfg.PortalRange : 13f;
_portalLight.intensity = baseI + amp * Mathf.Sin(UnityEngine.Time.time * 4f);
_portalLight.transform.position = new Vector3(pos.x, pos.y + 2.2f, pos.z);
}
if (!portalOn && _portalLight != null) { Return(_portalLight); _portalLight = null; }
// ---- impact flashes ----
float now = UnityEngine.Time.time;
int flashCap = cfg != null ? cfg.MaxFlashLights : 8;
Color flashDefault = cfg != null ? cfg.FlashDefaultColor : new Color(1f, 0.85f, 0.55f, 1f);
float flashIntensity = cfg != null ? cfg.FlashIntensity : 3.2f;
float flashRange = cfg != null ? cfg.FlashRange : 6.5f;
float flashDur = cfg != null ? cfg.FlashDuration : 0.18f;
for (int i = 0; i < Pending.Count; i++)
{
if (_flashes.Count >= flashCap) break;
var req = Pending[i];
float scale = Mathf.Clamp(req.Scale, 0.3f, 1.6f);
var light = Rent();
light.color = req.Color.a > 0f ? req.Color : flashDefault;
light.intensity = flashIntensity * scale;
light.range = flashRange * Mathf.Sqrt(scale);
light.transform.position = req.Pos + new Vector3(0f, 0.6f, 0f);
_flashes.Add(new ActiveFlash { Light = light, EndTime = now + flashDur, BaseIntensity = light.intensity, Duration = flashDur });
}
Pending.Clear();
for (int i = _flashes.Count - 1; i >= 0; i--)
{
var f = _flashes[i];
float remain = f.EndTime - now;
if (remain <= 0f) { Return(f.Light); _flashes.RemoveAt(i); continue; }
f.Light.intensity = f.BaseIntensity * (remain / f.Duration);
}
}
void PruneUnseen(Dictionary<Entity, Light> map)
{
_dead.Clear();
foreach (var kv in map)
if (!_seen.Contains(kv.Key)) _dead.Add(kv.Key);
for (int i = 0; i < _dead.Count; i++)
{
Return(map[_dead[i]]);
map.Remove(_dead[i]);
}
}
Light Rent()
{
while (_pool.Count > 0)
{
var pooled = _pool.Pop();
if (pooled == null) continue; // scene reload can null pooled objects
pooled.gameObject.SetActive(true);
return pooled;
}
var go = new GameObject("PointLight");
go.transform.SetParent(_root.transform, false);
var light = go.AddComponent<Light>();
light.type = LightType.Point;
light.shadows = LightShadows.None;
return light;
}
void Return(Light light)
{
if (light == null) return;
light.gameObject.SetActive(false);
_pool.Push(light);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7387d0b5b23709a45bc7032dfe336bbb
@@ -19,15 +19,15 @@ namespace ProjectM.Client
[Tooltip("Half-width of the smooth transition band around BoundaryX, in world units.")]
public float BlendHalfWidth = 80f;
[Header("Base (meadow) atmosphere")]
public Color BaseFogColor = new Color(0.784f, 0.878f, 0.824f, 1f); // #C8E0D2
public float BaseFogDensity = 0.006f;
public Color BaseAmbientSky = new Color(0.725f, 0.831f, 0.949f, 1f); // #B9D4F2
[Header("Base (meadow) atmosphere — Phase 1.5 dark-ambient set (dynamic-light-first)")]
public Color BaseFogColor = new Color(0.09f, 0.13f, 0.14f, 1f);
public float BaseFogDensity = 0.010f;
public Color BaseAmbientSky = new Color(0.32f, 0.38f, 0.48f, 1f);
[Header("Expedition (arid desert) atmosphere")]
public Color ExpeditionFogColor = new Color(0.851f, 0.549f, 0.227f, 1f); // #D98C3A
public float ExpeditionFogDensity = 0.010f;
public Color ExpeditionAmbientSky = new Color(0.910f, 0.769f, 0.604f, 1f); // #E8C49A
public Color ExpeditionFogColor = new Color(0.24f, 0.13f, 0.06f, 1f);
public float ExpeditionFogDensity = 0.016f;
public Color ExpeditionAmbientSky = new Color(0.45f, 0.31f, 0.20f, 1f);
void OnEnable() { Instance = this; }
void OnDisable() { if (Instance == this) Instance = null; }
@@ -31,37 +31,39 @@ namespace ProjectM.Client
float boundary = cfg != null ? cfg.BoundaryX : 500f;
float half = cfg != null ? Mathf.Max(1f, cfg.BlendHalfWidth) : 80f;
Color baseFog = cfg != null ? cfg.BaseFogColor : new Color(0.784f, 0.878f, 0.824f, 1f);
float baseDen = cfg != null ? cfg.BaseFogDensity : 0.006f;
Color baseAmb = cfg != null ? cfg.BaseAmbientSky : new Color(0.725f, 0.831f, 0.949f, 1f);
Color expFog = cfg != null ? cfg.ExpeditionFogColor : new Color(0.851f, 0.549f, 0.227f, 1f);
float expDen = cfg != null ? cfg.ExpeditionFogDensity : 0.010f;
Color expAmb = cfg != null ? cfg.ExpeditionAmbientSky : new Color(0.910f, 0.769f, 0.604f, 1f);
Color baseFog = cfg != null ? cfg.BaseFogColor : new Color(0.09f, 0.13f, 0.14f, 1f);
float baseDen = cfg != null ? cfg.BaseFogDensity : 0.010f;
Color baseAmb = cfg != null ? cfg.BaseAmbientSky : new Color(0.32f, 0.38f, 0.48f, 1f);
Color expFog = cfg != null ? cfg.ExpeditionFogColor : new Color(0.24f, 0.13f, 0.06f, 1f);
float expDen = cfg != null ? cfg.ExpeditionFogDensity : 0.016f;
Color expAmb = cfg != null ? cfg.ExpeditionAmbientSky : new Color(0.45f, 0.31f, 0.20f, 1f);
// Step 14 (expedition redesign): the EXPEDITION-side palette keys on the replicated per-room biome
// (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).
// Phase 1.5: all palettes moved to the dark-ambient set (dynamic lights carry readability).
if (SystemAPI.TryGetSingleton<ProjectM.Simulation.RunInfo>(out var runInfo)
&& runInfo.Lifecycle != ProjectM.Simulation.RunLifecycle.Staging)
{
switch (runInfo.CurrentBiome)
{
case ProjectM.Simulation.RoomBiomeId.Meadow:
expFog = new Color(0.62f, 0.82f, 0.62f, 1f); expDen = 0.008f;
expAmb = new Color(0.68f, 0.88f, 0.70f, 1f);
expFog = new Color(0.10f, 0.16f, 0.10f, 1f); expDen = 0.012f;
expAmb = new Color(0.30f, 0.42f, 0.32f, 1f);
break;
case ProjectM.Simulation.RoomBiomeId.Cavern:
expFog = new Color(0.42f, 0.48f, 0.60f, 1f); expDen = 0.014f;
expAmb = new Color(0.50f, 0.56f, 0.72f, 1f);
expFog = new Color(0.06f, 0.08f, 0.12f, 1f); expDen = 0.020f;
expAmb = new Color(0.24f, 0.28f, 0.38f, 1f);
break;
case ProjectM.Simulation.RoomBiomeId.Blight:
expFog = new Color(0.55f, 0.42f, 0.62f, 1f); expDen = 0.016f;
expAmb = new Color(0.62f, 0.50f, 0.70f, 1f);
expFog = new Color(0.13f, 0.07f, 0.15f, 1f); expDen = 0.022f;
expAmb = new Color(0.32f, 0.24f, 0.38f, 1f);
break;
// Arid keeps the config/default orange above.
// Arid keeps the config/default burnt-dusk orange above.
}
}
float x = _cam.transform.position.x;
float t = Mathf.Clamp01((x - (boundary - half)) / (2f * half));
t = t * t * (3f - 2f * t); // smoothstep