Hygiene B4a: extract shared FeedbackFx (dedup 4 procedural-FX copies)
New FeedbackFx static (MakeClip/MakeParticleMaterial/MakeBurst/PlayClip/EmitTinted/EmitAt); the 3 *FeedbackSystem copies + AmbientAudio.MakeSting now route through it via 'using static'. MakeBurst takes the FX-root + the per-use gravity/radius/sizeTail that were the only diffs between copies; MakeClip folds in noise + decay. Behaviour-identical by construction (every particle/clip param preserved exactly). 459/459 EditMode tests pass; compiles clean. VFX are presentation-only (no EditMode coverage) -> wants a Play-mode smoke to eyeball hit/death/harvest/structure bursts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -176,23 +176,6 @@ namespace ProjectM.Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Short one-shot tone sweeping f0->f1 with an exponential decay envelope.
|
// Short one-shot tone sweeping f0->f1 with an exponential decay envelope.
|
||||||
static AudioClip MakeSting(float f0, float f1, float dur, float vol)
|
static AudioClip MakeSting(float f0, float f1, float dur, float vol) => FeedbackFx.MakeClip("sting", f0, f1, dur, vol, decay: 3.5f);
|
||||||
{
|
|
||||||
const int rate = 44100;
|
|
||||||
int len = Mathf.Max(16, (int)(dur * rate));
|
|
||||||
var clip = AudioClip.Create("sting", len, 1, rate, false);
|
|
||||||
var data = new float[len];
|
|
||||||
float phase = 0f;
|
|
||||||
for (int i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
float t = i / (float)len;
|
|
||||||
float env = Mathf.Exp(-3.5f * t);
|
|
||||||
float freq = Mathf.Lerp(f0, f1, t);
|
|
||||||
phase += 2f * Mathf.PI * freq / rate;
|
|
||||||
data[i] = Mathf.Sin(phase) * env * vol;
|
|
||||||
}
|
|
||||||
clip.SetData(data, 0);
|
|
||||||
return clip;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Unity.Mathematics;
|
|||||||
using Unity.NetCode;
|
using Unity.NetCode;
|
||||||
using Unity.Transforms;
|
using Unity.Transforms;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using static ProjectM.Client.FeedbackFx;
|
||||||
|
|
||||||
namespace ProjectM.Client
|
namespace ProjectM.Client
|
||||||
{
|
{
|
||||||
@@ -143,11 +144,11 @@ namespace ProjectM.Client
|
|||||||
|
|
||||||
_fxRoot = new GameObject("~CombatFeedbackFX").transform;
|
_fxRoot = new GameObject("~CombatFeedbackFX").transform;
|
||||||
var mat = MakeParticleMaterial();
|
var mat = MakeParticleMaterial();
|
||||||
_hitFx = MakeBurst("HitSparks", mat, new Color(3f, 2.2f, 0.6f), 0.13f, 7f, 0.32f, 256);
|
_hitFx = MakeBurst(_fxRoot, "HitSparks", mat, new Color(3f, 2.2f, 0.6f), 0.13f, 7f, 0.32f, 256);
|
||||||
_deathFx = MakeBurst("DeathBurst", mat, new Color(3.2f, 0.7f, 0.25f), 0.22f, 9f, 0.55f, 512);
|
_deathFx = MakeBurst(_fxRoot, "DeathBurst", mat, new Color(3.2f, 0.7f, 0.25f), 0.22f, 9f, 0.55f, 512);
|
||||||
_muzzleFx = MakeBurst("Muzzle", mat, new Color(0.6f, 2.4f, 3.2f), 0.12f, 5f, 0.20f, 128);
|
_muzzleFx = MakeBurst(_fxRoot, "Muzzle", mat, new Color(0.6f, 2.4f, 3.2f), 0.12f, 5f, 0.20f, 128);
|
||||||
_dashFx = MakeBurst("DashWhoosh", mat, new Color(0.7f, 2.6f, 3.0f), 0.16f, 4f, 0.30f, 256);
|
_dashFx = MakeBurst(_fxRoot, "DashWhoosh", mat, new Color(0.7f, 2.6f, 3.0f), 0.16f, 4f, 0.30f, 256);
|
||||||
_swingFx = MakeBurst("MeleeSwing", mat, new Color(3.0f, 2.6f, 0.9f), 0.14f, 6f, 0.28f, 256);
|
_swingFx = MakeBurst(_fxRoot, "MeleeSwing", mat, new Color(3.0f, 2.6f, 0.9f), 0.14f, 6f, 0.28f, 256);
|
||||||
BuildSlash();
|
BuildSlash();
|
||||||
_dangerMat = MakeParticleMaterial();
|
_dangerMat = MakeParticleMaterial();
|
||||||
_dangerMat.name = "EnemyDanger";
|
_dangerMat.name = "EnemyDanger";
|
||||||
@@ -748,80 +749,11 @@ namespace ProjectM.Client
|
|||||||
|
|
||||||
// ---- Procedural SFX + pooled particle bursts (fallback when no authored prefab) ----
|
// ---- Procedural SFX + pooled particle bursts (fallback when no authored prefab) ----
|
||||||
|
|
||||||
static AudioClip MakeClip(string name, float f0, float f1, float dur, float vol, bool noise)
|
|
||||||
{
|
|
||||||
const int rate = 44100;
|
|
||||||
int len = Mathf.Max(16, (int)(dur * rate));
|
|
||||||
var clip = AudioClip.Create(name, len, 1, rate, false);
|
|
||||||
var data = new float[len];
|
|
||||||
var rng = new System.Random(name.Length * 9973 + 7);
|
|
||||||
float phase = 0f;
|
|
||||||
for (int i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
float t = i / (float)len;
|
|
||||||
float env = Mathf.Exp(-5f * t);
|
|
||||||
float freq = Mathf.Lerp(f0, f1, t);
|
|
||||||
phase += 2f * Mathf.PI * freq / rate;
|
|
||||||
float s = noise ? (float)(rng.NextDouble() * 2.0 - 1.0) : Mathf.Sin(phase);
|
|
||||||
data[i] = s * env * vol;
|
|
||||||
}
|
|
||||||
clip.SetData(data, 0);
|
|
||||||
return clip;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Material MakeParticleMaterial()
|
|
||||||
{
|
|
||||||
// Sprites/Default is an always-included, transparent, vertex-coloured shader — reliable for
|
|
||||||
// billboarded sparks; HDR start colours still push past the bloom threshold (Stage 5 look pass).
|
|
||||||
Shader sh = Shader.Find("Sprites/Default");
|
|
||||||
if (sh == null) sh = Shader.Find("Universal Render Pipeline/Particles/Unlit");
|
|
||||||
if (sh == null) sh = Shader.Find("Unlit/Color");
|
|
||||||
return new Material(sh) { name = "CombatFeedbackParticle" };
|
|
||||||
}
|
|
||||||
|
|
||||||
ParticleSystem MakeBurst(string name, Material mat, Color color, float size, float speed, float life, int max)
|
|
||||||
{
|
|
||||||
var go = new GameObject(name);
|
|
||||||
go.transform.SetParent(_fxRoot, false);
|
|
||||||
var ps = go.AddComponent<ParticleSystem>();
|
|
||||||
|
|
||||||
var main = ps.main;
|
|
||||||
main.loop = false;
|
|
||||||
main.playOnAwake = false;
|
|
||||||
// duration unused: manual Emit bursts with emission disabled
|
|
||||||
main.startLifetime = life;
|
|
||||||
main.startSpeed = speed;
|
|
||||||
main.startSize = size;
|
|
||||||
main.startColor = color;
|
|
||||||
main.maxParticles = max;
|
|
||||||
main.gravityModifier = 0f;
|
|
||||||
main.simulationSpace = ParticleSystemSimulationSpace.World;
|
|
||||||
|
|
||||||
var emission = ps.emission;
|
|
||||||
emission.enabled = false; // we Emit(count) manually
|
|
||||||
|
|
||||||
var shape = ps.shape;
|
|
||||||
shape.enabled = true;
|
|
||||||
shape.shapeType = ParticleSystemShapeType.Sphere;
|
|
||||||
shape.radius = 0.06f;
|
|
||||||
|
|
||||||
var colOverLife = ps.colorOverLifetime;
|
|
||||||
colOverLife.enabled = true;
|
|
||||||
var grad = new Gradient();
|
|
||||||
grad.SetKeys(
|
|
||||||
new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
|
|
||||||
new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0f, 1f) });
|
|
||||||
colOverLife.color = new ParticleSystem.MinMaxGradient(grad);
|
|
||||||
|
|
||||||
var sizeOverLife = ps.sizeOverLifetime;
|
|
||||||
sizeOverLife.enabled = true;
|
|
||||||
sizeOverLife.size = new ParticleSystem.MinMaxCurve(1f, AnimationCurve.Linear(0f, 1f, 1f, 0.2f));
|
|
||||||
|
|
||||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
|
||||||
renderer.material = mat;
|
|
||||||
renderer.renderMode = ParticleSystemRenderMode.Billboard;
|
|
||||||
return ps;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BuildSlash()
|
void BuildSlash()
|
||||||
{
|
{
|
||||||
@@ -1363,17 +1295,5 @@ void TriggerSlash(Vector3 pos, float2 facing, float range, float halfAngle, int
|
|||||||
mesh.RecalculateBounds();
|
mesh.RecalculateBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void EmitAt(ParticleSystem ps, Vector3 pos, int count)
|
|
||||||
{
|
|
||||||
if (ps == null) return;
|
|
||||||
ps.transform.position = pos;
|
|
||||||
ps.Emit(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PlayClip(AudioClip clip, Vector3 pos, float vol)
|
|
||||||
{
|
|
||||||
if (clip == null) return;
|
|
||||||
AudioSource.PlayClipAtPoint(clip, pos, vol * GameVolume.Sfx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace ProjectM.Client
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shared asset-free procedural-FX toolkit for the client presentation FEEDBACK systems (Combat / Structure /
|
||||||
|
/// World / Ambient), which previously each carried a near-identical private copy. Pure UnityEngine (no ECS), so
|
||||||
|
/// it stays a plain static helper. MakeBurst takes the owning FX-root transform + the per-use gravity / shape
|
||||||
|
/// radius / size-tail that were the ONLY real differences between the old copies (Combat 0/0.06/0.2,
|
||||||
|
/// Structure 0.3/0.18/0.15, World 0.25/0.10/0.2). MakeClip folds in the noise + envelope-decay knobs that were
|
||||||
|
/// the only differences between the CombatFeedback MakeClip and the AmbientAudio MakeSting.
|
||||||
|
/// </summary>
|
||||||
|
public static class FeedbackFx
|
||||||
|
{
|
||||||
|
public static AudioClip MakeClip(string name, float f0, float f1, float dur, float vol, bool noise = false, float decay = 5f)
|
||||||
|
{
|
||||||
|
const int rate = 44100;
|
||||||
|
int len = Mathf.Max(16, (int)(dur * rate));
|
||||||
|
var clip = AudioClip.Create(name, len, 1, rate, false);
|
||||||
|
var data = new float[len];
|
||||||
|
var rng = new System.Random(name.Length * 9973 + 7);
|
||||||
|
float phase = 0f;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
float t = i / (float)len;
|
||||||
|
float env = Mathf.Exp(-decay * t);
|
||||||
|
float freq = Mathf.Lerp(f0, f1, t);
|
||||||
|
phase += 2f * Mathf.PI * freq / rate;
|
||||||
|
float s = noise ? (float)(rng.NextDouble() * 2.0 - 1.0) : Mathf.Sin(phase);
|
||||||
|
data[i] = s * env * vol;
|
||||||
|
}
|
||||||
|
clip.SetData(data, 0);
|
||||||
|
return clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Material MakeParticleMaterial(string name = "FeedbackParticle")
|
||||||
|
{
|
||||||
|
// Sprites/Default is an always-included transparent vertex-coloured shader (reliable billboarded sparks;
|
||||||
|
// HDR start colours still push past the bloom threshold); fall back through URP unlit / Unlit/Color.
|
||||||
|
Shader sh = Shader.Find("Sprites/Default");
|
||||||
|
if (sh == null) sh = Shader.Find("Universal Render Pipeline/Particles/Unlit");
|
||||||
|
if (sh == null) sh = Shader.Find("Unlit/Color");
|
||||||
|
return new Material(sh) { name = name };
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParticleSystem MakeBurst(Transform parent, string name, Material mat, Color color,
|
||||||
|
float size, float speed, float life, int max,
|
||||||
|
float gravity = 0f, float shapeRadius = 0.06f, float sizeTail = 0.2f)
|
||||||
|
{
|
||||||
|
var go = new GameObject(name);
|
||||||
|
go.transform.SetParent(parent, false);
|
||||||
|
var ps = go.AddComponent<ParticleSystem>();
|
||||||
|
|
||||||
|
var main = ps.main;
|
||||||
|
main.loop = false;
|
||||||
|
main.playOnAwake = false;
|
||||||
|
main.startLifetime = life;
|
||||||
|
main.startSpeed = speed;
|
||||||
|
main.startSize = size;
|
||||||
|
main.startColor = color;
|
||||||
|
main.maxParticles = max;
|
||||||
|
main.gravityModifier = gravity;
|
||||||
|
main.simulationSpace = ParticleSystemSimulationSpace.World;
|
||||||
|
|
||||||
|
var emission = ps.emission;
|
||||||
|
emission.enabled = false; // manual Emit(count)
|
||||||
|
|
||||||
|
var shape = ps.shape;
|
||||||
|
shape.enabled = true;
|
||||||
|
shape.shapeType = ParticleSystemShapeType.Sphere;
|
||||||
|
shape.radius = shapeRadius;
|
||||||
|
|
||||||
|
var colOverLife = ps.colorOverLifetime;
|
||||||
|
colOverLife.enabled = true;
|
||||||
|
var grad = new Gradient();
|
||||||
|
grad.SetKeys(
|
||||||
|
new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
|
||||||
|
new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0f, 1f) });
|
||||||
|
colOverLife.color = new ParticleSystem.MinMaxGradient(grad);
|
||||||
|
|
||||||
|
var sizeOverLife = ps.sizeOverLifetime;
|
||||||
|
sizeOverLife.enabled = true;
|
||||||
|
sizeOverLife.size = new ParticleSystem.MinMaxCurve(1f, AnimationCurve.Linear(0f, 1f, 1f, sizeTail));
|
||||||
|
|
||||||
|
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||||
|
renderer.material = mat;
|
||||||
|
renderer.renderMode = ParticleSystemRenderMode.Billboard;
|
||||||
|
return ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EmitTinted(ParticleSystem ps, Vector3 pos, int count, Color tint)
|
||||||
|
{
|
||||||
|
if (ps == null) return;
|
||||||
|
var main = ps.main;
|
||||||
|
main.startColor = tint;
|
||||||
|
ps.transform.position = pos;
|
||||||
|
ps.Emit(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EmitAt(ParticleSystem ps, Vector3 pos, int count)
|
||||||
|
{
|
||||||
|
if (ps == null) return;
|
||||||
|
ps.transform.position = pos;
|
||||||
|
ps.Emit(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PlayClip(AudioClip clip, Vector3 pos, float vol)
|
||||||
|
{
|
||||||
|
if (clip == null) return;
|
||||||
|
AudioSource.PlayClipAtPoint(clip, pos, vol * GameVolume.Sfx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 09c134aba1e7696478a68382239ec4dc
|
||||||
@@ -5,6 +5,7 @@ using Unity.Mathematics;
|
|||||||
using Unity.NetCode;
|
using Unity.NetCode;
|
||||||
using Unity.Transforms;
|
using Unity.Transforms;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using static ProjectM.Client.FeedbackFx;
|
||||||
|
|
||||||
namespace ProjectM.Client
|
namespace ProjectM.Client
|
||||||
{
|
{
|
||||||
@@ -49,8 +50,8 @@ namespace ProjectM.Client
|
|||||||
if (_fxRoot != null) return;
|
if (_fxRoot != null) return;
|
||||||
_fxRoot = new GameObject("~StructureFeedbackFX").transform;
|
_fxRoot = new GameObject("~StructureFeedbackFX").transform;
|
||||||
var mat = MakeParticleMaterial();
|
var mat = MakeParticleMaterial();
|
||||||
_chipFx = MakeBurst("StructChips", mat, StructureFeelConfig.DamageTint, 0.12f, 5f, 0.30f, 256);
|
_chipFx = MakeBurst(_fxRoot, "StructChips", mat, StructureFeelConfig.DamageTint, 0.12f, 5f, 0.30f, 256, 0.3f, 0.18f, 0.15f);
|
||||||
_deathFx = MakeBurst("StructDeath", mat, StructureFeelConfig.DeathTint, 0.20f, 8f, 0.55f, 512);
|
_deathFx = MakeBurst(_fxRoot, "StructDeath", mat, StructureFeelConfig.DeathTint, 0.20f, 8f, 0.55f, 512, 0.3f, 0.18f, 0.15f);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
@@ -128,89 +129,14 @@ namespace ProjectM.Client
|
|||||||
|
|
||||||
// ---- procedural particles + SFX (mirrors WorldFeedbackSystem; self-contained) ----
|
// ---- procedural particles + SFX (mirrors WorldFeedbackSystem; self-contained) ----
|
||||||
|
|
||||||
static void EmitTinted(ParticleSystem ps, Vector3 pos, int count, Color tint)
|
|
||||||
{
|
|
||||||
if (ps == null) return;
|
|
||||||
var main = ps.main;
|
|
||||||
main.startColor = tint;
|
|
||||||
ps.transform.position = pos;
|
|
||||||
ps.Emit(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Material MakeParticleMaterial()
|
|
||||||
{
|
|
||||||
Shader sh = Shader.Find("Sprites/Default");
|
|
||||||
if (sh == null) sh = Shader.Find("Universal Render Pipeline/Particles/Unlit");
|
|
||||||
if (sh == null) sh = Shader.Find("Unlit/Color");
|
|
||||||
return new Material(sh) { name = "StructureFeedbackParticle" };
|
|
||||||
}
|
|
||||||
|
|
||||||
ParticleSystem MakeBurst(string name, Material mat, Color color, float size, float speed, float life, int max)
|
|
||||||
{
|
|
||||||
var go = new GameObject(name);
|
|
||||||
go.transform.SetParent(_fxRoot, false);
|
|
||||||
var ps = go.AddComponent<ParticleSystem>();
|
|
||||||
|
|
||||||
var main = ps.main;
|
|
||||||
main.loop = false;
|
|
||||||
main.playOnAwake = false;
|
|
||||||
main.startLifetime = life;
|
|
||||||
main.startSpeed = speed;
|
|
||||||
main.startSize = size;
|
|
||||||
main.startColor = color;
|
|
||||||
main.maxParticles = max;
|
|
||||||
main.gravityModifier = 0.3f;
|
|
||||||
main.simulationSpace = ParticleSystemSimulationSpace.World;
|
|
||||||
|
|
||||||
var emission = ps.emission;
|
|
||||||
emission.enabled = false; // manual Emit(count)
|
|
||||||
|
|
||||||
var shape = ps.shape;
|
|
||||||
shape.enabled = true;
|
|
||||||
shape.shapeType = ParticleSystemShapeType.Sphere;
|
|
||||||
shape.radius = 0.18f;
|
|
||||||
|
|
||||||
var colOverLife = ps.colorOverLifetime;
|
|
||||||
colOverLife.enabled = true;
|
|
||||||
var grad = new Gradient();
|
|
||||||
grad.SetKeys(
|
|
||||||
new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
|
|
||||||
new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0f, 1f) });
|
|
||||||
colOverLife.color = new ParticleSystem.MinMaxGradient(grad);
|
|
||||||
|
|
||||||
var sizeOverLife = ps.sizeOverLifetime;
|
|
||||||
sizeOverLife.enabled = true;
|
|
||||||
sizeOverLife.size = new ParticleSystem.MinMaxCurve(1f, AnimationCurve.Linear(0f, 1f, 1f, 0.15f));
|
|
||||||
|
|
||||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
|
||||||
renderer.material = mat;
|
|
||||||
renderer.renderMode = ParticleSystemRenderMode.Billboard;
|
|
||||||
return ps;
|
|
||||||
}
|
|
||||||
|
|
||||||
static AudioClip MakeClip(string name, float f0, float f1, float dur, float vol)
|
|
||||||
{
|
|
||||||
const int rate = 44100;
|
|
||||||
int len = Mathf.Max(16, (int)(dur * rate));
|
|
||||||
var clip = AudioClip.Create(name, len, 1, rate, false);
|
|
||||||
var data = new float[len];
|
|
||||||
float phase = 0f;
|
|
||||||
for (int i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
float t = i / (float)len;
|
|
||||||
float env = Mathf.Exp(-5f * t);
|
|
||||||
float freq = Mathf.Lerp(f0, f1, t);
|
|
||||||
phase += 2f * Mathf.PI * freq / rate;
|
|
||||||
data[i] = Mathf.Sin(phase) * env * vol;
|
|
||||||
}
|
|
||||||
clip.SetData(data, 0);
|
|
||||||
return clip;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PlayClip(AudioClip clip, Vector3 pos, float vol)
|
|
||||||
{
|
|
||||||
if (clip == null) return;
|
|
||||||
AudioSource.PlayClipAtPoint(clip, pos, vol * GameVolume.Sfx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Unity.Mathematics;
|
|||||||
using Unity.NetCode;
|
using Unity.NetCode;
|
||||||
using Unity.Transforms;
|
using Unity.Transforms;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using static ProjectM.Client.FeedbackFx;
|
||||||
|
|
||||||
namespace ProjectM.Client
|
namespace ProjectM.Client
|
||||||
{
|
{
|
||||||
@@ -47,8 +48,8 @@ namespace ProjectM.Client
|
|||||||
if (_fxRoot != null) return;
|
if (_fxRoot != null) return;
|
||||||
_fxRoot = new GameObject("~WorldFeedbackFX").transform;
|
_fxRoot = new GameObject("~WorldFeedbackFX").transform;
|
||||||
var mat = MakeParticleMaterial();
|
var mat = MakeParticleMaterial();
|
||||||
_chipFx = MakeBurst("HarvestChips", mat, new Color(2.6f, 1.9f, 0.7f), 0.10f, 5f, 0.30f, 256);
|
_chipFx = MakeBurst(_fxRoot, "HarvestChips", mat, new Color(2.6f, 1.9f, 0.7f), 0.10f, 5f, 0.30f, 256, 0.25f, 0.10f, 0.2f);
|
||||||
_clearFx = MakeBurst("ClutterClear", mat, new Color(3.0f, 1.1f, 0.25f), 0.16f, 7f, 0.45f, 512);
|
_clearFx = MakeBurst(_fxRoot, "ClutterClear", mat, new Color(3.0f, 1.1f, 0.25f), 0.16f, 7f, 0.45f, 512, 0.25f, 0.10f, 0.2f);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
@@ -143,89 +144,14 @@ void Observe(Entity e, int remaining, float3 pos, bool isClutter, Color tint, bo
|
|||||||
|
|
||||||
// ---- procedural particles + SFX (mirrors CombatFeedbackSystem; self-contained) ----
|
// ---- procedural particles + SFX (mirrors CombatFeedbackSystem; self-contained) ----
|
||||||
|
|
||||||
static void EmitTinted(ParticleSystem ps, Vector3 pos, int count, Color tint)
|
|
||||||
{
|
|
||||||
if (ps == null) return;
|
|
||||||
var main = ps.main;
|
|
||||||
main.startColor = tint;
|
|
||||||
ps.transform.position = pos;
|
|
||||||
ps.Emit(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Material MakeParticleMaterial()
|
|
||||||
{
|
|
||||||
Shader sh = Shader.Find("Sprites/Default");
|
|
||||||
if (sh == null) sh = Shader.Find("Universal Render Pipeline/Particles/Unlit");
|
|
||||||
if (sh == null) sh = Shader.Find("Unlit/Color");
|
|
||||||
return new Material(sh) { name = "WorldFeedbackParticle" };
|
|
||||||
}
|
|
||||||
|
|
||||||
ParticleSystem MakeBurst(string name, Material mat, Color color, float size, float speed, float life, int max)
|
|
||||||
{
|
|
||||||
var go = new GameObject(name);
|
|
||||||
go.transform.SetParent(_fxRoot, false);
|
|
||||||
var ps = go.AddComponent<ParticleSystem>();
|
|
||||||
|
|
||||||
var main = ps.main;
|
|
||||||
main.loop = false;
|
|
||||||
main.playOnAwake = false;
|
|
||||||
main.startLifetime = life;
|
|
||||||
main.startSpeed = speed;
|
|
||||||
main.startSize = size;
|
|
||||||
main.startColor = color;
|
|
||||||
main.maxParticles = max;
|
|
||||||
main.gravityModifier = 0.25f;
|
|
||||||
main.simulationSpace = ParticleSystemSimulationSpace.World;
|
|
||||||
|
|
||||||
var emission = ps.emission;
|
|
||||||
emission.enabled = false; // manual Emit(count)
|
|
||||||
|
|
||||||
var shape = ps.shape;
|
|
||||||
shape.enabled = true;
|
|
||||||
shape.shapeType = ParticleSystemShapeType.Sphere;
|
|
||||||
shape.radius = 0.10f;
|
|
||||||
|
|
||||||
var colOverLife = ps.colorOverLifetime;
|
|
||||||
colOverLife.enabled = true;
|
|
||||||
var grad = new Gradient();
|
|
||||||
grad.SetKeys(
|
|
||||||
new[] { new GradientColorKey(Color.white, 0f), new GradientColorKey(Color.white, 1f) },
|
|
||||||
new[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(0f, 1f) });
|
|
||||||
colOverLife.color = new ParticleSystem.MinMaxGradient(grad);
|
|
||||||
|
|
||||||
var sizeOverLife = ps.sizeOverLifetime;
|
|
||||||
sizeOverLife.enabled = true;
|
|
||||||
sizeOverLife.size = new ParticleSystem.MinMaxCurve(1f, AnimationCurve.Linear(0f, 1f, 1f, 0.2f));
|
|
||||||
|
|
||||||
var renderer = ps.GetComponent<ParticleSystemRenderer>();
|
|
||||||
renderer.material = mat;
|
|
||||||
renderer.renderMode = ParticleSystemRenderMode.Billboard;
|
|
||||||
return ps;
|
|
||||||
}
|
|
||||||
|
|
||||||
static AudioClip MakeClip(string name, float f0, float f1, float dur, float vol)
|
|
||||||
{
|
|
||||||
const int rate = 44100;
|
|
||||||
int len = Mathf.Max(16, (int)(dur * rate));
|
|
||||||
var clip = AudioClip.Create(name, len, 1, rate, false);
|
|
||||||
var data = new float[len];
|
|
||||||
float phase = 0f;
|
|
||||||
for (int i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
float t = i / (float)len;
|
|
||||||
float env = Mathf.Exp(-5f * t);
|
|
||||||
float freq = Mathf.Lerp(f0, f1, t);
|
|
||||||
phase += 2f * Mathf.PI * freq / rate;
|
|
||||||
data[i] = Mathf.Sin(phase) * env * vol;
|
|
||||||
}
|
|
||||||
clip.SetData(data, 0);
|
|
||||||
return clip;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PlayClip(AudioClip clip, Vector3 pos, float vol)
|
|
||||||
{
|
|
||||||
if (clip == null) return;
|
|
||||||
AudioSource.PlayClipAtPoint(clip, pos, vol * GameVolume.Sfx);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user