535 lines
14 KiB
C#
535 lines
14 KiB
C#
using UnityEngine;
|
||
using System.Collections.Generic;
|
||
using DG.Tweening;
|
||
|
||
/// <summary>
|
||
/// 水晶雕像组 - 管理5个雕像的组
|
||
/// 包含1个目标雕像和4个正常雕像
|
||
/// </summary>
|
||
public class CrystalStatueGroup : MonoBehaviour
|
||
{
|
||
#region 配置
|
||
|
||
[Header("雕像引用")]
|
||
[Tooltip("组内所有雕像(包括目标和正常雕像)")]
|
||
public CrystalStatue[] statues;
|
||
|
||
[Header("目标雕像")]
|
||
[Tooltip("目标雕像引用(场景中已配置)")]
|
||
public CrystalStatue targetStatue;
|
||
|
||
[Header("升起/下沉动画")]
|
||
[Tooltip("升起高度(从地下升起的距离)")]
|
||
public float riseHeight = 0.5f;
|
||
|
||
[Tooltip("升起动画时长")]
|
||
public float riseDuration = 0.8f;
|
||
|
||
[Tooltip("下沉动画时长")]
|
||
public float sinkDuration = 0.5f;
|
||
|
||
[Tooltip("雕像出现间隔(错开效果)")]
|
||
public float staggerDelay = 0.1f;
|
||
|
||
[Header("随机旋转")]
|
||
[Tooltip("是否启用随机旋转")]
|
||
public bool enableRandomRotation = true;
|
||
|
||
[Tooltip("旋转角度份数(360度分成几份)")]
|
||
public int rotationDivisions = 5;
|
||
|
||
[Header("调试")]
|
||
public bool debugMode = true;
|
||
|
||
#endregion
|
||
|
||
#region 状态
|
||
|
||
/// <summary>
|
||
/// 当前组是否激活
|
||
/// </summary>
|
||
public bool IsGroupActive { get; private set; } = false;
|
||
|
||
/// <summary>
|
||
/// 组内是否所有雕像已碎裂
|
||
/// </summary>
|
||
public bool IsAllBroken { get; private set; } = false;
|
||
|
||
// 回调
|
||
/// <summary>
|
||
/// 目标雕像被击碎回调
|
||
/// </summary>
|
||
public System.Action<CrystalStatueGroup> OnTargetBroken;
|
||
|
||
/// <summary>
|
||
/// 错误雕像被击碎回调
|
||
/// </summary>
|
||
public System.Action<CrystalStatueGroup> OnWrongStatueBroken;
|
||
|
||
#endregion
|
||
|
||
#region Unity生命周期
|
||
|
||
private void Awake()
|
||
{
|
||
// 如果没有配置雕像,尝试从子物体获取
|
||
if (statues == null || statues.Length == 0)
|
||
{
|
||
statues = GetComponentsInChildren<CrystalStatue>();
|
||
}
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
// 初始状态为隐藏
|
||
//gameObject.SetActive(false);
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 公开方法
|
||
|
||
/// <summary>
|
||
/// 激活雕像组(升起动画)
|
||
/// </summary>
|
||
public void ActivateGroup()
|
||
{
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 激活雕像组: {name}");
|
||
|
||
IsGroupActive = true;
|
||
IsAllBroken = false;
|
||
|
||
// 显示组
|
||
gameObject.SetActive(true);
|
||
// 同步位置到玩家
|
||
SyncPositionToPlayer();
|
||
|
||
// 应用随机旋转
|
||
ApplyRandomRotation();
|
||
|
||
// 播放升起动画
|
||
PlayRiseAnimation();
|
||
|
||
// 初始化所有雕像
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null)
|
||
{
|
||
// 判断是否为目标雕像
|
||
bool isTarget = (statue == targetStatue);
|
||
statue.Initialize(isTarget);
|
||
|
||
// 注册碎裂回调
|
||
statue.OnStatueBroken = OnStatueBrokenHandler;
|
||
}
|
||
}
|
||
|
||
if (debugMode)
|
||
{
|
||
if (targetStatue != null)
|
||
Debug.Log($"[CrystalStatueGroup] 目标雕像: {targetStatue.name}");
|
||
else
|
||
Debug.LogWarning($"[CrystalStatueGroup] 未配置目标雕像!");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 隐藏雕像组(下沉动画)
|
||
/// </summary>
|
||
public void DeactivateGroup()
|
||
{
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 隐藏雕像组: {name}");
|
||
|
||
// 播放下沉动画,动画结束后隐藏
|
||
PlaySinkAnimation(() =>
|
||
{
|
||
IsGroupActive = false;
|
||
gameObject.SetActive(false);
|
||
|
||
// 清除回调
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null)
|
||
{
|
||
statue.OnStatueBroken = null;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 立即隐藏(无动画)
|
||
/// </summary>
|
||
public void DeactivateGroupImmediate()
|
||
{
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 立即隐藏雕像组: {name}");
|
||
|
||
IsGroupActive = false;
|
||
gameObject.SetActive(false);
|
||
|
||
// 清除回调
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null)
|
||
{
|
||
statue.OnStatueBroken = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步位置到玩家中心
|
||
/// </summary>
|
||
public void SyncPositionToPlayer()
|
||
{
|
||
if (GameLocal.Ins != null && GameLocal.Ins.self != null)
|
||
{
|
||
Vector3 playerPos = GameLocal.Ins.self.transform.position;
|
||
// 只同步XZ平面,保持Y轴不变
|
||
transform.position = new Vector3(playerPos.x, transform.position.y, playerPos.z);
|
||
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 同步位置到玩家: {transform.position}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重置组内所有雕像
|
||
/// </summary>
|
||
public void ResetGroup()
|
||
{
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null)
|
||
{
|
||
statue.Reset();
|
||
}
|
||
}
|
||
|
||
IsAllBroken = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 让所有未碎裂的雕像播放狞笑
|
||
/// </summary>
|
||
public void PlayAllEvilLaugh()
|
||
{
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null && !statue.IsBroken())
|
||
{
|
||
statue.PlayEvilLaugh();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 让所有未碎裂的雕像消失
|
||
/// </summary>
|
||
public void DisappearAllStatues(float duration = 0.5f)
|
||
{
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null && !statue.IsBroken())
|
||
{
|
||
statue.ForceDisappear(duration);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查是否所有雕像都已碎裂
|
||
/// </summary>
|
||
public bool CheckAllBroken()
|
||
{
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null && !statue.IsBroken())
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 启用所有雕像的击打功能
|
||
/// </summary>
|
||
public void EnableHit()
|
||
{
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null)
|
||
{
|
||
statue.SetCanHit(true);
|
||
}
|
||
}
|
||
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 启用击打");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 禁用所有雕像的击打功能
|
||
/// </summary>
|
||
public void DisableHit()
|
||
{
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null)
|
||
{
|
||
statue.SetCanHit(false);
|
||
}
|
||
}
|
||
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 禁用击打");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置是否允许显示裂痕(语音期间为false,特效出现后为true)
|
||
/// </summary>
|
||
public void SetCanShowCrack(bool value)
|
||
{
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null)
|
||
{
|
||
statue.SetCanShowCrack(value);
|
||
}
|
||
}
|
||
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 设置裂痕显示: {value}");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法
|
||
|
||
/// <summary>
|
||
/// 应用随机Y轴旋转
|
||
/// 将360度分成rotationDivisions份,随机选择一个角度
|
||
/// </summary>
|
||
private void ApplyRandomRotation()
|
||
{
|
||
if (!enableRandomRotation)
|
||
return;
|
||
|
||
// 计算每份的角度
|
||
float anglePerDivision = 360f / rotationDivisions;
|
||
|
||
// 随机选择一个份数(0到rotationDivisions-1)
|
||
int randomDivision = Random.Range(0, rotationDivisions);
|
||
|
||
// 计算最终角度
|
||
float randomAngle = randomDivision * anglePerDivision;
|
||
|
||
// 应用Y轴旋转
|
||
transform.rotation = Quaternion.Euler(0f, randomAngle, 0f);
|
||
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 随机旋转: {randomAngle}度 (第{randomDivision + 1}/{rotationDivisions}份)");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 播放升起动画(雕像从地下升起)
|
||
/// </summary>
|
||
private void PlayRiseAnimation()
|
||
{
|
||
for (int i = 0; i < statues.Length; i++)
|
||
{
|
||
if (statues[i] == null)
|
||
continue;
|
||
|
||
CrystalStatue statue = statues[i];
|
||
Transform statueTransform = statue.transform;
|
||
|
||
// 保存目标位置
|
||
Vector3 targetPosition = statueTransform.position;
|
||
|
||
// 设置初始位置(在地下)
|
||
statueTransform.position = targetPosition - Vector3.up * riseHeight;
|
||
|
||
// 初始缩放为0
|
||
statueTransform.localScale = Vector3.zero;
|
||
|
||
// 计算延迟时间(错开出现)
|
||
float delay = i * staggerDelay;
|
||
|
||
// 使用DOTween播放升起动画
|
||
Sequence riseSeq = DOTween.Sequence();
|
||
|
||
// 延迟开始
|
||
riseSeq.SetDelay(delay);
|
||
|
||
// 同时执行缩放和位移动画
|
||
riseSeq.Join(statueTransform.DOScale(Vector3.one, riseDuration)
|
||
.SetEase(Ease.OutBack));
|
||
|
||
riseSeq.Join(statueTransform.DOMoveY(targetPosition.y, riseDuration)
|
||
.SetEase(Ease.OutQuad));
|
||
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 雕像 {statue.name} 升起动画,延迟: {delay}s");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 播放下沉动画(雕像沉入地下)
|
||
/// </summary>
|
||
/// <param name="onComplete">动画完成回调</param>
|
||
private void PlaySinkAnimation(System.Action onComplete = null)
|
||
{
|
||
int completedCount = 0;
|
||
int totalStatues = 0;
|
||
|
||
for (int i = 0; i < statues.Length; i++)
|
||
{
|
||
if (statues[i] == null || statues[i].IsBroken())
|
||
continue;
|
||
|
||
totalStatues++;
|
||
}
|
||
|
||
if (totalStatues == 0)
|
||
{
|
||
onComplete?.Invoke();
|
||
return;
|
||
}
|
||
|
||
for (int i = 0; i < statues.Length; i++)
|
||
{
|
||
if (statues[i] == null || statues[i].IsBroken())
|
||
continue;
|
||
|
||
CrystalStatue statue = statues[i];
|
||
Transform statueTransform = statue.transform;
|
||
|
||
// 计算延迟时间(错开下沉,逆序)
|
||
float delay = (statues.Length - 1 - i) * staggerDelay;
|
||
|
||
// 使用DOTween播放下沉动画
|
||
Sequence sinkSeq = DOTween.Sequence();
|
||
|
||
// 延迟开始
|
||
sinkSeq.SetDelay(delay);
|
||
|
||
// 同时执行缩放和位移动画
|
||
sinkSeq.Join(statueTransform.DOScale(Vector3.zero, sinkDuration)
|
||
.SetEase(Ease.InBack));
|
||
|
||
sinkSeq.Join(statueTransform.DOMoveY(
|
||
statueTransform.position.y - riseHeight,
|
||
sinkDuration)
|
||
.SetEase(Ease.InQuad));
|
||
|
||
// 动画完成计数
|
||
sinkSeq.OnComplete(() =>
|
||
{
|
||
completedCount++;
|
||
if (completedCount >= totalStatues)
|
||
{
|
||
onComplete?.Invoke();
|
||
}
|
||
});
|
||
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 雕像 {statue.name} 下沉动画");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 雕像碎裂处理
|
||
/// </summary>
|
||
private void OnStatueBrokenHandler(CrystalStatue statue, bool isTarget)
|
||
{
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 雕像碎裂: {statue.name}, 是否目标: {isTarget}");
|
||
|
||
// 禁用组内其他雕像的击打功能(一旦有雕像碎裂,不能再敲其他雕像)
|
||
DisableHitForOtherStatues(statue);
|
||
|
||
if (isTarget)
|
||
{
|
||
// 目标雕像被击碎
|
||
OnTargetBroken?.Invoke(this);
|
||
}
|
||
else
|
||
{
|
||
// 错误雕像被击碎
|
||
OnWrongStatueBroken?.Invoke(this);
|
||
}
|
||
|
||
// 检查是否所有雕像都已碎裂
|
||
if (CheckAllBroken())
|
||
{
|
||
IsAllBroken = true;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 禁用除指定雕像外的所有雕像击打功能
|
||
/// </summary>
|
||
private void DisableHitForOtherStatues(CrystalStatue excludedStatue)
|
||
{
|
||
foreach (var statue in statues)
|
||
{
|
||
if (statue != null && statue != excludedStatue && !statue.IsBroken())
|
||
{
|
||
statue.SetCanHit(false);
|
||
}
|
||
}
|
||
|
||
if (debugMode)
|
||
Debug.Log($"[CrystalStatueGroup] 禁用其他雕像击打功能");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 编辑器辅助
|
||
|
||
#if UNITY_EDITOR
|
||
[ContextMenu("自动获取雕像引用")]
|
||
private void AutoGetStatues()
|
||
{
|
||
statues = GetComponentsInChildren<CrystalStatue>();
|
||
Debug.Log($"[CrystalStatueGroup] 获取到 {statues.Length} 个雕像");
|
||
}
|
||
|
||
[ContextMenu("测试:激活组")]
|
||
private void TestActivate()
|
||
{
|
||
ActivateGroup();
|
||
}
|
||
|
||
[ContextMenu("测试:隐藏组")]
|
||
private void TestDeactivate()
|
||
{
|
||
DeactivateGroup();
|
||
}
|
||
|
||
[ContextMenu("测试:重置组")]
|
||
private void TestReset()
|
||
{
|
||
ResetGroup();
|
||
}
|
||
|
||
[ContextMenu("测试:狞笑")]
|
||
private void TestEvilLaugh()
|
||
{
|
||
PlayAllEvilLaugh();
|
||
}
|
||
|
||
[ContextMenu("测试:消失")]
|
||
private void TestDisappear()
|
||
{
|
||
DisappearAllStatues();
|
||
}
|
||
#endif
|
||
|
||
#endregion
|
||
} |