feat(huoshan): 扩展辉光管与旋钮表现
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,10 +4,10 @@ using UnityEngine.Serialization;
|
||||
namespace AibisDream.MiniGame.HuoShan
|
||||
{
|
||||
/// <summary>
|
||||
/// 笑话发生器 · 像素噪点云效果 — 逐像素写 Texture2D,量化色阶呈现粗颗粒像素风
|
||||
/// 由 KnobController 通过 SetIntensity / TriggerPulse 驱动
|
||||
/// 表现:满强度时噪点云活跃跳动(笑声节奏),随强度降低逐步被压制变稀变暗,
|
||||
/// 但反抗脉冲会不断把云重新炸亮 —— 读作"抑制在进行,但没能成功"
|
||||
/// 笑话发生器 · 像素火花管 — 逐像素写 Texture2D,量化色阶呈现粗颗粒像素风
|
||||
/// 由 KnobController 通过 SetFill / SetIntensity / TriggerPulse 驱动
|
||||
/// 表现:管体兼作进度条,右端随抑制推进往左退;管内每格独立跳动并不断迸出火花,
|
||||
/// 抑制越深越稀越暗,但反抗脉冲会把整管重新炸亮 —— 读作"抑制在进行,但没能成功"
|
||||
/// </summary>
|
||||
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 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("笑声节奏")]
|
||||
[Tooltip("笑声脉冲串的基础频率(次/秒),强度越高笑得越快")]
|
||||
[SerializeField] private float laughRate = 2.2f;
|
||||
[Tooltip("笑声包络对亮度/密度的影响强度")]
|
||||
[SerializeField] private float laughAmplitude = 0.6f;
|
||||
[SerializeField] private float laughAmplitude = 0.85f;
|
||||
|
||||
[Header("噪声流动")]
|
||||
[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("闪烁")]
|
||||
[SerializeField] private float flickerRate = 8f;
|
||||
@@ -70,11 +102,34 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
private float _seedY;
|
||||
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 struct Spark
|
||||
{
|
||||
public float X;
|
||||
public float Y;
|
||||
public float VX;
|
||||
public float VY;
|
||||
public float Life;
|
||||
public float MaxLife;
|
||||
}
|
||||
|
||||
/// <summary>当前显示强度(含脉冲)</summary>
|
||||
public float DisplayIntensity { get; private set; }
|
||||
|
||||
/// <summary>当前管体剩余长度(0-1),已平滑</summary>
|
||||
public float Fill => _fill;
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
private void Awake()
|
||||
@@ -82,6 +137,7 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
_seedX = Random.Range(0f, 1000f);
|
||||
_seedY = Random.Range(0f, 1000f);
|
||||
_laughSeed = Random.Range(0f, 1000f);
|
||||
_sparks = new Spark[Mathf.Max(1, maxSparks)];
|
||||
CacheAreaBounds();
|
||||
EnsurePixelRenderer();
|
||||
}
|
||||
@@ -98,6 +154,8 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
float dt = Time.deltaTime;
|
||||
_time += dt;
|
||||
UpdateResistancePulse(dt);
|
||||
_fill += (_fillTarget - _fill) * Mathf.Min(1f, dt * fillLerpSpeed);
|
||||
UpdateSparks(dt);
|
||||
RenderPixels();
|
||||
}
|
||||
|
||||
@@ -111,10 +169,26 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
_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>
|
||||
public void TriggerPulse(float strength = 0.5f)
|
||||
{
|
||||
_resistancePulse = Mathf.Max(_resistancePulse, Mathf.Clamp01(strength));
|
||||
BurstSparks(Mathf.RoundToInt(strength * 24f));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -212,6 +286,110 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
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()
|
||||
{
|
||||
if (_tex == null) return;
|
||||
@@ -220,95 +398,96 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
DisplayIntensity = di;
|
||||
|
||||
// 活动度:强度越高越活跃,且用陡峭曲线让压制过程变化明显
|
||||
float activity = Mathf.Pow(Mathf.Clamp01(_intensity), 1.8f);
|
||||
float laugh = GetLaughEnvelope(activity);
|
||||
_activity = Mathf.Pow(Mathf.Clamp01(_intensity), 1.8f);
|
||||
_laugh = GetLaughEnvelope(_activity);
|
||||
|
||||
float baseFlicker = 1f + Mathf.Sin(_time * flickerRate) * flickerAmplitude * di;
|
||||
float laughBoost = 1f + laugh * laughAmplitude;
|
||||
float laughBoost = 1f + _laugh * laughAmplitude;
|
||||
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 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.Apply(false);
|
||||
return;
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
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++)
|
||||
{
|
||||
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 cy2 = cy * cy;
|
||||
float super = cx2 * cx2 * cx2 + cy2 * cy2 * cy2;
|
||||
if (super > 1f)
|
||||
{
|
||||
_pixels[idx] = new Color32(0, 0, 0, 0);
|
||||
continue;
|
||||
}
|
||||
if (super > 1f) continue;
|
||||
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);
|
||||
|
||||
// 每格固定 hash 抖动,避免整片同步开合,形成噪点云颗粒感
|
||||
float hash = Frac(Mathf.Sin((px * 12.9898f + py * 78.233f) + _seedX) * 43758.5453f);
|
||||
// 每格固定 hash 决定自己的闪烁相位与速率 → 逐格独立跳动,不再像一张静态贴图
|
||||
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 val = (0.35f + n * 0.75f) * edgeFade * totalBoost * di;
|
||||
val *= Mathf.Lerp(0.7f, 1.15f, hash);
|
||||
float alive = twinkle * Mathf.Lerp(0.7f, 1.35f, hash)
|
||||
+ edgeHot * 0.7f
|
||||
+ _resistancePulse * 0.5f
|
||||
+ _laugh * 0.35f;
|
||||
if (alive < gate) continue;
|
||||
|
||||
bool lit = hash < densityGate;
|
||||
if (!lit)
|
||||
{
|
||||
_pixels[idx] = new Color32(0, 0, 0, 0);
|
||||
continue;
|
||||
}
|
||||
float val = (0.22f + n * 0.7f + twinkle * 0.4f) * edgeFade * totalBoost * di;
|
||||
val *= 1f + edgeHot * 1.2f;
|
||||
|
||||
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 (val < thresholdLow) continue;
|
||||
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;
|
||||
}
|
||||
|
||||
_pixels[idx] = c;
|
||||
}
|
||||
}
|
||||
|
||||
DrawSparks();
|
||||
|
||||
_tex.SetPixels32(_pixels);
|
||||
_tex.Apply(false);
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
[Tooltip("辉光管粒子效果(不填则从 SaleView 下查找)")]
|
||||
[SerializeField] private GlowTubeEffect glowTubeEffect;
|
||||
|
||||
[Tooltip("辉光管兼作进度条:勾选则以「失败阈值」为满量程,旋钮一转管体就明显变短;不勾选按整圈量程")]
|
||||
[SerializeField] private bool glowFillUsesFailThreshold = true;
|
||||
|
||||
[Header("信息面板")]
|
||||
[SerializeField] private GameObject infoPanel;
|
||||
[SerializeField] private UnityEngine.UI.Text infoText;
|
||||
@@ -95,26 +98,6 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
[Tooltip("开始拖拽旋钮时是否退出激活态(回到展开)")]
|
||||
[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("事件")]
|
||||
public UnityEvent onPowerAdjustmentStart;
|
||||
public UnityEvent<float> onPowerChanged;
|
||||
@@ -169,20 +152,11 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
private EventTriggerEx _eventTrigger;
|
||||
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 ExpandedSpriteName = "旋钮展开";
|
||||
private const string ActiveSpriteName = "旋钮激活状态";
|
||||
private static readonly int MainTexId = Shader.PropertyToID("_MainTex");
|
||||
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 const string LegacyRingProgressName = "灯带进度条";
|
||||
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 bool _isLidOpen;
|
||||
@@ -240,7 +214,8 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
DisableLegacyKnobLid();
|
||||
ApplyKnobAppearance(KnobAppearance.Collapsed, force: true);
|
||||
|
||||
EnsureRingProgress();
|
||||
DisableLegacyRingProgress();
|
||||
EnsureGlowTubeEffect();
|
||||
|
||||
EnsureInteractionHighlightSprite();
|
||||
SetInteractionHighlightVisible(false);
|
||||
@@ -374,65 +349,44 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
knobVisual.sprite = target;
|
||||
}
|
||||
|
||||
private void EnsureRingProgress()
|
||||
/// <summary>
|
||||
/// 旧版环形灯带进度条已废弃(进度改由辉光管长度承担),场景里残留的节点直接隐藏。
|
||||
/// </summary>
|
||||
private void DisableLegacyRingProgress()
|
||||
{
|
||||
if (ringProgressRenderer == null && knobTransform != null && !string.IsNullOrEmpty(ringProgressChildName))
|
||||
{
|
||||
var ring = knobTransform.Find(ringProgressChildName);
|
||||
if (ring == null && knobTransform.parent != null)
|
||||
ring = knobTransform.parent.Find(ringProgressChildName);
|
||||
if (ring != null)
|
||||
ringProgressRenderer = ring.GetComponent<SpriteRenderer>();
|
||||
}
|
||||
if (knobTransform == null) return;
|
||||
|
||||
if (ringProgressRenderer == null)
|
||||
return;
|
||||
|
||||
Shader shader = Resources.Load<Shader>(RingProgressShaderResourcePath);
|
||||
if (shader == null)
|
||||
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);
|
||||
var ring = knobTransform.Find(LegacyRingProgressName);
|
||||
if (ring == null && knobTransform.parent != null)
|
||||
ring = knobTransform.parent.Find(LegacyRingProgressName);
|
||||
if (ring != null)
|
||||
ring.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void ApplyRingProgressMaterialProperties()
|
||||
private void EnsureGlowTubeEffect()
|
||||
{
|
||||
if (_ringProgressMaterial == null)
|
||||
return;
|
||||
if (glowTubeEffect != null) return;
|
||||
|
||||
_ringProgressMaterial.SetFloat(StartAngleId, ringProgressStartAngle);
|
||||
_ringProgressMaterial.SetFloat(FillClockwiseId, ringProgressClockwise ? 1f : 0f);
|
||||
_ringProgressMaterial.SetColor(ColorId, ringProgressTint);
|
||||
_ringProgressMaterial.SetFloat(RecolorStrengthId, ringProgressRecolor);
|
||||
var t = transform;
|
||||
while (t != null)
|
||||
{
|
||||
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)
|
||||
return;
|
||||
|
||||
if (ringProgressRenderer != null && ringProgressRenderer.sprite != null)
|
||||
_ringProgressMaterial.SetTexture(MainTexId, ringProgressRenderer.sprite.texture);
|
||||
|
||||
_ringProgressMaterial.SetFloat(ProgressId, Mathf.Clamp01(progress));
|
||||
float span = glowFillUsesFailThreshold ? Mathf.Max(0.01f, failThreshold) : 1f;
|
||||
return Mathf.Clamp01(1f - _knobValue / span);
|
||||
}
|
||||
|
||||
private void EnsureEventTriggerEntries()
|
||||
@@ -455,6 +409,8 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
private void Start()
|
||||
{
|
||||
UpdateFromKnobAngle();
|
||||
if (glowTubeEffect != null)
|
||||
glowTubeEffect.SetFillImmediate(GetGlowFill());
|
||||
}
|
||||
|
||||
private void Update()
|
||||
@@ -603,7 +559,6 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
{
|
||||
_knobAngle = Mathf.Clamp(_knobAngle, 0f, _maxAngle);
|
||||
_knobValue = _knobAngle / _maxAngle;
|
||||
UpdateRingProgress(_knobValue);
|
||||
|
||||
// Intensity: cubic falloff
|
||||
_intensity = Mathf.Pow(1f - _knobValue, 3f);
|
||||
@@ -784,13 +739,14 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
float overshoot = _overshootIntensity * Mathf.Exp(-_overshootDecay * 2.5f)
|
||||
* (1f + Mathf.Sin(_overshootDecay * 40f) * 0.5f * Mathf.Exp(-_overshootDecay * 1.5f));
|
||||
|
||||
// 辉光与环形进度一致:用线性剩余量驱动粒子,避免 Pow(...,3) 导致中段过暗、与灯带进度脱节
|
||||
float glowBase = Mathf.Max(0.03f, 1f - _knobValue);
|
||||
_displayIntensity = glowBase + _resistancePulse + Mathf.Max(0f, overshoot);
|
||||
// 辉光管兼作进度条:管长(fill)与亮度同源,拧旋钮时管体从右往左退且整体变暗
|
||||
float fill = GetGlowFill();
|
||||
_displayIntensity = Mathf.Max(0.03f, fill) + _resistancePulse + Mathf.Max(0f, overshoot);
|
||||
_displayIntensity = Mathf.Min(1.5f, _displayIntensity);
|
||||
|
||||
if (glowTubeEffect != null)
|
||||
{
|
||||
glowTubeEffect.SetFill(fill);
|
||||
glowTubeEffect.SetIntensity(_displayIntensity);
|
||||
if (_resistancePulse > 0.1f)
|
||||
glowTubeEffect.TriggerPulse(_resistancePulse * 0.5f);
|
||||
@@ -954,6 +910,9 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
|
||||
UpdateFromKnobAngle();
|
||||
|
||||
if (glowTubeEffect != null)
|
||||
glowTubeEffect.SetFillImmediate(GetGlowFill());
|
||||
|
||||
HideInfoPanel();
|
||||
|
||||
Debug.Log("[KnobController] Reset");
|
||||
@@ -987,16 +946,6 @@ namespace AibisDream.MiniGame.HuoShan
|
||||
onPowerAdjustmentSuccess?.Invoke(_currentPower);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_ringProgressMaterial == null) return;
|
||||
|
||||
if (Application.isPlaying)
|
||||
Destroy(_ringProgressMaterial);
|
||||
else
|
||||
DestroyImmediate(_ringProgressMaterial);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user