437 lines
16 KiB
C#
437 lines
16 KiB
C#
using UnityEngine;
|
||
using System.Collections.Generic;
|
||
|
||
namespace AibisDream.MiniGame.Language
|
||
{
|
||
/// <summary>
|
||
/// 候选粒子(可交互的主要粒子)
|
||
/// </summary>
|
||
public class CandidateParticle : TextParticle
|
||
{
|
||
[Header("粒子状态(目标/非目标,颜色由 Manager 配置,可与红蓝解绑)")]
|
||
public bool isRed = false; // 当前是否为目标粒子(用于颜色/样式,可配成蓝)
|
||
public bool isOrange = false; // 是否受到效果影响(橙色)
|
||
public bool originalIsRed = false; // 原始是否为目标粒子(不变,用于字符逻辑)
|
||
public bool hasEffect = false; // 是否有效果
|
||
public bool isTemporaryRed = false; // 临时红色状态
|
||
|
||
[Header("连接")]
|
||
public List<CandidateParticle> connections = new List<CandidateParticle>();
|
||
|
||
[Header("效果")]
|
||
public float changeSpeedMultiplier = 1f;
|
||
public Vector2 vibrationOffset;
|
||
public float vibrationPhase;
|
||
public float effectTimer;
|
||
public float effectAlpha;
|
||
public float effectScale = 1f;
|
||
|
||
[Header("焦虑效果")]
|
||
public float anxietyShakePhase;
|
||
public float anxietyShakeIntensity;
|
||
public float anxietyFloatPhase;
|
||
public float anxietyFloatSpeed;
|
||
public Vector2 anxietyShakeOffset;
|
||
public float anxietyFloatY;
|
||
|
||
[Header("非目标干扰效果")]
|
||
public float interferenceShakeIntensity = 0f;
|
||
public float interferenceShakeSpeed = 8f;
|
||
public float interferenceFloatAmplitude = 0.015f;
|
||
private float interferenceShakePhase;
|
||
private float interferenceFloatPhase;
|
||
private Vector2 interferenceShakeOffset;
|
||
private float interferenceFloatY;
|
||
|
||
[Header("波形关联")]
|
||
public float calmProgress = 0f; // 平静进度(0-1)
|
||
public float resolvedColorProgress = 0f; // 飞向中央时的目标色渐变进度(0-1)
|
||
public float dissolveProgress = 0f; // 消散进度(0-1)
|
||
public WaveformPoint waveformPoint; // 关联的波形点
|
||
|
||
[Header("聚焦干扰")]
|
||
[Range(0f, 1f)] public float focusInterferenceProgress = 0f;
|
||
public Color focusInterferenceColor = new Color(1f, 0.2392157f, 0.3215686f, 1f);
|
||
public float focusInterferenceShakeAmplitude = 0.16f;
|
||
public float focusInterferenceShakeSpeed = 24f;
|
||
private bool focusInterferenceUsesMarquee;
|
||
private Color[] focusInterferenceMarqueeColors;
|
||
private int focusInterferenceMarqueeIndex;
|
||
private float focusInterferenceMarqueeStepDuration = 0.18f;
|
||
|
||
// 颜色配置(由Manager设置)
|
||
private Color targetColor = new Color(1f, 0.39f, 0.39f, 1f); // 目标粒子颜色
|
||
private Color nonTargetColor = new Color(0.2f, 0.78f, 1f, 1f); // 非目标粒子颜色
|
||
private Color propagationColor = new Color(1f, 0.59f, 0.2f, 1f); // 传播颜色
|
||
private Color calmColor = new Color(0.3607843f, 0.945098f, 0.8156863f, 1f); // 最终目标色 #5CF1D0
|
||
private Color[] nonTargetMarqueeColors;
|
||
private int nonTargetMarqueeIndex;
|
||
private float nonTargetMarqueeStepDuration = 0.18f;
|
||
|
||
// 字体样式配置(由Manager设置)
|
||
private float targetFontSize = 3.5f;
|
||
private float nonTargetFontSize = 2.5f;
|
||
private bool targetBold = true;
|
||
private bool nonTargetBold = false;
|
||
|
||
protected override void Awake()
|
||
{
|
||
base.Awake();
|
||
|
||
// 与场景示例文字保持一致:固定使用完整顶点 Alpha,不引入随机亮度差异。
|
||
baseAlpha = 1f;
|
||
alpha = baseAlpha;
|
||
size = nonTargetFontSize; // 默认使用非目标粒子大小
|
||
velocity = new Vector2(Random.Range(-0.5f, 0.5f), Random.Range(-0.5f, 0.5f));
|
||
changeInterval = Random.Range(1f, 2.5f);
|
||
|
||
interferenceShakePhase = Random.Range(0f, Mathf.PI * 2f);
|
||
interferenceFloatPhase = Random.Range(0f, Mathf.PI * 2f);
|
||
|
||
if (textMesh != null)
|
||
{
|
||
textMesh.fontSize = size;
|
||
UpdateFontStyle();
|
||
}
|
||
}
|
||
|
||
protected override void Update()
|
||
{
|
||
base.Update();
|
||
|
||
// 更新效果动画
|
||
if (hasEffect)
|
||
{
|
||
UpdateEffectAnimation();
|
||
}
|
||
|
||
// 更新焦虑效果
|
||
if (isRed && (anxietyShakeIntensity > 0f || focusInterferenceProgress > 0f))
|
||
{
|
||
UpdateAnxietyEffect();
|
||
}
|
||
|
||
UpdateFocusInterferenceCharacters();
|
||
|
||
// 非目标粒子干扰抖动
|
||
if (!isRed && !isCalmed && interferenceShakeIntensity > 0)
|
||
{
|
||
UpdateInterferenceEffect();
|
||
}
|
||
}
|
||
|
||
private void UpdateEffectAnimation()
|
||
{
|
||
if (effectTimer > 0)
|
||
{
|
||
effectTimer -= Time.deltaTime;
|
||
|
||
// 振动效果
|
||
vibrationPhase += Time.deltaTime * 15f;
|
||
float vibrationIntensity = 0.02f;
|
||
vibrationOffset = new Vector2(
|
||
Mathf.Cos(vibrationPhase) * vibrationIntensity,
|
||
Mathf.Sin(vibrationPhase * 1.3f) * vibrationIntensity
|
||
);
|
||
|
||
// 透明度动画(0-1 范围)
|
||
effectAlpha = Mathf.Lerp(0.6f, 1f, (Mathf.Sin(Time.time * 3f) + 1f) / 2f);
|
||
|
||
// 缩放动画
|
||
effectScale = Mathf.Lerp(0.95f, 1.05f, (Mathf.Sin(Time.time * 4.5f) + 1f) / 2f);
|
||
|
||
}
|
||
}
|
||
|
||
private void UpdateAnxietyEffect()
|
||
{
|
||
// 常规焦虑随稳定度消退;聚焦干扰不受稳定度影响,稳定后的字也能被重新强烈扰动。
|
||
float normalShake = anxietyShakeIntensity * (1f - calmProgress) * 0.01f;
|
||
float disruptionShake = focusInterferenceShakeAmplitude * focusInterferenceProgress;
|
||
float shakeIntensity = normalShake + disruptionShake;
|
||
float floatAmplitude = 0.03f * (1f - calmProgress) + disruptionShake * 0.35f;
|
||
|
||
if (calmProgress < 0.9f || focusInterferenceProgress > 0f)
|
||
{
|
||
anxietyShakePhase += Time.deltaTime * Mathf.Lerp(
|
||
10f,
|
||
focusInterferenceShakeSpeed,
|
||
focusInterferenceProgress);
|
||
anxietyFloatPhase += Mathf.Max(anxietyFloatSpeed, Time.deltaTime * 8f * focusInterferenceProgress);
|
||
}
|
||
|
||
// 计算抖动偏移
|
||
anxietyShakeOffset = new Vector2(
|
||
Mathf.Cos(anxietyShakePhase) * shakeIntensity + Mathf.Sin(anxietyShakePhase * 1.7f) * shakeIntensity * 0.5f,
|
||
Mathf.Sin(anxietyShakePhase * 1.3f) * shakeIntensity + Mathf.Cos(anxietyShakePhase * 0.9f) * shakeIntensity * 0.5f
|
||
);
|
||
|
||
// 计算漂浮偏移
|
||
anxietyFloatY = Mathf.Sin(anxietyFloatPhase) * floatAmplitude;
|
||
}
|
||
|
||
protected override void UpdateVisuals()
|
||
{
|
||
if (textMesh == null) return;
|
||
|
||
// 更新字体样式(大小和加粗)
|
||
UpdateFontStyle();
|
||
|
||
// 计算显示位置(叠加干扰抖动)
|
||
Vector3 displayOffset = (Vector3)(vibrationOffset + anxietyShakeOffset + interferenceShakeOffset)
|
||
+ Vector3.up * (anxietyFloatY + interferenceFloatY);
|
||
textMesh.transform.localPosition = displayOffset;
|
||
|
||
// 计算颜色和透明度
|
||
Color color = Color.white;
|
||
float displayAlpha = alpha;
|
||
|
||
if (hasEffect && effectTimer > 0)
|
||
{
|
||
displayAlpha = effectAlpha;
|
||
textMesh.transform.localScale = Vector3.one * effectScale;
|
||
}
|
||
|
||
// 根据消散进度减少透明度
|
||
displayAlpha *= (1 - dissolveProgress);
|
||
|
||
if (isRed)
|
||
{
|
||
// 颜色进度与稳定度解耦:飞向中央时平滑过渡,之后保持固定目标色。
|
||
Color resolved = Color.Lerp(targetColor, calmColor, Mathf.Clamp01(resolvedColorProgress));
|
||
Color interferenceColor = focusInterferenceUsesMarquee
|
||
? GetMarqueeColor(
|
||
focusInterferenceMarqueeColors,
|
||
focusInterferenceMarqueeIndex,
|
||
focusInterferenceMarqueeStepDuration,
|
||
focusInterferenceColor)
|
||
: focusInterferenceColor;
|
||
Color solid = Color.Lerp(
|
||
resolved,
|
||
interferenceColor,
|
||
Mathf.Clamp01(focusInterferenceProgress));
|
||
color = new Color(solid.r, solid.g, solid.b, displayAlpha);
|
||
}
|
||
else if (isOrange)
|
||
{
|
||
// 传播效果颜色
|
||
color = new Color(propagationColor.r, propagationColor.g, propagationColor.b, displayAlpha);
|
||
}
|
||
else
|
||
{
|
||
// 笑话/逃避文字使用统一时钟驱动的固定色板走马灯;只循环,不随机。
|
||
Color marqueeColor = GetNonTargetMarqueeColor();
|
||
color = new Color(marqueeColor.r, marqueeColor.g, marqueeColor.b, displayAlpha);
|
||
}
|
||
|
||
// 面板上的目标/非目标色直接写到顶点色(与示例 TMP 相同,不改材质 FaceColor)
|
||
textMesh.color = color;
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 目标粒子:chineseChars 随机单字;非目标粒子:从笑话/干扰短句字符中随机。
|
||
/// 使用 IsTarget 判断,与红蓝颜色解绑。
|
||
/// </summary>
|
||
protected override string GetNextDisplayChar()
|
||
{
|
||
if (TryGetOverridePoolChar(out string overrideChar))
|
||
return overrideChar;
|
||
return IsTarget ? GetRandomChar() : GetRandomCharFromPhrases();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否为目标粒子(由 originalIsRed 表示,与显示颜色解绑)
|
||
/// </summary>
|
||
private bool IsTarget => originalIsRed;
|
||
|
||
private void UpdateInterferenceEffect()
|
||
{
|
||
interferenceShakePhase += Time.deltaTime * interferenceShakeSpeed;
|
||
interferenceFloatPhase += Time.deltaTime * interferenceShakeSpeed * 0.3f;
|
||
|
||
float intensity = interferenceShakeIntensity;
|
||
interferenceShakeOffset = new Vector2(
|
||
Mathf.Sin(interferenceShakePhase) * intensity
|
||
+ Mathf.Sin(interferenceShakePhase * 2.3f) * intensity * 0.4f,
|
||
Mathf.Cos(interferenceShakePhase * 1.7f) * intensity * 0.7f
|
||
+ Mathf.Sin(interferenceShakePhase * 0.6f) * intensity * 0.3f
|
||
);
|
||
|
||
interferenceFloatY = Mathf.Sin(interferenceFloatPhase) * interferenceFloatAmplitude;
|
||
}
|
||
|
||
private void UpdateFocusInterferenceCharacters()
|
||
{
|
||
if (focusInterferenceProgress <= 0f ||
|
||
string.IsNullOrEmpty(overrideCharPool) ||
|
||
(!isStatic && !isCalmed))
|
||
{
|
||
return;
|
||
}
|
||
|
||
changeTimer -= Time.deltaTime;
|
||
if (changeTimer > 0f)
|
||
return;
|
||
|
||
currentChar = GetNextDisplayChar();
|
||
UpdateText();
|
||
changeTimer = Mathf.Max(0.02f, changeInterval);
|
||
}
|
||
|
||
public void SetInterferenceParams(float shakeIntensity, float shakeSpeed, float changeIntv, float floatAmplitude)
|
||
{
|
||
interferenceShakeIntensity = shakeIntensity;
|
||
interferenceShakeSpeed = shakeSpeed;
|
||
interferenceFloatAmplitude = floatAmplitude;
|
||
if (!isRed)
|
||
{
|
||
changeInterval = Mathf.Max(0.05f, changeIntv * Random.Range(0.7f, 1.3f));
|
||
}
|
||
}
|
||
|
||
public void InitializeAnxietyEffect()
|
||
{
|
||
anxietyShakePhase = Random.Range(0f, Mathf.PI * 2f);
|
||
anxietyShakeIntensity = Random.Range(2f, 4f);
|
||
anxietyFloatPhase = Random.Range(0f, Mathf.PI * 2f);
|
||
anxietyFloatSpeed = Random.Range(0.02f, 0.04f);
|
||
}
|
||
|
||
public void ApplyEffect(float duration = 1f)
|
||
{
|
||
hasEffect = true;
|
||
effectTimer = duration;
|
||
isOrange = true;
|
||
isRed = false;
|
||
changeSpeedMultiplier = 3f;
|
||
vibrationPhase = Random.Range(0f, Mathf.PI * 2f);
|
||
}
|
||
|
||
public void RemoveEffect()
|
||
{
|
||
hasEffect = false;
|
||
isOrange = false;
|
||
isRed = false;
|
||
changeSpeedMultiplier = 1f;
|
||
vibrationOffset = Vector2.zero;
|
||
effectTimer = 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置粒子颜色配置
|
||
/// </summary>
|
||
public void SetColors(Color target, Color nonTarget, Color propagation, Color calm)
|
||
{
|
||
targetColor = target;
|
||
nonTargetColor = nonTarget;
|
||
propagationColor = propagation;
|
||
calmColor = calm;
|
||
}
|
||
|
||
public void SetNonTargetMarquee(
|
||
Color[] colors,
|
||
int particleIndex,
|
||
float stepDuration)
|
||
{
|
||
nonTargetMarqueeColors = colors;
|
||
nonTargetMarqueeIndex = Mathf.Max(0, particleIndex);
|
||
nonTargetMarqueeStepDuration = Mathf.Max(0.02f, stepDuration);
|
||
}
|
||
|
||
private Color GetNonTargetMarqueeColor()
|
||
{
|
||
return GetMarqueeColor(
|
||
nonTargetMarqueeColors,
|
||
nonTargetMarqueeIndex,
|
||
nonTargetMarqueeStepDuration,
|
||
nonTargetColor);
|
||
}
|
||
|
||
private static Color GetMarqueeColor(
|
||
Color[] colors,
|
||
int particleIndex,
|
||
float stepDuration,
|
||
Color fallback)
|
||
{
|
||
if (colors == null || colors.Length == 0)
|
||
return fallback;
|
||
|
||
int step = Mathf.FloorToInt(Time.time / Mathf.Max(0.02f, stepDuration));
|
||
int index = (Mathf.Max(0, particleIndex) + step) % colors.Length;
|
||
return colors[index];
|
||
}
|
||
|
||
public void SetFocusInterference(
|
||
Color color,
|
||
float shakeAmplitude,
|
||
float shakeSpeed = 24f)
|
||
{
|
||
focusInterferenceUsesMarquee = false;
|
||
focusInterferenceColor = color;
|
||
focusInterferenceShakeAmplitude = Mathf.Max(0f, shakeAmplitude);
|
||
focusInterferenceShakeSpeed = Mathf.Max(0f, shakeSpeed);
|
||
}
|
||
|
||
public void SetFocusInterferenceMarquee(
|
||
Color[] colors,
|
||
int particleIndex,
|
||
float stepDuration,
|
||
float shakeAmplitude,
|
||
float shakeSpeed = 24f)
|
||
{
|
||
focusInterferenceUsesMarquee = colors != null && colors.Length > 0;
|
||
focusInterferenceMarqueeColors = colors;
|
||
focusInterferenceMarqueeIndex = Mathf.Max(0, particleIndex);
|
||
focusInterferenceMarqueeStepDuration = Mathf.Max(0.02f, stepDuration);
|
||
focusInterferenceShakeAmplitude = Mathf.Max(0f, shakeAmplitude);
|
||
focusInterferenceShakeSpeed = Mathf.Max(0f, shakeSpeed);
|
||
}
|
||
|
||
public void ClearFocusInterference()
|
||
{
|
||
focusInterferenceProgress = 0f;
|
||
focusInterferenceUsesMarquee = false;
|
||
anxietyShakeOffset = Vector2.zero;
|
||
anxietyFloatY = 0f;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置字体样式配置
|
||
/// </summary>
|
||
public void SetFontStyles(float targetSize, float nonTargetSize, bool targetIsBold, bool nonTargetIsBold)
|
||
{
|
||
targetFontSize = targetSize;
|
||
nonTargetFontSize = nonTargetSize;
|
||
targetBold = targetIsBold;
|
||
nonTargetBold = nonTargetIsBold;
|
||
|
||
// 立即应用
|
||
if (textMesh != null)
|
||
{
|
||
UpdateFontStyle();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新字体样式(根据粒子状态)
|
||
/// </summary>
|
||
private void UpdateFontStyle()
|
||
{
|
||
if (textMesh == null) return;
|
||
|
||
// 根据是否为目标粒子设置大小和加粗
|
||
if (isRed)
|
||
{
|
||
textMesh.fontSize = targetFontSize;
|
||
textMesh.fontStyle = targetBold ? TMPro.FontStyles.Bold : TMPro.FontStyles.Normal;
|
||
}
|
||
else
|
||
{
|
||
textMesh.fontSize = nonTargetFontSize;
|
||
textMesh.fontStyle = nonTargetBold ? TMPro.FontStyles.Bold : TMPro.FontStyles.Normal;
|
||
}
|
||
}
|
||
}
|
||
}
|