Files
aibis-dream/Assets/Scripts/MiniGame/HuoShan/SalesSystem/GlowTubeEffect.cs
T

332 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
namespace AibisDream.MiniGame.HuoShan
{
/// <summary>
/// 辉光管粒子效果 — 在指定区域内用粒子模拟等离子体发光
/// 移植自 Web_EmotionWave glow-tube 分支的渲染逻辑
/// 由 KnobController 通过 SetIntensity / TriggerPulse 驱动
/// </summary>
public class GlowTubeEffect : MonoBehaviour
{
[Header("区域")]
[Tooltip("效果区域参考(取其 SpriteRenderer bounds),不填则用自身")]
[SerializeField] private SpriteRenderer areaReference;
[Header("粒子")]
[Tooltip("粒子数量")]
[SerializeField] private int particleCount = 80;
[Tooltip("粒子基础大小")]
[SerializeField] private float particleSize = 0.06f;
[Tooltip("粒子材质(Additive 发光),不赋值则自动创建")]
[SerializeField] private Material particleMaterial;
[Tooltip("粒子纹理(软圆),不赋值则自动生成")]
[SerializeField] private Texture2D particleTexture;
[Header("Sorting")]
[CustomSortingLayer]
[SerializeField] private int sortingLayerId;
[SerializeField] private int sortingOrder = 55;
[Header("颜色")]
[SerializeField] private Color lowColor = new Color(0.31f, 0.08f, 0.16f, 1f);
[SerializeField] private Color midColor = new Color(1f, 0.47f, 0.31f, 1f);
[SerializeField] private Color highColor = new Color(1f, 0.78f, 0.67f, 1f);
[Header("闪烁")]
[SerializeField] private float flickerRate = 8f;
[SerializeField] private float flickerAmplitude = 0.15f;
[Header("噪声流动")]
[SerializeField] private float noiseScale = 3f;
[SerializeField] private float noiseSpeed = 1.2f;
private ParticleSystem _ps;
private ParticleSystem.Particle[] _particles;
private float[] _phases;
private Material _matInstance;
private Texture2D _generatedTex;
private float _intensity = 1f;
private float _resistancePulse;
private float _nextPulseTime;
private float _time;
private float _seedX;
private float _seedY;
private Vector3 _areaCenter;
private Vector2 _areaHalfSize;
/// <summary>当前显示强度(含脉冲)</summary>
public float DisplayIntensity { get; private set; }
#region Lifecycle
private void Awake()
{
_seedX = Random.Range(0f, 1000f);
_seedY = Random.Range(0f, 1000f);
CacheAreaBounds();
EnsureParticleSystem();
}
private void OnDestroy()
{
if (_matInstance != null) Destroy(_matInstance);
if (_generatedTex != null) Destroy(_generatedTex);
}
private void Update()
{
float dt = Time.deltaTime;
_time += dt;
UpdateResistancePulse(dt);
UpdateParticles();
}
#endregion
#region Public API
/// <summary>设置基础强度(0=熄灭, 1=最亮, 可超过 1</summary>
public void SetIntensity(float value)
{
_intensity = Mathf.Max(0f, value);
}
/// <summary>触发一次反抗脉冲爆亮</summary>
public void TriggerPulse(float strength = 0.5f)
{
_resistancePulse = Mathf.Max(_resistancePulse, Mathf.Clamp01(strength));
}
#endregion
#region Particle System Setup
private void CacheAreaBounds()
{
if (areaReference != null)
{
var b = areaReference.bounds;
_areaCenter = b.center;
_areaHalfSize = new Vector2(b.extents.x, b.extents.y);
}
else
{
_areaCenter = transform.position;
_areaHalfSize = new Vector2(0.5f, 0.25f);
}
}
private void EnsureParticleSystem()
{
var go = new GameObject("GlowTubeParticles");
go.transform.SetParent(transform);
go.transform.localPosition = Vector3.zero;
go.transform.localScale = Vector3.one;
_ps = go.AddComponent<ParticleSystem>();
var main = _ps.main;
main.simulationSpace = ParticleSystemSimulationSpace.World;
main.loop = false;
main.startLifetime = 999f;
main.startSize = particleSize;
main.maxParticles = particleCount;
main.playOnAwake = false;
main.startSpeed = 0f;
main.startColor = midColor;
var emission = _ps.emission;
emission.enabled = false;
var vel = _ps.velocityOverLifetime;
vel.enabled = false;
var col = _ps.colorOverLifetime;
col.enabled = false;
var sol = _ps.sizeOverLifetime;
sol.enabled = false;
var psr = go.GetComponent<ParticleSystemRenderer>();
if (psr != null)
{
psr.renderMode = ParticleSystemRenderMode.Billboard;
ApplyMaterial(psr);
int lid = SortingLayer.IsValid(sortingLayerId) ? sortingLayerId : SortingLayer.layers[0].id;
psr.sortingLayerID = lid;
psr.sortingOrder = sortingOrder;
}
_particles = new ParticleSystem.Particle[particleCount];
_phases = new float[particleCount];
for (int i = 0; i < particleCount; i++)
_phases[i] = Random.value;
_ps.Play();
_ps.Emit(particleCount);
}
private void ApplyMaterial(ParticleSystemRenderer psr)
{
var tex = particleTexture != null ? particleTexture : GenerateSoftCircle();
if (tex == null) return;
Material mat;
if (particleMaterial != null)
{
mat = new Material(particleMaterial);
}
else
{
var shader = Shader.Find("Legacy Shaders/Particles/Additive")
?? Shader.Find("Particles/Additive");
if (shader == null) return;
mat = new Material(shader);
mat.SetColor("_TintColor", Color.white);
}
mat.mainTexture = tex;
_matInstance = mat;
psr.material = mat;
}
private Texture2D GenerateSoftCircle()
{
if (_generatedTex != null) return _generatedTex;
const int size = 64;
_generatedTex = new Texture2D(size, size)
{
wrapMode = TextureWrapMode.Clamp,
filterMode = FilterMode.Bilinear
};
var px = new Color[size * size];
float c = (size - 1) * 0.5f;
for (int y = 0; y < size; y++)
for (int x = 0; x < size; x++)
{
float dx = x - c, dy = y - c;
float d = Mathf.Sqrt(dx * dx + dy * dy) / c;
float a = 1f - Mathf.Clamp01(d);
a *= a;
px[y * size + x] = new Color(1f, 1f, 1f, a);
}
_generatedTex.SetPixels(px);
_generatedTex.Apply();
return _generatedTex;
}
#endregion
#region Per-frame Update
private void UpdateResistancePulse(float dt)
{
if (_intensity < 0.5f && _intensity > 0.03f)
{
float suppression = 1f - _intensity;
if (_time > _nextPulseTime)
{
_resistancePulse = Mathf.Max(_resistancePulse,
suppression * (0.25f + Random.value * 0.5f));
_nextPulseTime = _time + 0.2f + Random.value * (2.5f - suppression * 2f);
}
}
_resistancePulse *= Mathf.Exp(-dt * 9f);
if (_resistancePulse < 0.008f) _resistancePulse = 0f;
}
private void UpdateParticles()
{
if (_ps == null || _particles == null) return;
CacheAreaBounds();
float di = Mathf.Clamp(_intensity + _resistancePulse, 0f, 1.5f);
DisplayIntensity = di;
int alive = _ps.GetParticles(_particles);
if (alive == 0)
{
_ps.Emit(particleCount);
return;
}
if (di < 0.01f)
{
for (int i = 0; i < particleCount && i < alive; i++)
{
_particles[i].startColor = new Color(0, 0, 0, 0);
_particles[i].startSize = 0f;
}
_ps.SetParticles(_particles, alive);
return;
}
float baseFlicker = 1f + Mathf.Sin(_time * flickerRate) * flickerAmplitude * di;
float burstFlicker = 1f;
if (di > 0.7f)
{
float bc = Mathf.PerlinNoise(_time * 2f + _seedX, _seedY) - 0.5f;
if (bc > 0.35f) burstFlicker = 1.3f + bc * 0.4f;
}
float totalFlicker = baseFlicker * burstFlicker;
float z = _areaCenter.z;
for (int i = 0; i < particleCount && i < alive; i++)
{
_phases[i] += noiseSpeed * Time.deltaTime * 0.3f;
if (_phases[i] > 100f) _phases[i] -= 100f;
float nx = _phases[i] * noiseScale + _seedX;
float ny = i * 0.37f + _seedY;
float noiseX = Mathf.PerlinNoise(nx, ny) * 2f - 1f;
float noiseY = Mathf.PerlinNoise(ny, nx) * 2f - 1f;
float px = _areaCenter.x + noiseX * _areaHalfSize.x * 0.85f;
float py = _areaCenter.y + noiseY * _areaHalfSize.y * 0.85f;
// Elliptical edge fade
float ex = (px - _areaCenter.x) / Mathf.Max(0.01f, _areaHalfSize.x);
float ey = (py - _areaCenter.y) / Mathf.Max(0.01f, _areaHalfSize.y);
float ellipse = ex * ex + ey * ey;
float edgeFade = Mathf.Clamp01(1f - Mathf.Pow(Mathf.Clamp01(ellipse), 1.5f));
float val = edgeFade * di * totalFlicker;
Color c;
if (val < 0.35f)
{
c = Color.Lerp(new Color(0.05f, 0.02f, 0.03f), lowColor, val / 0.35f);
}
else if (val < 0.7f)
{
c = Color.Lerp(lowColor, midColor, (val - 0.35f) / 0.35f);
}
else
{
c = Color.Lerp(midColor, highColor, (val - 0.7f) / 0.3f);
}
c.a = Mathf.Clamp01(val * 1.2f) * di;
if (_resistancePulse > 0.05f)
{
float flash = _resistancePulse * 0.5f * edgeFade;
c = Color.Lerp(c, highColor, Mathf.Min(0.6f, flash));
}
_particles[i].position = new Vector3(px, py, z);
_particles[i].startColor = c;
_particles[i].startSize = particleSize * (0.6f + val * 0.8f);
_particles[i].remainingLifetime = 999f;
_particles[i].startLifetime = 999f;
}
_ps.SetParticles(_particles, Mathf.Min(alive, particleCount));
}
#endregion
}
}