diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs b/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs index 37b6c4b8d..3c371ca3e 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/CandidateParticle.cs @@ -35,6 +35,15 @@ namespace AibisDream.MiniGame.Language 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 dissolveProgress = 0f; // 消散进度(0-1) @@ -63,6 +72,9 @@ namespace AibisDream.MiniGame.Language 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; @@ -85,6 +97,12 @@ namespace AibisDream.MiniGame.Language { UpdateAnxietyEffect(); } + + // 非目标粒子干扰抖动 + if (!isRed && !isCalmed && interferenceShakeIntensity > 0) + { + UpdateInterferenceEffect(); + } } private void UpdateEffectAnimation() @@ -141,8 +159,9 @@ namespace AibisDream.MiniGame.Language // 更新字体样式(大小和加粗) UpdateFontStyle(); - // 计算显示位置 - Vector3 displayOffset = (Vector3)(vibrationOffset + anxietyShakeOffset) + Vector3.up * anxietyFloatY; + // 计算显示位置(叠加干扰抖动) + Vector3 displayOffset = (Vector3)(vibrationOffset + anxietyShakeOffset + interferenceShakeOffset) + + Vector3.up * (anxietyFloatY + interferenceFloatY); textMesh.transform.localPosition = displayOffset; // 计算颜色和透明度 @@ -204,6 +223,33 @@ namespace AibisDream.MiniGame.Language /// 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; + } + + 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); diff --git a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs index 3a527a7dc..7551772b4 100644 --- a/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs +++ b/Assets/Scripts/MiniGame/HuoShan/Language/LanguageParticleManager.cs @@ -89,6 +89,12 @@ namespace AibisDream.MiniGame.Language [SerializeField] private bool targetParticleBold = true; // 目标粒子是否加粗 [SerializeField] private bool nonTargetParticleBold = false; // 非目标粒子是否加粗 + [Header("非目标粒子干扰效果")] + [SerializeField, Range(0f, 0.1f)] private float nonTargetShakeIntensity = 0.02f; // 非目标粒子抖动强度 + [SerializeField, Range(1f, 20f)] private float nonTargetShakeSpeed = 8f; // 非目标粒子抖动速度 + [SerializeField, Range(0.1f, 1f)] private float nonTargetChangeInterval = 0.4f; // 非目标粒子变字间隔(越小越快) + [SerializeField, Range(0f, 0.03f)] private float nonTargetFloatAmplitude = 0.015f; // 非目标粒子漂浮幅度 + [Header("连接线粗细")] [SerializeField] private float targetConnectionThickness = 0.04f; // 目标连线粗细 [SerializeField] private float nonTargetConnectionThickness = 0.02f; // 非目标连线粗细 @@ -110,6 +116,8 @@ namespace AibisDream.MiniGame.Language [SerializeField] private GameObject languageDeepPanel1; [SerializeField] private GameObject languageDeepPanel2; [SerializeField] private Button focusSequenceButton; + [Tooltip("火山释放log界面,结束时会对其 SpriteRenderer 做淡出")] + [SerializeField] private SpriteRenderer releaseLogSpriteRenderer; [Tooltip("开场表达panel Timeline 名称(DirectorName 或 DirectorName/AddressableKey),播完才算表达流程完成")] [SerializeField] private string expressionPanelTimelineName = "火山表达模块/火山表达panel"; [Tooltip("火山表达确认 Timeline 名称,播完才解锁按钮交互")] @@ -460,6 +468,7 @@ namespace AibisDream.MiniGame.Language particle.SetFont(chineseFontAsset); particle.SetColors(targetParticleColor, nonTargetParticleColor, nonTargetParticleColor, targetCalmColor); particle.SetFontStyles(targetParticleFontSize, nonTargetParticleFontSize, targetParticleBold, nonTargetParticleBold); + particle.SetInterferenceParams(nonTargetShakeIntensity, nonTargetShakeSpeed, nonTargetChangeInterval, nonTargetFloatAmplitude); SetParticleBounds(particle); // 设置发光效果(目标粒子和非目标粒子使用不同强度) @@ -862,9 +871,11 @@ namespace AibisDream.MiniGame.Language } } - // 首先激活 button 但不可交互,播放火山表达确认后再解锁 + // 首先激活 button 但不可交互,播放火山表达确认后再解锁(若之前被 fade 过,需重置 alpha) if (focusSequenceButton != null) { + var btnCg = focusSequenceButton.GetComponent(); + if (btnCg != null) btnCg.alpha = 1f; focusSequenceButton.gameObject.SetActive(true); focusSequenceButton.interactable = false; } @@ -1172,10 +1183,14 @@ namespace AibisDream.MiniGame.Language completionPhase = CompletionPhase.Finished; + // languageDeepPanel1、languageDeepPanel2、button、火山释放log界面 一起淡出 + FadeOutUIElement(languageDeepPanel1, focusFadeDuration); + FadeOutUIElement(languageDeepPanel2, focusFadeDuration); if (focusSequenceButton != null) { - focusSequenceButton.gameObject.SetActive(false); + FadeOutUIElement(focusSequenceButton.gameObject, focusFadeDuration); } + FadeOutSpriteRenderer(releaseLogSpriteRenderer, focusFadeDuration); // 触发完成对话节点 if (!string.IsNullOrEmpty(completionDialogNode)) @@ -1237,6 +1252,38 @@ namespace AibisDream.MiniGame.Language } } + /// + /// 淡出 UI 元素(使用 CanvasGroup),结束后 SetActive(false) + /// + private void FadeOutUIElement(GameObject obj, float duration) + { + if (obj == null || !obj.activeSelf) return; + + var cg = obj.GetComponent(); + if (cg == null) + cg = obj.AddComponent(); + cg.alpha = 1f; + cg.DOFade(0f, duration) + .SetEase(Ease.OutQuad) + .OnComplete(() => obj.SetActive(false)); + } + + /// + /// 淡出 SpriteRenderer,结束后禁用并隐藏 + /// + private void FadeOutSpriteRenderer(SpriteRenderer sr, float duration) + { + if (sr == null || !sr.gameObject.activeSelf) return; + + sr.DOFade(0f, duration) + .SetEase(Ease.OutQuad) + .OnComplete(() => + { + sr.enabled = false; + sr.gameObject.SetActive(false); + }); + } + private Vector3 ComputeCentroid(List particles) { if (particles == null || particles.Count == 0) @@ -1377,6 +1424,7 @@ namespace AibisDream.MiniGame.Language p.alpha = p.baseAlpha; p.transform.localScale = Vector3.one; p.transform.DOKill(); + p.SetInterferenceParams(nonTargetShakeIntensity, nonTargetShakeSpeed, nonTargetChangeInterval, nonTargetFloatAmplitude); if (p.glowSprite != null) { @@ -1625,9 +1673,19 @@ namespace AibisDream.MiniGame.Language { if (!string.IsNullOrEmpty(expressionPanelTimelineName) && TimelineCenter.Instance != null) { - // 火山表达 panel timeline 驱动 panel1/2,播放前需先激活 - if (languageDeepPanel1 != null) languageDeepPanel1.SetActive(true); - if (languageDeepPanel2 != null) languageDeepPanel2.SetActive(true); + // 火山表达 panel timeline 驱动 panel1/2,播放前需先激活(若之前被 fade 过,需重置 alpha) + if (languageDeepPanel1 != null) + { + var cg1 = languageDeepPanel1.GetComponent(); + if (cg1 != null) cg1.alpha = 1f; + languageDeepPanel1.SetActive(true); + } + if (languageDeepPanel2 != null) + { + var cg2 = languageDeepPanel2.GetComponent(); + if (cg2 != null) cg2.alpha = 1f; + languageDeepPanel2.SetActive(true); + } yield return null; // 确保 DirectorHandler 已注册 yield return TimelineCenter.Instance.PlayTimelineAsync(expressionPanelTimelineName);