Feel: ambient motion layer + art look locked (clean dark-Synty, pixel OFF)
Art look LOCKED by the operator from the artlook_* side-by-sides: clean dark-Synty; PixelOutline._MasterEnabled=0 shipped (feature stays dev-toggleable via F3; other fullscreen effects remain backlog). New AmbientMotionSystem (client-only PresentationSystemGroup, the "world alive" anti-static beat, zero netcode): - per-biome DRIFT particles from one world-space emitter following the camera (meadow motes / arid wind-blown dust / cavern motes / blight spores; palette keys on camera-X region + replicated room biome, mirroring WorldAtmosphereSystem's switch) - idle SWAY on the ACTIVE cosmetic flora prop roots near the camera (LOD children follow the root; probed: none static-batched) - walk-through RUSTLE: flora brushed by the local player (GhostOwnerIsLocal idiom) gets a decaying energy shake Rotations write around a cached base (lossless when toggled off); knobs in AmbientMotionConfig (scene object, code defaults when absent). Verified live: drift palettes exact in both regions (meadow 0.62/0.85/0.60 -> arid 0.85/0.68/0.45 on region cross), flora beside the player rocked (0,0)->(-4.0,+5.0) deg within the rustle+sway envelope, 466/466 EditMode, console clean. Discovery for follow-up: the BASE biome's flora props are authored INACTIVE (84 bushes etc.) - the base has no sway targets until some are re-enabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -40011,6 +40011,68 @@ Transform:
|
|||||||
m_CorrespondingSourceObject: {fileID: 6078297801174817557, guid: 66d4f7c006b77084da3929add6d06f24, type: 3}
|
m_CorrespondingSourceObject: {fileID: 6078297801174817557, guid: 66d4f7c006b77084da3929add6d06f24, type: 3}
|
||||||
m_PrefabInstance: {fileID: 1002930408}
|
m_PrefabInstance: {fileID: 1002930408}
|
||||||
m_PrefabAsset: {fileID: 0}
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
--- !u!1 &1007206781
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 1007206783}
|
||||||
|
- component: {fileID: 1007206782}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: AmbientMotion
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &1007206782
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1007206781}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: c5eaa0f5b116ec34695d5e4646784462, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: ProjectM.Client::ProjectM.Client.AmbientMotionConfig
|
||||||
|
Enabled: 1
|
||||||
|
DriftEnabled: 1
|
||||||
|
DriftRate: 28
|
||||||
|
DriftSize: 0.09
|
||||||
|
MeadowMoteColor: {r: 0.62, g: 0.85, b: 0.6, a: 0.35}
|
||||||
|
AridDustColor: {r: 0.85, g: 0.68, b: 0.45, a: 0.28}
|
||||||
|
CavernMoteColor: {r: 0.55, g: 0.65, b: 0.95, a: 0.33}
|
||||||
|
BlightSporeColor: {r: 0.72, g: 0.5, b: 0.85, a: 0.33}
|
||||||
|
AridWind: {x: 2.2, y: -0.15, z: 0.5}
|
||||||
|
SwayEnabled: 1
|
||||||
|
SwayRadius: 30
|
||||||
|
SwayAmpDeg: 1.4
|
||||||
|
SwayHz: 0.8
|
||||||
|
RustleEnabled: 1
|
||||||
|
RustleRadius: 1.7
|
||||||
|
RustleAmpDeg: 7
|
||||||
|
RustleHz: 9
|
||||||
|
RustleDecay: 2.5
|
||||||
|
--- !u!4 &1007206783
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1007206781}
|
||||||
|
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 &1010024963
|
--- !u!1001 &1010024963
|
||||||
PrefabInstance:
|
PrefabInstance:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -77186,6 +77248,7 @@ GameObject:
|
|||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 2045829909}
|
- component: {fileID: 2045829909}
|
||||||
- component: {fileID: 2045829910}
|
- component: {fileID: 2045829910}
|
||||||
|
- component: {fileID: 2045829911}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: CabinLight
|
m_Name: CabinLight
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -77273,6 +77336,35 @@ Light:
|
|||||||
m_LightUnit: 1
|
m_LightUnit: 1
|
||||||
m_LuxAtDistance: 1
|
m_LuxAtDistance: 1
|
||||||
m_EnableSpotReflector: 1
|
m_EnableSpotReflector: 1
|
||||||
|
--- !u!114 &2045829911
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2045829908}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData
|
||||||
|
m_UsePipelineSettings: 1
|
||||||
|
m_AdditionalLightsShadowResolutionTier: 2
|
||||||
|
m_CustomShadowLayers: 0
|
||||||
|
m_LightCookieSize: {x: 1, y: 1}
|
||||||
|
m_LightCookieOffset: {x: 0, y: 0}
|
||||||
|
m_SoftShadowQuality: 0
|
||||||
|
m_RenderingLayersMask:
|
||||||
|
serializedVersion: 0
|
||||||
|
m_Bits: 1
|
||||||
|
m_ShadowRenderingLayersMask:
|
||||||
|
serializedVersion: 0
|
||||||
|
m_Bits: 1
|
||||||
|
m_Version: 4
|
||||||
|
m_LightLayerMask: 1
|
||||||
|
m_ShadowLayerMask: 1
|
||||||
|
m_RenderingLayers: 1
|
||||||
|
m_ShadowRenderingLayers: 1
|
||||||
--- !u!1001 &2047680188
|
--- !u!1001 &2047680188
|
||||||
PrefabInstance:
|
PrefabInstance:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -81293,3 +81385,4 @@ SceneRoots:
|
|||||||
- {fileID: 1851179016}
|
- {fileID: 1851179016}
|
||||||
- {fileID: 70737666}
|
- {fileID: 70737666}
|
||||||
- {fileID: 1276835916}
|
- {fileID: 1276835916}
|
||||||
|
- {fileID: 1007206783}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ProjectM.Client
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Live-tunable knobs for <see cref="AmbientMotionSystem"/> — the Phase 1.5 "world alive" ambient layer
|
||||||
|
/// (per-biome drift particles + idle foliage sway + walk-through rustle). Mirrors the
|
||||||
|
/// <see cref="WorldAtmosphereConfig"/>/<see cref="DynamicLightConfig"/> bridge idiom: a MonoBehaviour 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.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AmbientMotionConfig : MonoBehaviour
|
||||||
|
{
|
||||||
|
public static AmbientMotionConfig Instance;
|
||||||
|
|
||||||
|
[Header("Master")]
|
||||||
|
public bool Enabled = true;
|
||||||
|
|
||||||
|
[Header("Biome drift particles (emitter follows the camera; sim space stays world)")]
|
||||||
|
public bool DriftEnabled = true;
|
||||||
|
[Min(0f)] public float DriftRate = 28f;
|
||||||
|
[Min(0.01f)] public float DriftSize = 0.09f;
|
||||||
|
public Color MeadowMoteColor = new Color(0.62f, 0.85f, 0.60f, 0.35f);
|
||||||
|
public Color AridDustColor = new Color(0.85f, 0.68f, 0.45f, 0.28f);
|
||||||
|
public Color CavernMoteColor = new Color(0.55f, 0.65f, 0.95f, 0.33f);
|
||||||
|
public Color BlightSporeColor = new Color(0.72f, 0.50f, 0.85f, 0.33f);
|
||||||
|
public Vector3 AridWind = new Vector3(2.2f, -0.15f, 0.5f);
|
||||||
|
|
||||||
|
[Header("Idle foliage sway (flora prop roots within view of the camera)")]
|
||||||
|
public bool SwayEnabled = true;
|
||||||
|
[Min(1f)] public float SwayRadius = 30f;
|
||||||
|
[Min(0f)] public float SwayAmpDeg = 1.4f;
|
||||||
|
[Min(0.05f)] public float SwayHz = 0.8f;
|
||||||
|
|
||||||
|
[Header("Walk-through rustle (local player brushing flora)")]
|
||||||
|
public bool RustleEnabled = true;
|
||||||
|
[Min(0.2f)] public float RustleRadius = 1.7f;
|
||||||
|
[Min(0f)] public float RustleAmpDeg = 7f;
|
||||||
|
[Min(0.5f)] public float RustleHz = 9f;
|
||||||
|
[Min(0.2f)] public float RustleDecay = 2.5f;
|
||||||
|
|
||||||
|
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: c5eaa0f5b116ec34695d5e4646784462
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using ProjectM.Simulation;
|
||||||
|
using Unity.Entities;
|
||||||
|
using Unity.NetCode;
|
||||||
|
using Unity.Transforms;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ProjectM.Client
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client-only AMBIENT MOTION layer (Phase 1.5 "world alive" — the anti-static beat, zero netcode):
|
||||||
|
/// - per-biome DRIFT particles (meadow motes / arid wind-blown dust / cavern motes / blight spores) from
|
||||||
|
/// one world-space emitter that follows the camera; palette keys on camera-X region + the replicated
|
||||||
|
/// per-room biome, mirroring <see cref="WorldAtmosphereSystem"/>'s switch
|
||||||
|
/// - idle SWAY on the cosmetic flora prop roots near the camera (Game.unity classic layer; probed
|
||||||
|
/// 2026-07-09: 212 flora roots, none static-batched, LOD children follow the root)
|
||||||
|
/// - walk-through RUSTLE: flora brushed by the LOCAL player (the GhostOwnerIsLocal idiom) gets an
|
||||||
|
/// energy pulse that shakes and decays
|
||||||
|
/// Observe-only <see cref="SystemBase"/> in <see cref="PresentationSystemGroup"/>; wall-clock time is fine
|
||||||
|
/// (presentation). Rotations write around a cached base so the layer is lossless when toggled off.
|
||||||
|
/// Knobs live in <see cref="AmbientMotionConfig"/> (defaults when absent).
|
||||||
|
/// </summary>
|
||||||
|
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
|
||||||
|
[UpdateInGroup(typeof(PresentationSystemGroup))]
|
||||||
|
public partial class AmbientMotionSystem : SystemBase
|
||||||
|
{
|
||||||
|
static readonly System.Text.RegularExpressions.Regex FloraName = new System.Text.RegularExpressions.Regex(
|
||||||
|
"bush|grass|flower|fern|groundcover|ground_cover|wildflower|clover|reed|succulent|cactus",
|
||||||
|
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
struct Flora
|
||||||
|
{
|
||||||
|
public Transform T;
|
||||||
|
public Vector3 Pos;
|
||||||
|
public Quaternion BaseRot;
|
||||||
|
public float Phase;
|
||||||
|
public float RustleEnergy;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly List<Flora> _flora = new List<Flora>(256);
|
||||||
|
bool _floraCached;
|
||||||
|
string _sceneKey;
|
||||||
|
Camera _cam;
|
||||||
|
GameObject _root;
|
||||||
|
ParticleSystem _drift;
|
||||||
|
int _biomeKey = -1;
|
||||||
|
|
||||||
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
RestoreFlora();
|
||||||
|
if (_root != null) Object.Destroy(_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
||||||
|
if (scene.name != "Game") { RestoreFlora(); _floraCached = false; return; }
|
||||||
|
if (_cam == null) _cam = Camera.main;
|
||||||
|
if (_cam == null) return;
|
||||||
|
|
||||||
|
var cfg = AmbientMotionConfig.Instance;
|
||||||
|
bool enabled = cfg == null || cfg.Enabled;
|
||||||
|
if (!enabled)
|
||||||
|
{
|
||||||
|
RestoreFlora();
|
||||||
|
if (_drift != null && _drift.isPlaying) _drift.Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_floraCached || _sceneKey != scene.path) CacheFlora(scene);
|
||||||
|
|
||||||
|
UpdateDrift(cfg);
|
||||||
|
UpdateSwayAndRustle(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- flora cache ----------
|
||||||
|
|
||||||
|
void CacheFlora(UnityEngine.SceneManagement.Scene scene)
|
||||||
|
{
|
||||||
|
RestoreFlora();
|
||||||
|
_flora.Clear();
|
||||||
|
foreach (var root in scene.GetRootGameObjects())
|
||||||
|
{
|
||||||
|
if (root.name != "BaseBiome" && !root.name.StartsWith("ExpeditionBiome")) continue;
|
||||||
|
foreach (var t in root.GetComponentsInChildren<Transform>(false))
|
||||||
|
{
|
||||||
|
if (!FloraName.IsMatch(t.gameObject.name)) continue;
|
||||||
|
if (t.gameObject.name.Contains("LOD")) continue; // prop roots only; LOD children follow
|
||||||
|
if (t.GetComponent<LODGroup>() == null && t.GetComponent<MeshRenderer>() == null) continue;
|
||||||
|
var p = t.position;
|
||||||
|
_flora.Add(new Flora
|
||||||
|
{
|
||||||
|
T = t,
|
||||||
|
Pos = p,
|
||||||
|
BaseRot = t.localRotation,
|
||||||
|
Phase = (p.x * 12.9898f + p.z * 78.233f) % 6.2831f,
|
||||||
|
RustleEnergy = 0f,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_floraCached = true;
|
||||||
|
_sceneKey = scene.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RestoreFlora()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _flora.Count; i++)
|
||||||
|
if (_flora[i].T != null) _flora[i].T.localRotation = _flora[i].BaseRot;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- biome drift particles ----------
|
||||||
|
|
||||||
|
void UpdateDrift(AmbientMotionConfig cfg)
|
||||||
|
{
|
||||||
|
bool driftOn = cfg == null || cfg.DriftEnabled;
|
||||||
|
if (!driftOn)
|
||||||
|
{
|
||||||
|
if (_drift != null && _drift.isPlaying) _drift.Stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_root == null)
|
||||||
|
{
|
||||||
|
_root = new GameObject("~AmbientMotion");
|
||||||
|
Object.DontDestroyOnLoad(_root);
|
||||||
|
}
|
||||||
|
if (_drift == null)
|
||||||
|
{
|
||||||
|
var mat = FeedbackFx.MakeParticleMaterial("AmbientDrift");
|
||||||
|
_drift = FeedbackFx.MakeBurst(_root.transform, "Drift", mat, Color.white,
|
||||||
|
0.09f, 0.15f, 9f, 600, gravity: 0f, shapeRadius: 1f, sizeTail: 0.9f);
|
||||||
|
var main = _drift.main;
|
||||||
|
main.simulationSpace = ParticleSystemSimulationSpace.World;
|
||||||
|
main.startLifetime = 9f;
|
||||||
|
var shape = _drift.shape;
|
||||||
|
shape.enabled = true;
|
||||||
|
shape.shapeType = ParticleSystemShapeType.Box;
|
||||||
|
shape.scale = new Vector3(36f, 7f, 24f);
|
||||||
|
var noise = _drift.noise;
|
||||||
|
noise.enabled = true;
|
||||||
|
noise.strength = 0.45f;
|
||||||
|
noise.frequency = 0.25f;
|
||||||
|
var emission = _drift.emission;
|
||||||
|
emission.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// palette key: base hub vs the replicated per-room biome (WorldAtmosphereSystem's switch shape)
|
||||||
|
int key = 0; // 0 = meadow/base
|
||||||
|
if (_cam.transform.position.x > 500f)
|
||||||
|
{
|
||||||
|
key = 1; // arid default
|
||||||
|
if (SystemAPI.TryGetSingleton<RunInfo>(out var ri) && ri.Lifecycle != RunLifecycle.Staging)
|
||||||
|
{
|
||||||
|
switch (ri.CurrentBiome)
|
||||||
|
{
|
||||||
|
case RoomBiomeId.Meadow: key = 0; break;
|
||||||
|
case RoomBiomeId.Cavern: key = 2; break;
|
||||||
|
case RoomBiomeId.Blight: key = 3; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key != _biomeKey)
|
||||||
|
{
|
||||||
|
_biomeKey = key;
|
||||||
|
var main = _drift.main;
|
||||||
|
main.startSize = cfg != null ? cfg.DriftSize : 0.09f;
|
||||||
|
Color c;
|
||||||
|
Vector3 wind;
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case 1: c = cfg != null ? cfg.AridDustColor : new Color(0.85f, 0.68f, 0.45f, 0.28f); wind = cfg != null ? cfg.AridWind : new Vector3(2.2f, -0.15f, 0.5f); break;
|
||||||
|
case 2: c = cfg != null ? cfg.CavernMoteColor : new Color(0.55f, 0.65f, 0.95f, 0.33f); wind = new Vector3(0f, 0.18f, 0f); break;
|
||||||
|
case 3: c = cfg != null ? cfg.BlightSporeColor : new Color(0.72f, 0.50f, 0.85f, 0.33f); wind = new Vector3(0f, 0.28f, 0f); break;
|
||||||
|
default: c = cfg != null ? cfg.MeadowMoteColor : new Color(0.62f, 0.85f, 0.60f, 0.35f); wind = new Vector3(0f, 0.22f, 0f); break;
|
||||||
|
}
|
||||||
|
main.startColor = c;
|
||||||
|
var vel = _drift.velocityOverLifetime;
|
||||||
|
vel.enabled = true;
|
||||||
|
vel.space = ParticleSystemSimulationSpace.World;
|
||||||
|
vel.x = wind.x; vel.y = wind.y; vel.z = wind.z;
|
||||||
|
var emission = _drift.emission;
|
||||||
|
emission.rateOverTime = cfg != null ? cfg.DriftRate : 28f;
|
||||||
|
}
|
||||||
|
if (!_drift.isPlaying) _drift.Play();
|
||||||
|
// follow the camera's ground focus so the volume always blankets the view
|
||||||
|
var camPos = _cam.transform.position;
|
||||||
|
_drift.transform.position = new Vector3(camPos.x, 3.5f, camPos.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- sway + rustle ----------
|
||||||
|
|
||||||
|
void UpdateSwayAndRustle(AmbientMotionConfig cfg)
|
||||||
|
{
|
||||||
|
bool swayOn = cfg == null || cfg.SwayEnabled;
|
||||||
|
bool rustleOn = cfg == null || cfg.RustleEnabled;
|
||||||
|
if (!swayOn && !rustleOn) { RestoreFlora(); return; }
|
||||||
|
|
||||||
|
float swayRadius = cfg != null ? cfg.SwayRadius : 30f;
|
||||||
|
float swayAmp = cfg != null ? cfg.SwayAmpDeg : 1.4f;
|
||||||
|
float swayHz = cfg != null ? cfg.SwayHz : 0.8f;
|
||||||
|
float rustleRadius = cfg != null ? cfg.RustleRadius : 1.7f;
|
||||||
|
float rustleAmp = cfg != null ? cfg.RustleAmpDeg : 7f;
|
||||||
|
float rustleHz = cfg != null ? cfg.RustleHz : 9f;
|
||||||
|
float rustleDecay = cfg != null ? cfg.RustleDecay : 2.5f;
|
||||||
|
|
||||||
|
// local player position (rustle source); missing (menu/no ghost yet) -> far away
|
||||||
|
Vector3 playerPos = new Vector3(1e9f, 0f, 1e9f);
|
||||||
|
foreach (var lt in SystemAPI.Query<RefRO<LocalTransform>>().WithAll<GhostOwnerIsLocal, PlayerTag>())
|
||||||
|
playerPos = new Vector3(lt.ValueRO.Position.x, 0f, lt.ValueRO.Position.z);
|
||||||
|
|
||||||
|
float t = UnityEngine.Time.time;
|
||||||
|
float dt = UnityEngine.Time.deltaTime;
|
||||||
|
var camPos = _cam.transform.position;
|
||||||
|
float swayR2 = swayRadius * swayRadius;
|
||||||
|
float rustleR2 = rustleRadius * rustleRadius;
|
||||||
|
|
||||||
|
for (int i = 0; i < _flora.Count; i++)
|
||||||
|
{
|
||||||
|
var f = _flora[i];
|
||||||
|
if (f.T == null) continue;
|
||||||
|
|
||||||
|
float dxc = f.Pos.x - camPos.x, dzc = f.Pos.z - camPos.z;
|
||||||
|
bool nearCam = dxc * dxc + dzc * dzc < swayR2;
|
||||||
|
|
||||||
|
if (rustleOn)
|
||||||
|
{
|
||||||
|
float dxp = f.Pos.x - playerPos.x, dzp = f.Pos.z - playerPos.z;
|
||||||
|
if (dxp * dxp + dzp * dzp < rustleR2) f.RustleEnergy = 1f;
|
||||||
|
else if (f.RustleEnergy > 0f) f.RustleEnergy = Mathf.Max(0f, f.RustleEnergy - rustleDecay * dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nearCam && f.RustleEnergy <= 0f)
|
||||||
|
{
|
||||||
|
_flora[i] = f;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float ax = 0f, az = 0f;
|
||||||
|
if (swayOn && nearCam)
|
||||||
|
{
|
||||||
|
ax += swayAmp * Mathf.Sin(t * swayHz * 6.2831f + f.Phase);
|
||||||
|
az += swayAmp * 0.8f * Mathf.Sin(t * swayHz * 5.5f + f.Phase * 1.7f);
|
||||||
|
}
|
||||||
|
if (f.RustleEnergy > 0f)
|
||||||
|
{
|
||||||
|
float e = f.RustleEnergy;
|
||||||
|
ax += rustleAmp * e * Mathf.Sin(t * rustleHz * 6.2831f + f.Phase);
|
||||||
|
az += rustleAmp * 0.7f * e * Mathf.Sin(t * rustleHz * 5.1f + f.Phase * 2.3f);
|
||||||
|
}
|
||||||
|
f.T.localRotation = f.BaseRot * Quaternion.Euler(ax, 0f, az);
|
||||||
|
_flora[i] = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a4a8e8f6d3c4d95428cf227b39f5fc30
|
||||||
@@ -28,16 +28,16 @@ Material:
|
|||||||
- _Brightness: 1.1
|
- _Brightness: 1.1
|
||||||
- _ColorLevels: 12
|
- _ColorLevels: 12
|
||||||
- _Contrast: 1.05
|
- _Contrast: 1.05
|
||||||
- _DepthEdgesEnabled: 0
|
- _DepthEdgesEnabled: 1
|
||||||
- _DepthThreshold: 0.08
|
- _DepthThreshold: 0.08
|
||||||
- _EdgeStrength: 1
|
- _EdgeStrength: 1
|
||||||
- _MasterEnabled: 1
|
- _MasterEnabled: 0
|
||||||
- _NormalEdgesEnabled: 0
|
- _NormalEdgesEnabled: 1
|
||||||
- _NormalThreshold: 1
|
- _NormalThreshold: 1
|
||||||
- _OutlineThickness: 1
|
- _OutlineThickness: 1
|
||||||
- _PixelHeight: 200
|
- _PixelHeight: 399.82455
|
||||||
- _PixelateEnabled: 0
|
- _PixelateEnabled: 1
|
||||||
- _PosterizeEnabled: 0
|
- _PosterizeEnabled: 1
|
||||||
- _Saturation: 1.35
|
- _Saturation: 1.35
|
||||||
m_Colors:
|
m_Colors:
|
||||||
- _OutlineColorInner: {r: 0, g: 0, b: 0, a: 1}
|
- _OutlineColorInner: {r: 0, g: 0, b: 0, a: 1}
|
||||||
|
|||||||
Reference in New Issue
Block a user