feat(huoshan): 扩展辉光管与旋钮表现

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-25 15:57:03 +08:00
co-authored by Cursor
parent bee4e0c796
commit 2c6ac3e142
2 changed files with 277 additions and 149 deletions
@@ -4,10 +4,10 @@ using UnityEngine.Serialization;
namespace AibisDream.MiniGame.HuoShan namespace AibisDream.MiniGame.HuoShan
{ {
/// <summary> /// <summary>
/// 笑话发生器 · 像素噪点云效果 — 逐像素写 Texture2D,量化色阶呈现粗颗粒像素风 /// 笑话发生器 · 像素火花管 — 逐像素写 Texture2D,量化色阶呈现粗颗粒像素风
/// 由 KnobController 通过 SetIntensity / TriggerPulse 驱动 /// 由 KnobController 通过 SetFill / SetIntensity / TriggerPulse 驱动
/// 表现:满强度时噪点云活跃跳动(笑声节奏),随强度降低逐步被压制变稀变暗 /// 表现:管体兼作进度条,右端随抑制推进往左退;管内每格独立跳动并不断迸出火花
/// 但反抗脉冲会不断把云重新炸亮 —— 读作"抑制在进行,但没能成功" /// 抑制越深越稀越暗,但反抗脉冲会把整管重新炸亮 —— 读作"抑制在进行,但没能成功"
/// </summary> /// </summary>
public class GlowTubeEffect : MonoBehaviour public class GlowTubeEffect : MonoBehaviour
{ {
@@ -33,15 +33,47 @@ namespace AibisDream.MiniGame.HuoShan
[SerializeField] private Color midColor = new Color(1f, 0.47f, 0.31f, 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); [SerializeField] private Color highColor = new Color(1f, 0.78f, 0.67f, 1f);
[Header("进度条(管体长度 = 剩余量)")]
[Tooltip("剩余量跟随速度:越大越跟手")]
[SerializeField] private float fillLerpSpeed = 16f;
[Tooltip("前沿热区宽度(占全管比例):越大前端亮带越长")]
[SerializeField] private float edgeHotWidth = 0.1f;
[Tooltip("前沿抖动幅度(占全管比例):让右端边界像火焰一样翻腾而不是一刀切")]
[SerializeField] private float edgeBoil = 0.025f;
[Header("笑声节奏")] [Header("笑声节奏")]
[Tooltip("笑声脉冲串的基础频率(次/秒),强度越高笑得越快")] [Tooltip("笑声脉冲串的基础频率(次/秒),强度越高笑得越快")]
[SerializeField] private float laughRate = 2.2f; [SerializeField] private float laughRate = 2.2f;
[Tooltip("笑声包络对亮度/密度的影响强度")] [Tooltip("笑声包络对亮度/密度的影响强度")]
[SerializeField] private float laughAmplitude = 0.6f; [SerializeField] private float laughAmplitude = 0.85f;
[Header("噪声流动")] [Header("噪声流动")]
[SerializeField] private float noiseScale = 3f; [SerializeField] private float noiseScale = 3f;
[SerializeField] private float noiseSpeed = 1.2f; [SerializeField] private float noiseSpeed = 2.6f;
[Header("逐格跳动")]
[Tooltip("每格独立闪烁的基础速率(次/秒),越大越躁")]
[SerializeField] private float twinkleRate = 11f;
[Tooltip("跳动锐度:越大越接近整格啪嗒开关,越小越像呼吸")]
[Range(1f, 6f)]
[SerializeField] private float twinkleSharpness = 2.4f;
[Header("火花")]
[Tooltip("同屏最大火花数")]
[SerializeField] private int maxSparks = 96;
[Tooltip("满强度时每秒生成的火花数")]
[SerializeField] private float sparkRate = 45f;
[Tooltip("火花基础存活时长 (秒)")]
[SerializeField] private float sparkLife = 0.32f;
[Tooltip("火花从前沿迸出的比例(其余散布在整段管体内)")]
[Range(0f, 1f)]
[SerializeField] private float sparkEdgeBias = 0.55f;
[Header("闪烁")] [Header("闪烁")]
[SerializeField] private float flickerRate = 8f; [SerializeField] private float flickerRate = 8f;
@@ -70,11 +102,34 @@ namespace AibisDream.MiniGame.HuoShan
private float _seedY; private float _seedY;
private float _laughSeed; private float _laughSeed;
private float _fillTarget = 1f;
private float _fill = 1f;
private float _activity = 1f;
private float _laugh;
private Spark[] _sparks;
private int _sparkCount;
private float _sparkAccumulator;
private Vector2 _areaHalfSize = new Vector2(0.5f, 0.25f); private Vector2 _areaHalfSize = new Vector2(0.5f, 0.25f);
private struct Spark
{
public float X;
public float Y;
public float VX;
public float VY;
public float Life;
public float MaxLife;
}
/// <summary>当前显示强度(含脉冲)</summary> /// <summary>当前显示强度(含脉冲)</summary>
public float DisplayIntensity { get; private set; } public float DisplayIntensity { get; private set; }
/// <summary>当前管体剩余长度(0-1),已平滑</summary>
public float Fill => _fill;
#region Lifecycle #region Lifecycle
private void Awake() private void Awake()
@@ -82,6 +137,7 @@ namespace AibisDream.MiniGame.HuoShan
_seedX = Random.Range(0f, 1000f); _seedX = Random.Range(0f, 1000f);
_seedY = Random.Range(0f, 1000f); _seedY = Random.Range(0f, 1000f);
_laughSeed = Random.Range(0f, 1000f); _laughSeed = Random.Range(0f, 1000f);
_sparks = new Spark[Mathf.Max(1, maxSparks)];
CacheAreaBounds(); CacheAreaBounds();
EnsurePixelRenderer(); EnsurePixelRenderer();
} }
@@ -98,6 +154,8 @@ namespace AibisDream.MiniGame.HuoShan
float dt = Time.deltaTime; float dt = Time.deltaTime;
_time += dt; _time += dt;
UpdateResistancePulse(dt); UpdateResistancePulse(dt);
_fill += (_fillTarget - _fill) * Mathf.Min(1f, dt * fillLerpSpeed);
UpdateSparks(dt);
RenderPixels(); RenderPixels();
} }
@@ -111,10 +169,26 @@ namespace AibisDream.MiniGame.HuoShan
_intensity = Mathf.Max(0f, value); _intensity = Mathf.Max(0f, value);
} }
/// <summary>
/// 设置管体剩余长度(1=满管,0=空管)。管体从右端往左退,兼作旋钮进度条。
/// </summary>
public void SetFill(float value)
{
_fillTarget = Mathf.Clamp01(value);
}
/// <summary>立即把管长设为指定值(重置用,不做平滑)</summary>
public void SetFillImmediate(float value)
{
_fillTarget = Mathf.Clamp01(value);
_fill = _fillTarget;
}
/// <summary>触发一次反抗脉冲爆亮</summary> /// <summary>触发一次反抗脉冲爆亮</summary>
public void TriggerPulse(float strength = 0.5f) public void TriggerPulse(float strength = 0.5f)
{ {
_resistancePulse = Mathf.Max(_resistancePulse, Mathf.Clamp01(strength)); _resistancePulse = Mathf.Max(_resistancePulse, Mathf.Clamp01(strength));
BurstSparks(Mathf.RoundToInt(strength * 24f));
} }
#endregion #endregion
@@ -212,6 +286,110 @@ namespace AibisDream.MiniGame.HuoShan
return Mathf.Clamp01(burst) * activity; return Mathf.Clamp01(burst) * activity;
} }
#endregion
#region Sparks
private void UpdateSparks(float dt)
{
if (_sparks == null || _sparks.Length != Mathf.Max(1, maxSparks))
{
_sparks = new Spark[Mathf.Max(1, maxSparks)];
_sparkCount = 0;
}
// 存活推进
int write = 0;
for (int i = 0; i < _sparkCount; i++)
{
var s = _sparks[i];
s.Life -= dt;
if (s.Life <= 0f) continue;
s.X += s.VX * dt;
s.Y += s.VY * dt;
s.VY *= 1f - Mathf.Min(0.9f, dt * 2.2f); // 轻微空气阻力,火花往外冲后停住
if (s.X < -0.05f || s.X > 1.05f || s.Y < -0.4f || s.Y > 1.4f) continue;
_sparks[write++] = s;
}
_sparkCount = write;
if (_fill <= 0.02f) return;
float spawnPerSec = sparkRate * (0.2f + _activity * 1.1f) * (1f + _laugh * 2.5f + _resistancePulse * 3f);
_sparkAccumulator += dt * spawnPerSec;
int budget = 24;
while (_sparkAccumulator >= 1f && budget-- > 0)
{
_sparkAccumulator -= 1f;
SpawnSpark();
}
if (_sparkAccumulator > 4f) _sparkAccumulator = 4f;
}
private void BurstSparks(int count)
{
for (int i = 0; i < count; i++) SpawnSpark();
}
private void SpawnSpark()
{
if (_sparks == null || _sparkCount >= _sparks.Length) return;
float x = Random.value < sparkEdgeBias
? Mathf.Max(0f, _fill - Random.value * edgeHotWidth * 0.8f) // 前沿迸溅
: Random.value * _fill;
float y = 0.5f + (Random.value - 0.5f) * 0.75f;
float outward = y >= 0.5f ? 1f : -1f;
float life = Mathf.Max(0.05f, sparkLife * (0.55f + Random.value * 0.9f));
_sparks[_sparkCount++] = new Spark
{
X = x,
Y = y,
// 略偏左:被抑制的方向
VX = (Random.value - 0.5f) * 0.55f - 0.12f,
VY = outward * (0.3f + Random.value * 1.1f),
Life = life,
MaxLife = life
};
}
private void DrawSparks()
{
int w = _texWidth;
int h = _texHeight;
for (int i = 0; i < _sparkCount; i++)
{
var s = _sparks[i];
int px = Mathf.RoundToInt(s.X * (w - 1));
int py = Mathf.RoundToInt(s.Y * (h - 1));
if (px < 0 || px >= w || py < 0 || py >= h) continue;
float t = s.MaxLife > 0f ? Mathf.Clamp01(s.Life / s.MaxLife) : 0f;
Color c = t > 0.5f ? highColor : Color.Lerp(midColor, highColor, t * 2f);
c.a = 1f;
_pixels[py * w + px] = c;
// 拖尾一格,让火花有方向感
int tx = px - (s.VX >= 0f ? 1 : -1);
int ty = py - (s.VY >= 0f ? 1 : -1);
if (t < 0.75f && tx >= 0 && tx < w && ty >= 0 && ty < h)
{
var tc = midColor;
tc.a = 1f;
_pixels[ty * w + tx] = tc;
}
}
}
#endregion
#region Render
private void RenderPixels() private void RenderPixels()
{ {
if (_tex == null) return; if (_tex == null) return;
@@ -220,95 +398,96 @@ namespace AibisDream.MiniGame.HuoShan
DisplayIntensity = di; DisplayIntensity = di;
// 活动度:强度越高越活跃,且用陡峭曲线让压制过程变化明显 // 活动度:强度越高越活跃,且用陡峭曲线让压制过程变化明显
float activity = Mathf.Pow(Mathf.Clamp01(_intensity), 1.8f); _activity = Mathf.Pow(Mathf.Clamp01(_intensity), 1.8f);
float laugh = GetLaughEnvelope(activity); _laugh = GetLaughEnvelope(_activity);
float baseFlicker = 1f + Mathf.Sin(_time * flickerRate) * flickerAmplitude * di; float baseFlicker = 1f + Mathf.Sin(_time * flickerRate) * flickerAmplitude * di;
float laughBoost = 1f + laugh * laughAmplitude; float laughBoost = 1f + _laugh * laughAmplitude;
float totalBoost = baseFlicker * laughBoost; float totalBoost = baseFlicker * laughBoost;
// 点亮密度随活动度收缩:功率越低,能被点亮的格子越少(云变稀);满功率时基本全亮 // 点亮密度随活动度收缩:功率越低,同一时刻亮着的格子越少(云变稀、跳得越碎
float densityGate = 0.3f + activity * 0.75f + _resistancePulse * 0.6f; float density = Mathf.Clamp01(0.28f + _activity * 0.72f + _resistancePulse * 0.6f + _laugh * 0.35f);
float gate = 1f - density;
int w = _texWidth; int w = _texWidth;
int h = _texHeight; 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;
if (di < 0.01f || _fill < 0.005f)
{ {
var clear = new Color32(0, 0, 0, 0);
for (int i = 0; i < _pixels.Length; i++) _pixels[i] = clear;
_tex.SetPixels32(_pixels); _tex.SetPixels32(_pixels);
_tex.Apply(false); _tex.Apply(false);
return; return;
} }
float nt = _time * noiseSpeed; float nt = _time * noiseSpeed;
float invW = 1f / Mathf.Max(1, w - 1);
float invH = 1f / Mathf.Max(1, h - 1);
for (int py = 0; py < h; py++) for (int py = 0; py < h; py++)
{ {
float cy = (py / (float)(h - 1) - 0.5f) * 2f; float cy = (py * invH - 0.5f) * 2f;
// 每行前沿单独抖动:右端像火焰一样翻腾,而不是一条硬边
float boilN = Mathf.PerlinNoise(py * 0.35f + _seedY, _time * 6f) - 0.5f;
float fillEdge = _fill + boilN * edgeBoil * 2f;
for (int px = 0; px < w; px++) for (int px = 0; px < w; px++)
{ {
int idx = py * w + px; int idx = py * w + px;
float cx = (px / (float)(w - 1) - 0.5f) * 2f; float u = px * invW;
// 管体只画到前沿为止 —— 抑制推进时整条管从右往左变短
float behindEdge = fillEdge - u;
if (behindEdge < 0f) continue;
float cx = (u - 0.5f) * 2f;
// 超椭圆遮罩:只裁掉四角,让噪点云基本铺满整个管体 // 超椭圆遮罩:只裁掉四角,让噪点云基本铺满整个管体
float cx2 = cx * cx; float cx2 = cx * cx;
float cy2 = cy * cy; float cy2 = cy * cy;
float super = cx2 * cx2 * cx2 + cy2 * cy2 * cy2; float super = cx2 * cx2 * cx2 + cy2 * cy2 * cy2;
if (super > 1f) if (super > 1f) continue;
{
_pixels[idx] = new Color32(0, 0, 0, 0);
continue;
}
float edgeFade = 1f - super * super; float edgeFade = 1f - super * super;
float nx = px * (noiseScale / w) + _seedX; // 前沿热区:越靠近右端越亮,读作"还在往回顶的火头"
float ny = py * (noiseScale / w) + _seedY; float edgeHot = Mathf.Exp(-behindEdge / Mathf.Max(0.01f, edgeHotWidth));
float nx = px * (noiseScale * invW) + _seedX;
float ny = py * (noiseScale * invW) + _seedY;
float n = Mathf.PerlinNoise(nx + nt, ny - nt * 0.6f); float n = Mathf.PerlinNoise(nx + nt, ny - nt * 0.6f);
// 每格固定 hash 抖动,避免整片同步开合,形成噪点云颗粒感 // 每格固定 hash 决定自己的闪烁相位与速率 → 逐格独立跳动,不再像一张静态贴图
float hash = Frac(Mathf.Sin((px * 12.9898f + py * 78.233f) + _seedX) * 43758.5453f); float hash = Frac(Mathf.Sin(px * 12.9898f + py * 78.233f + _seedX) * 43758.5453f);
float twinkle = 0.5f + 0.5f * Mathf.Sin(_time * twinkleRate * (0.45f + hash * 1.7f) + hash * 63f);
twinkle = Mathf.Pow(twinkle, twinkleSharpness);
// 基线抬高:满强度时整管基本填满,靠色阶档位区分明暗 float alive = twinkle * Mathf.Lerp(0.7f, 1.35f, hash)
float val = (0.35f + n * 0.75f) * edgeFade * totalBoost * di; + edgeHot * 0.7f
val *= Mathf.Lerp(0.7f, 1.15f, hash); + _resistancePulse * 0.5f
+ _laugh * 0.35f;
if (alive < gate) continue;
bool lit = hash < densityGate; float val = (0.22f + n * 0.7f + twinkle * 0.4f) * edgeFade * totalBoost * di;
if (!lit) val *= 1f + edgeHot * 1.2f;
{
_pixels[idx] = new Color32(0, 0, 0, 0);
continue;
}
Color32 c; Color32 c;
if (val < thresholdLow) if (val < thresholdLow) continue;
{ if (val < thresholdMid) c = lowColor;
_pixels[idx] = new Color32(0, 0, 0, 0); else if (val < thresholdHigh) c = midColor;
continue; else c = highColor;
}
else if (val < thresholdMid)
{
c = lowColor;
}
else if (val < thresholdHigh)
{
c = midColor;
}
else
{
c = highColor;
}
if (_resistancePulse > 0.05f && hash > 1f - _resistancePulse) if (edgeHot > 0.65f || (_resistancePulse > 0.05f && hash > 1f - _resistancePulse))
{
c = highColor; c = highColor;
}
_pixels[idx] = c; _pixels[idx] = c;
} }
} }
DrawSparks();
_tex.SetPixels32(_pixels); _tex.SetPixels32(_pixels);
_tex.Apply(false); _tex.Apply(false);
} }
@@ -53,6 +53,9 @@ namespace AibisDream.MiniGame.HuoShan
[Tooltip("辉光管粒子效果(不填则从 SaleView 下查找)")] [Tooltip("辉光管粒子效果(不填则从 SaleView 下查找)")]
[SerializeField] private GlowTubeEffect glowTubeEffect; [SerializeField] private GlowTubeEffect glowTubeEffect;
[Tooltip("辉光管兼作进度条:勾选则以「失败阈值」为满量程,旋钮一转管体就明显变短;不勾选按整圈量程")]
[SerializeField] private bool glowFillUsesFailThreshold = true;
[Header("信息面板")] [Header("信息面板")]
[SerializeField] private GameObject infoPanel; [SerializeField] private GameObject infoPanel;
[SerializeField] private UnityEngine.UI.Text infoText; [SerializeField] private UnityEngine.UI.Text infoText;
@@ -95,26 +98,6 @@ namespace AibisDream.MiniGame.HuoShan
[Tooltip("开始拖拽旋钮时是否退出激活态(回到展开)")] [Tooltip("开始拖拽旋钮时是否退出激活态(回到展开)")]
[SerializeField] private bool clearHighlightWhenDragStarts = true; [SerializeField] private bool clearHighlightWhenDragStarts = true;
[Header("环形进度条")]
[Tooltip("灯带进度条 SpriteRenderer;不填则尝试自动查找")]
[SerializeField] private SpriteRenderer ringProgressRenderer;
[Tooltip("自动查找的灯带节点名")]
[SerializeField] private string ringProgressChildName = "灯带进度条";
[Tooltip("环形填充起始角度,0=右,90=上")]
[SerializeField] private float ringProgressStartAngle = 90f;
[Tooltip("勾选则按顺时针方向填充")]
[SerializeField] private bool ringProgressClockwise = true;
[Tooltip("灯带进度条着色")]
[SerializeField] private Color ringProgressTint = new Color(1f, 0.72f, 0.25f);
[Tooltip("着色强度:0=保留贴图本色,1=完全按上方颜色重新上色(贴图只保留明暗)")]
[Range(0f, 1f)]
[SerializeField] private float ringProgressRecolor = 1f;
[Header("事件")] [Header("事件")]
public UnityEvent onPowerAdjustmentStart; public UnityEvent onPowerAdjustmentStart;
public UnityEvent<float> onPowerChanged; public UnityEvent<float> onPowerChanged;
@@ -169,20 +152,11 @@ namespace AibisDream.MiniGame.HuoShan
private EventTriggerEx _eventTrigger; private EventTriggerEx _eventTrigger;
private bool _useEventTrigger; private bool _useEventTrigger;
private Material _ringProgressMaterial;
private const string RingProgressShaderName = "AibisDream/SpriteRadialProgress";
private const string RingProgressShaderResourcePath = "Shader/SpriteRadialProgress";
private const string CollapsedSpriteName = "旋钮收起"; private const string CollapsedSpriteName = "旋钮收起";
private const string ExpandedSpriteName = "旋钮展开"; private const string ExpandedSpriteName = "旋钮展开";
private const string ActiveSpriteName = "旋钮激活状态"; private const string ActiveSpriteName = "旋钮激活状态";
private static readonly int MainTexId = Shader.PropertyToID("_MainTex"); private const string LegacyRingProgressName = "灯带进度条";
private static readonly int ProgressId = Shader.PropertyToID("_Progress");
private static readonly int StartAngleId = Shader.PropertyToID("_StartAngle");
private static readonly int FillClockwiseId = Shader.PropertyToID("_FillClockwise");
private static readonly int ShineFadeId = Shader.PropertyToID("_ShineFade"); private static readonly int ShineFadeId = Shader.PropertyToID("_ShineFade");
private static readonly int ColorId = Shader.PropertyToID("_Color");
private static readonly int RecolorStrengthId = Shader.PropertyToID("_RecolorStrength");
private SpriteRenderer _shineHighlightSprite; private SpriteRenderer _shineHighlightSprite;
private bool _isLidOpen; private bool _isLidOpen;
@@ -240,7 +214,8 @@ namespace AibisDream.MiniGame.HuoShan
DisableLegacyKnobLid(); DisableLegacyKnobLid();
ApplyKnobAppearance(KnobAppearance.Collapsed, force: true); ApplyKnobAppearance(KnobAppearance.Collapsed, force: true);
EnsureRingProgress(); DisableLegacyRingProgress();
EnsureGlowTubeEffect();
EnsureInteractionHighlightSprite(); EnsureInteractionHighlightSprite();
SetInteractionHighlightVisible(false); SetInteractionHighlightVisible(false);
@@ -374,65 +349,44 @@ namespace AibisDream.MiniGame.HuoShan
knobVisual.sprite = target; knobVisual.sprite = target;
} }
private void EnsureRingProgress() /// <summary>
/// 旧版环形灯带进度条已废弃(进度改由辉光管长度承担),场景里残留的节点直接隐藏。
/// </summary>
private void DisableLegacyRingProgress()
{ {
if (ringProgressRenderer == null && knobTransform != null && !string.IsNullOrEmpty(ringProgressChildName)) if (knobTransform == null) return;
{
var ring = knobTransform.Find(ringProgressChildName);
if (ring == null && knobTransform.parent != null)
ring = knobTransform.parent.Find(ringProgressChildName);
if (ring != null)
ringProgressRenderer = ring.GetComponent<SpriteRenderer>();
}
if (ringProgressRenderer == null) var ring = knobTransform.Find(LegacyRingProgressName);
return; if (ring == null && knobTransform.parent != null)
ring = knobTransform.parent.Find(LegacyRingProgressName);
Shader shader = Resources.Load<Shader>(RingProgressShaderResourcePath); if (ring != null)
if (shader == null) ring.gameObject.SetActive(false);
shader = Shader.Find(RingProgressShaderName);
if (shader == null)
{
#if UNITY_EDITOR
Debug.LogWarning($"[KnobController] 未找到环形进度条 Shader{RingProgressShaderName}", this);
#endif
return;
}
_ringProgressMaterial = new Material(shader)
{
name = $"{ringProgressRenderer.gameObject.name}_RingProgress_Mat"
};
if (ringProgressRenderer.sprite != null)
_ringProgressMaterial.SetTexture(MainTexId, ringProgressRenderer.sprite.texture);
ringProgressRenderer.material = _ringProgressMaterial;
ApplyRingProgressMaterialProperties();
UpdateRingProgress(_knobValue);
} }
private void ApplyRingProgressMaterialProperties() private void EnsureGlowTubeEffect()
{ {
if (_ringProgressMaterial == null) if (glowTubeEffect != null) return;
return;
_ringProgressMaterial.SetFloat(StartAngleId, ringProgressStartAngle); var t = transform;
_ringProgressMaterial.SetFloat(FillClockwiseId, ringProgressClockwise ? 1f : 0f); while (t != null)
_ringProgressMaterial.SetColor(ColorId, ringProgressTint); {
_ringProgressMaterial.SetFloat(RecolorStrengthId, ringProgressRecolor); if (t.name == "SaleView" || t.name == "SellView" || t.name == "SalelView")
{
glowTubeEffect = t.GetComponentInChildren<GlowTubeEffect>(true);
break;
}
t = t.parent;
}
if (glowTubeEffect == null)
glowTubeEffect = FindObjectOfType<GlowTubeEffect>(true);
} }
private void UpdateRingProgress(float progress) /// <summary>辉光管剩余量:1=满管,0=空管。旋钮拧到失败阈值时正好排空。</summary>
private float GetGlowFill()
{ {
if (_ringProgressMaterial == null) float span = glowFillUsesFailThreshold ? Mathf.Max(0.01f, failThreshold) : 1f;
return; return Mathf.Clamp01(1f - _knobValue / span);
if (ringProgressRenderer != null && ringProgressRenderer.sprite != null)
_ringProgressMaterial.SetTexture(MainTexId, ringProgressRenderer.sprite.texture);
_ringProgressMaterial.SetFloat(ProgressId, Mathf.Clamp01(progress));
} }
private void EnsureEventTriggerEntries() private void EnsureEventTriggerEntries()
@@ -455,6 +409,8 @@ namespace AibisDream.MiniGame.HuoShan
private void Start() private void Start()
{ {
UpdateFromKnobAngle(); UpdateFromKnobAngle();
if (glowTubeEffect != null)
glowTubeEffect.SetFillImmediate(GetGlowFill());
} }
private void Update() private void Update()
@@ -603,7 +559,6 @@ namespace AibisDream.MiniGame.HuoShan
{ {
_knobAngle = Mathf.Clamp(_knobAngle, 0f, _maxAngle); _knobAngle = Mathf.Clamp(_knobAngle, 0f, _maxAngle);
_knobValue = _knobAngle / _maxAngle; _knobValue = _knobAngle / _maxAngle;
UpdateRingProgress(_knobValue);
// Intensity: cubic falloff // Intensity: cubic falloff
_intensity = Mathf.Pow(1f - _knobValue, 3f); _intensity = Mathf.Pow(1f - _knobValue, 3f);
@@ -784,13 +739,14 @@ namespace AibisDream.MiniGame.HuoShan
float overshoot = _overshootIntensity * Mathf.Exp(-_overshootDecay * 2.5f) float overshoot = _overshootIntensity * Mathf.Exp(-_overshootDecay * 2.5f)
* (1f + Mathf.Sin(_overshootDecay * 40f) * 0.5f * Mathf.Exp(-_overshootDecay * 1.5f)); * (1f + Mathf.Sin(_overshootDecay * 40f) * 0.5f * Mathf.Exp(-_overshootDecay * 1.5f));
// 辉光与环形进度一致:用线性剩余量驱动粒子,避免 Pow(...,3) 导致中段过暗、与灯带进度脱节 // 辉光管兼作进度条:管长(fill)与亮度同源,拧旋钮时管体从右往左退且整体变暗
float glowBase = Mathf.Max(0.03f, 1f - _knobValue); float fill = GetGlowFill();
_displayIntensity = glowBase + _resistancePulse + Mathf.Max(0f, overshoot); _displayIntensity = Mathf.Max(0.03f, fill) + _resistancePulse + Mathf.Max(0f, overshoot);
_displayIntensity = Mathf.Min(1.5f, _displayIntensity); _displayIntensity = Mathf.Min(1.5f, _displayIntensity);
if (glowTubeEffect != null) if (glowTubeEffect != null)
{ {
glowTubeEffect.SetFill(fill);
glowTubeEffect.SetIntensity(_displayIntensity); glowTubeEffect.SetIntensity(_displayIntensity);
if (_resistancePulse > 0.1f) if (_resistancePulse > 0.1f)
glowTubeEffect.TriggerPulse(_resistancePulse * 0.5f); glowTubeEffect.TriggerPulse(_resistancePulse * 0.5f);
@@ -954,6 +910,9 @@ namespace AibisDream.MiniGame.HuoShan
UpdateFromKnobAngle(); UpdateFromKnobAngle();
if (glowTubeEffect != null)
glowTubeEffect.SetFillImmediate(GetGlowFill());
HideInfoPanel(); HideInfoPanel();
Debug.Log("[KnobController] Reset"); Debug.Log("[KnobController] Reset");
@@ -987,16 +946,6 @@ namespace AibisDream.MiniGame.HuoShan
onPowerAdjustmentSuccess?.Invoke(_currentPower); onPowerAdjustmentSuccess?.Invoke(_currentPower);
} }
private void OnDestroy()
{
if (_ringProgressMaterial == null) return;
if (Application.isPlaying)
Destroy(_ringProgressMaterial);
else
DestroyImmediate(_ringProgressMaterial);
}
#endregion #endregion
} }