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

321 lines
12 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;
using UnityEngine.Serialization;
namespace AibisDream.MiniGame.HuoShan
{
/// <summary>
/// 笑话发生器 · 像素噪点云效果 — 逐像素写 Texture2D,量化色阶呈现粗颗粒像素风
/// 由 KnobController 通过 SetIntensity / TriggerPulse 驱动
/// 表现:满强度时噪点云活跃跳动(笑声节奏),随强度降低逐步被压制变稀变暗,
/// 但反抗脉冲会不断把云重新炸亮 —— 读作"抑制在进行,但没能成功"
/// </summary>
public class GlowTubeEffect : MonoBehaviour
{
[Header("区域")]
[Tooltip("效果区域参考(取其 SpriteRenderer bounds),不填则用自身")]
[SerializeField] private SpriteRenderer areaReference;
[Header("像素网格")]
[Tooltip("横向像素格数(纵向按区域纵横比自动推算)")]
[SerializeField] private int pixelColumns = 72;
[Tooltip("渲染用材质(Unlit,不受 2D 灯光影响),不填则回退到 areaReference 的材质")]
[FormerlySerializedAs("particleMaterial")]
[SerializeField] private Material pixelMaterial;
[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("笑声节奏")]
[Tooltip("笑声脉冲串的基础频率(次/秒),强度越高笑得越快")]
[SerializeField] private float laughRate = 2.2f;
[Tooltip("笑声包络对亮度/密度的影响强度")]
[SerializeField] private float laughAmplitude = 0.6f;
[Header("噪声流动")]
[SerializeField] private float noiseScale = 3f;
[SerializeField] private float noiseSpeed = 1.2f;
[Header("闪烁")]
[SerializeField] private float flickerRate = 8f;
[SerializeField] private float flickerAmplitude = 0.15f;
[Header("量化阈值(值→档位,制造啪嗒式整块亮灭)")]
[SerializeField] private float thresholdLow = 0.16f;
[SerializeField] private float thresholdMid = 0.42f;
[SerializeField] private float thresholdHigh = 0.72f;
private SpriteRenderer _sr;
private Texture2D _tex;
private Sprite _sprite;
private Color32[] _pixels;
private Material _matInstance;
private int _texWidth;
private int _texHeight;
private float _intensity = 1f;
private float _resistancePulse;
private float _nextPulseTime;
private float _time;
private float _seedX;
private float _seedY;
private float _laughSeed;
private Vector2 _areaHalfSize = new Vector2(0.5f, 0.25f);
/// <summary>当前显示强度(含脉冲)</summary>
public float DisplayIntensity { get; private set; }
#region Lifecycle
private void Awake()
{
_seedX = Random.Range(0f, 1000f);
_seedY = Random.Range(0f, 1000f);
_laughSeed = Random.Range(0f, 1000f);
CacheAreaBounds();
EnsurePixelRenderer();
}
private void OnDestroy()
{
if (_matInstance != null) Destroy(_matInstance);
if (_tex != null) Destroy(_tex);
if (_sprite != null) Destroy(_sprite);
}
private void Update()
{
float dt = Time.deltaTime;
_time += dt;
UpdateResistancePulse(dt);
RenderPixels();
}
#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 Setup
private void CacheAreaBounds()
{
var reference = areaReference != null ? areaReference : GetComponent<SpriteRenderer>();
if (reference != null)
{
var b = reference.bounds;
_areaHalfSize = new Vector2(Mathf.Max(0.01f, b.extents.x), Mathf.Max(0.01f, b.extents.y));
}
}
private void EnsurePixelRenderer()
{
var go = new GameObject("GlowTubePixels");
go.transform.SetParent(transform);
go.transform.localPosition = Vector3.zero;
go.transform.localRotation = Quaternion.identity;
go.transform.localScale = Vector3.one;
_sr = go.AddComponent<SpriteRenderer>();
_texWidth = Mathf.Max(4, pixelColumns);
float aspect = _areaHalfSize.y / Mathf.Max(0.001f, _areaHalfSize.x);
_texHeight = Mathf.Max(2, Mathf.RoundToInt(_texWidth * aspect));
_tex = new Texture2D(_texWidth, _texHeight, TextureFormat.RGBA32, false)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Clamp
};
_pixels = new Color32[_texWidth * _texHeight];
float ppu = _texWidth / (_areaHalfSize.x * 2f);
_sprite = Sprite.Create(_tex, new Rect(0, 0, _texWidth, _texHeight), new Vector2(0.5f, 0.5f), ppu);
_sr.sprite = _sprite;
var mat = pixelMaterial != null
? new Material(pixelMaterial)
: (areaReference != null && areaReference.sharedMaterial != null
? new Material(areaReference.sharedMaterial)
: null);
if (mat != null)
{
_matInstance = mat;
_sr.sharedMaterial = mat;
}
int lid = SortingLayer.IsValid(sortingLayerId) ? sortingLayerId : SortingLayer.layers[0].id;
_sr.sortingLayerID = lid;
_sr.sortingOrder = sortingOrder;
}
#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;
}
/// <summary>成组尖峰包络:"哈-哈-哈"式快攻击、弹跳衰减的脉冲串</summary>
private float GetLaughEnvelope(float activity)
{
float rate = laughRate * (0.4f + activity * 1.1f);
float phase = _time * rate + _laughSeed;
float cycle = phase - Mathf.Floor(phase);
// 每个周期内是一串递减的尖峰(哈-哈-哈),而不是单个正弦波
float burst = 0f;
for (int i = 0; i < 3; i++)
{
float spikeCenter = i * 0.28f;
float d = cycle - spikeCenter;
float spike = Mathf.Exp(-d * d * 220f) * (1f - i * 0.28f);
burst += Mathf.Max(0f, spike);
}
return Mathf.Clamp01(burst) * activity;
}
private void RenderPixels()
{
if (_tex == null) return;
float di = Mathf.Clamp(_intensity + _resistancePulse, 0f, 1.5f);
DisplayIntensity = di;
// 活动度:强度越高越活跃,且用陡峭曲线让压制过程变化明显
float activity = Mathf.Pow(Mathf.Clamp01(_intensity), 1.8f);
float laugh = GetLaughEnvelope(activity);
float baseFlicker = 1f + Mathf.Sin(_time * flickerRate) * flickerAmplitude * di;
float laughBoost = 1f + laugh * laughAmplitude;
float totalBoost = baseFlicker * laughBoost;
// 点亮密度随活动度收缩:功率越低,能被点亮的格子越少(云变稀);满功率时基本全亮
float densityGate = 0.3f + activity * 0.75f + _resistancePulse * 0.6f;
int w = _texWidth;
int h = _texHeight;
if (di < 0.01f)
{
var clear = new Color32(0, 0, 0, 0);
for (int i = 0; i < _pixels.Length; i++) _pixels[i] = clear;
_tex.SetPixels32(_pixels);
_tex.Apply(false);
return;
}
float nt = _time * noiseSpeed;
for (int py = 0; py < h; py++)
{
float cy = (py / (float)(h - 1) - 0.5f) * 2f;
for (int px = 0; px < w; px++)
{
int idx = py * w + px;
float cx = (px / (float)(w - 1) - 0.5f) * 2f;
// 超椭圆遮罩:只裁掉四角,让噪点云基本铺满整个管体
float cx2 = cx * cx;
float cy2 = cy * cy;
float super = cx2 * cx2 * cx2 + cy2 * cy2 * cy2;
if (super > 1f)
{
_pixels[idx] = new Color32(0, 0, 0, 0);
continue;
}
float edgeFade = 1f - super * super;
float nx = px * (noiseScale / w) + _seedX;
float ny = py * (noiseScale / w) + _seedY;
float n = Mathf.PerlinNoise(nx + nt, ny - nt * 0.6f);
// 每格固定 hash 抖动,避免整片同步开合,形成噪点云颗粒感
float hash = Frac(Mathf.Sin((px * 12.9898f + py * 78.233f) + _seedX) * 43758.5453f);
// 基线抬高:满强度时整管基本填满,靠色阶档位区分明暗
float val = (0.35f + n * 0.75f) * edgeFade * totalBoost * di;
val *= Mathf.Lerp(0.7f, 1.15f, hash);
bool lit = hash < densityGate;
if (!lit)
{
_pixels[idx] = new Color32(0, 0, 0, 0);
continue;
}
Color32 c;
if (val < thresholdLow)
{
_pixels[idx] = new Color32(0, 0, 0, 0);
continue;
}
else if (val < thresholdMid)
{
c = lowColor;
}
else if (val < thresholdHigh)
{
c = midColor;
}
else
{
c = highColor;
}
if (_resistancePulse > 0.05f && hash > 1f - _resistancePulse)
{
c = highColor;
}
_pixels[idx] = c;
}
}
_tex.SetPixels32(_pixels);
_tex.Apply(false);
}
private static float Frac(float v) => v - Mathf.Floor(v);
#endregion
}
}