Files
MRSnowWhite/Assets/_SnowWhite/Scripts/Cutscene/CrystalStatue.cs
2026-03-17 19:42:50 +08:00

475 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using DarkTonic.MasterAudio;
using DG.Tweening;
using System;
/// <summary>
/// 水晶雕像控制器 - 单个雕像的控制逻辑
/// 击打产生裂痕,使用动画帧控制
/// </summary>
public class CrystalStatue : MonoBehaviour
{
#region
[Header("雕像配置")]
[Tooltip("是否为目标雕像(需要击碎的雕像)")]
public bool isTargetStatue = false;
[Header("动画配置")]
[Tooltip("裂痕动画控制器")]
public Animator crackAnimator;
[Tooltip("裂痕动画名称")]
public string crackAnimName = "Crack";
[Tooltip("动画总帧数")]
public int totalAnimFrames = 6;
[Tooltip("第一次击打目标帧")]
public int firstHitFrame = 2;
[Tooltip("第二次击打目标帧")]
public int secondHitFrame = 4;
[Header("击打配置")]
[Tooltip("击打需要的次数")]
public int hitCountToBreak = 3;
[Header("音效配置")]
[Tooltip("击打音效")]
public string hitSound = "2.1";
//[Tooltip("碎裂音效")]
//public string breakSound = "2.2";
[Tooltip("狞笑音效(击打错误时)")]
public string evilLaughSound = "2.3";
[Header("粒子效果")]
[Tooltip("击打粒子效果")]
public ParticleSystem hitParticle;
[Tooltip("碎裂粒子效果")]
public ParticleSystem breakParticle;
[Tooltip("狞笑粒子效果")]
public ParticleSystem evilLaughParticle;
public GameObject hitObj;
[Header("震动配置")]
public float hitShakeDuration = 0.2f;
public float hitShakeStrength = 0.05f;
[Header("碎裂配置")]
public float breakDuration = 1f;
public float disappearDelay = 0.3f;
[Header("调试")]
public bool debugMode = true;
#endregion
#region
public enum StatueState
{
Idle, // 正常状态
Hit, // 被击打中
Breaking, // 正在碎裂
Broken, // 已碎裂
EvilLaugh // 狞笑(击打错误)
}
public StatueState currentState { get; private set; } = StatueState.Idle;
// 当前击打次数
private int currentHitCount = 0;
// 是否可被击打
private bool canHit = true;
// 是否允许显示裂痕语音期间为false特效出现后为true
private bool canShowCrack = true;
// 动画哈希
private int crackAnimHash;
// 事件回调
/// <summary>
/// 雕像碎裂回调 (雕像, 是否是目标雕像)
/// </summary>
public Action<CrystalStatue, bool> OnStatueBroken;
#endregion
#region Unity生命周期
private void Awake()
{
if (crackAnimator == null)
crackAnimator = GetComponent<Animator>();
// 缓存动画哈希
crackAnimHash = Animator.StringToHash(crackAnimName);
}
private void Start()
{
currentHitCount = 0;
canHit = true;
currentState = StatueState.Idle;
// 初始化动画状态
ResetAnimatorState();
}
#endregion
#region
/// <summary>
/// 初始化雕像
/// </summary>
/// <param name="isTarget">是否为目标雕像</param>
public void Initialize(bool isTarget)
{
isTargetStatue = isTarget;
currentHitCount = 0;
canHit = true;
currentState = StatueState.Idle;
// 重置动画
ResetAnimatorState();
// 重置缩放
transform.localScale = Vector3.one;
// 显示对象
gameObject.SetActive(true);
hitObj.SetActive(false);
if (debugMode)
Debug.Log($"[CrystalStatue] 初始化雕像: {name}, 是否目标: {isTarget}");
}
/// <summary>
/// 玩家击中雕像
/// </summary>
public void OnPlayerHit()
{
if (!canHit || currentState == StatueState.Broken)
{
if (debugMode)
Debug.Log($"[CrystalStatue] {name} 不可被击打");
return;
}
// 播放击打音效和特效(语音期间也有)
PlayHitSound();
PlayHitEffect();
// 如果不允许显示裂痕(语音期间),直接返回
if (!canShowCrack)
{
if (debugMode)
Debug.Log($"[CrystalStatue] {name} 语音期间敲打,仅播放特效不显示裂痕");
return;
}
currentHitCount++;
currentState = StatueState.Hit;
if (debugMode)
Debug.Log($"[CrystalStatue] {name} 被击打! 第{currentHitCount}次击打, 是否目标: {isTargetStatue}");
// 更新动画帧
UpdateCrackAnimation();
// 检查是否碎裂
if (currentHitCount >= hitCountToBreak)
{
Break();
}
else
{
// 短暂冷却
canHit = false;
DOVirtual.DelayedCall(0.3f, () =>
{
canHit = true;
if (currentState != StatueState.Broken)
currentState = StatueState.Idle;
});
}
}
/// <summary>
/// 播放邪恶狞笑(击打错误时)
/// </summary>
public void PlayEvilLaugh()
{
currentState = StatueState.EvilLaugh;
canHit = false;
if (debugMode)
Debug.Log($"[CrystalStatue] {name} 播放邪恶狞笑");
// 播放狞笑音效
if (!string.IsNullOrEmpty(evilLaughSound))
{
MasterAudio.PlaySound3DAtTransform(evilLaughSound, transform);
}
// 播放狞笑粒子
if (evilLaughParticle != null)
{
evilLaughParticle.Play();
}
}
/// <summary>
/// 强制消失
/// </summary>
public void ForceDisappear(float duration = 0.5f)
{
canHit = false;
// 缩放消失
transform.DOScale(Vector3.zero, duration)
.SetEase(Ease.InBack)
.OnComplete(() =>
{
currentState = StatueState.Broken;
gameObject.SetActive(false);
});
}
/// <summary>
/// 重置雕像状态
/// </summary>
public void Reset()
{
currentHitCount = 0;
canHit = true;
currentState = StatueState.Idle;
// 重置缩放
transform.localScale = Vector3.one;
// 重置动画
ResetAnimatorState();
// 显示对象
gameObject.SetActive(true);
}
/// <summary>
/// 是否已碎裂
/// </summary>
public bool IsBroken()
{
return currentState == StatueState.Broken;
}
/// <summary>
/// 获取当前击打次数
/// </summary>
public int GetHitCount()
{
return currentHitCount;
}
/// <summary>
/// 设置是否可被击打
/// </summary>
public void SetCanHit(bool value)
{
canHit = value;
}
/// <summary>
/// 设置是否允许显示裂痕语音期间为false特效出现后为true
/// </summary>
public void SetCanShowCrack(bool value)
{
canShowCrack = value;
hitObj.SetActive(value);
}
#endregion
#region
/// <summary>
/// 重置动画状态到初始帧
/// </summary>
private void ResetAnimatorState()
{
if (crackAnimator != null)
{
// 播放到第0帧
crackAnimator.Play(crackAnimHash, 0, 0f);
crackAnimator.speed = 0f; // 暂停在第0帧
}
}
/// <summary>
/// 更新裂痕动画帧
/// </summary>
private void UpdateCrackAnimation()
{
if (crackAnimator == null)
{
if (debugMode)
Debug.LogWarning($"[CrystalStatue] {name} 没有动画组件");
return;
}
float targetFrame = 0f;
bool playFull = false;
switch (currentHitCount)
{
case 1:
targetFrame = firstHitFrame;
break;
case 2:
targetFrame = secondHitFrame;
break;
case 3:
// 第三次击打,完整播放动画
playFull = true;
break;
}
if (playFull)
{
// 完整播放动画
crackAnimator.speed = 1f;
crackAnimator.Play(crackAnimHash, 0, 0f);
}
else
{
// 播放到指定帧并暂停
float normalizedTime = targetFrame / (float)totalAnimFrames;
// 使用DoTween平滑过渡到目标帧
DOVirtual.Float(
crackAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime,
normalizedTime,
0.2f,
(value) =>
{
crackAnimator.Play(crackAnimHash, 0, value);
crackAnimator.speed = 0f;
}
);
}
if (debugMode)
Debug.Log($"[CrystalStatue] {name} 更新动画帧: 第{currentHitCount}次击打, 帧: {targetFrame}");
}
/// <summary>
/// 雕像碎裂
/// </summary>
private void Break()
{
currentState = StatueState.Breaking;
canHit = false;
if (debugMode)
Debug.Log($"[CrystalStatue] {name} 碎裂! 是否目标: {isTargetStatue}");
// 播放碎裂粒子
if (breakParticle != null)
{
breakParticle.Play();
}
// 碎裂缩放动画
transform.DOScale(Vector3.zero, breakDuration)
.SetEase(Ease.InBack)
.OnComplete(() =>
{
currentState = StatueState.Broken;
gameObject.SetActive(false);
// 触发回调
OnStatueBroken?.Invoke(this, isTargetStatue);
});
}
/// <summary>
/// 播放击打音效
/// </summary>
private void PlayHitSound()
{
if (!string.IsNullOrEmpty(hitSound))
{
MasterAudio.PlaySound3DAtTransform(hitSound, transform);
}
}
/// <summary>
/// 播放击打特效
/// </summary>
private void PlayHitEffect()
{
// 播放粒子
if (hitParticle != null)
{
hitParticle.Play();
}
// 播放震动
transform.DOShakePosition(
hitShakeDuration,
hitShakeStrength,
10,
90f,
false,
true
);
}
#endregion
#region
#if UNITY_EDITOR
[ContextMenu("测试:击打雕像")]
private void TestHit()
{
OnPlayerHit();
}
[ContextMenu("测试:碎裂雕像")]
private void TestBreak()
{
currentHitCount = hitCountToBreak;
Break();
}
[ContextMenu("测试:重置雕像")]
private void TestReset()
{
Reset();
}
[ContextMenu("测试:播放狞笑")]
private void TestEvilLaugh()
{
PlayEvilLaugh();
}
[ContextMenu("测试:强制消失")]
private void TestForceDisappear()
{
ForceDisappear();
}
#endif
#endregion
}