using UnityEngine;
using UnityEngine.Serialization;
namespace AibisDream.MiniGame.HuoShan
{
///
/// 笑话发生器 · 像素噪点云效果 — 逐像素写 Texture2D,量化色阶呈现粗颗粒像素风
/// 由 KnobController 通过 SetIntensity / TriggerPulse 驱动
/// 表现:满强度时噪点云活跃跳动(笑声节奏),随强度降低逐步被压制变稀变暗,
/// 但反抗脉冲会不断把云重新炸亮 —— 读作"抑制在进行,但没能成功"
///
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);
/// 当前显示强度(含脉冲)
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
/// 设置基础强度(0=熄灭, 1=最亮, 可超过 1)
public void SetIntensity(float value)
{
_intensity = Mathf.Max(0f, value);
}
/// 触发一次反抗脉冲爆亮
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();
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();
_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;
}
/// 成组尖峰包络:"哈-哈-哈"式快攻击、弹跳衰减的脉冲串
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
}
}