using DG.Tweening; using System.Collections.Generic; using System.Linq; using UnityEngine; public class SplitMergeController : MonoBehaviour { [System.Serializable] public class PartInfo { public Transform partTransform; [HideInInspector] public Vector3 assembledWorldPosition; [HideInInspector] public Quaternion assembledWorldRotation; [HideInInspector] public Vector3 detachedWorldPosition; [HideInInspector] public Quaternion detachedWorldRotation; [HideInInspector] public Vector3 assembledLocalPosition; [HideInInspector] public Quaternion assembledLocalRotation; } [Header("动画参数")] [SerializeField] private float animationDuration = 1.0f; [SerializeField] private float splitDistance = 3.0f; [SerializeField] private float heightOffset = 1.0f; [SerializeField] private Ease moveEase = Ease.OutCubic; [SerializeField] private Ease rotateEase = Ease.OutCubic; [Header("调试设置")] [SerializeField] private bool debugMode = false; [SerializeField] private List parts = new List(); // 状态控制 private bool isSplit = false; private Sequence currentAnimationSequence; private float lastOperationTime = 0f; private const float CLICK_COOLDOWN = 0.1f; private Transform targetParent; #region 公共接口 /// /// 设置目标父节点 /// public void SetTarget(Transform parent) { if (parent == null) { Debug.LogError("SetTarget: 父节点不能为空"); return; } if (debugMode) Debug.Log($"SetTarget: 开始设置目标父节点 {parent.name}"); // 停止当前动画 StopCurrentAnimation(); // 清空现有部件 parts.Clear(); targetParent = parent; // 收集所有可移动的子物体 CollectMovableParts(parent); if (debugMode) Debug.Log($"SetTarget: 已收集 {parts.Count} 个可移动部件"); if (parts.Count == 0) { Debug.LogWarning($"SetTarget: 目标父节点 {parent.name} 下没有找到可移动的部件"); return; } // 初始化部件数据 InitializeParts(); Debug.Log($"SetTarget: 成功设置目标 {parent.name}, 包含 {parts.Count} 个部件"); } /// /// 拆分所有部件 /// public void Split() { if (parts.Count == 0) { Debug.LogError("Split: 请先调用 SetTarget 设置目标"); return; } if (isSplit) { if (debugMode) Debug.Log("Split: 已处于拆分状态"); return; } if (IsInCooldown()) { if (debugMode) Debug.Log("Split: 操作冷却中"); return; } if (IsAnimating()) { if (debugMode) Debug.Log("Split: 正在动画中,将中断当前动画"); } StopCurrentAnimation(); lastOperationTime = Time.time; if (debugMode) Debug.Log($"Split: 开始拆分 {parts.Count} 个部件"); // 使用DOTween创建动画序列 currentAnimationSequence = DOTween.Sequence(); int activeParts = 0; foreach (PartInfo part in parts) { if (part.partTransform == null || !part.partTransform.gameObject.activeInHierarchy) { if (debugMode) Debug.LogWarning($"Split: 跳过无效部件 {part.partTransform?.name}"); continue; } // 检查部件是否在场景中 if (!IsTransformValid(part.partTransform)) { if (debugMode) Debug.LogWarning($"Split: 跳过无效变换 {part.partTransform.name}"); continue; } activeParts++; // 添加移动动画 var moveTween = part.partTransform.DOMove( part.detachedWorldPosition, animationDuration ).SetEase(moveEase); // 添加旋转动画 var rotateTween = part.partTransform.DORotateQuaternion( part.detachedWorldRotation, animationDuration ).SetEase(rotateEase); // 添加到序列 currentAnimationSequence.Insert(0, moveTween); currentAnimationSequence.Insert(0, rotateTween); } if (activeParts == 0) { Debug.LogError("Split: 没有有效的部件可以拆分"); currentAnimationSequence = null; return; } if (debugMode) Debug.Log($"Split: 为 {activeParts} 个有效部件创建动画"); // 设置动画完成回调 currentAnimationSequence.OnComplete(() => { isSplit = true; currentAnimationSequence = null; if (debugMode) Debug.Log("Split: 拆分动画完成"); }); currentAnimationSequence.OnKill(() => { if (debugMode) Debug.Log("Split: 拆分动画被中断"); }); currentAnimationSequence.Play(); } /// /// 合并所有部件 /// public void Merge() { if (parts.Count == 0) { Debug.LogError("Merge: 请先调用 SetTarget 设置目标"); return; } if (!isSplit) { if (debugMode) Debug.Log("Merge: 已处于合并状态"); return; } if (IsInCooldown()) { if (debugMode) Debug.Log("Merge: 操作冷却中"); return; } if (IsAnimating()) { if (debugMode) Debug.Log("Merge: 正在动画中,将中断当前动画"); } StopCurrentAnimation(); lastOperationTime = Time.time; if (debugMode) Debug.Log($"Merge: 开始合并 {parts.Count} 个部件"); // 使用DOTween创建动画序列 currentAnimationSequence = DOTween.Sequence(); int activeParts = 0; foreach (PartInfo part in parts) { if (part.partTransform == null || !part.partTransform.gameObject.activeInHierarchy) { if (debugMode) Debug.LogWarning($"Merge: 跳过无效部件 {part.partTransform?.name}"); continue; } // 检查部件是否在场景中 if (!IsTransformValid(part.partTransform)) { if (debugMode) Debug.LogWarning($"Merge: 跳过无效变换 {part.partTransform.name}"); continue; } activeParts++; // 添加移动动画 var moveTween = part.partTransform.DOMove( part.assembledWorldPosition, animationDuration ).SetEase(moveEase); // 添加旋转动画 var rotateTween = part.partTransform.DORotateQuaternion( part.assembledWorldRotation, animationDuration ).SetEase(rotateEase); // 添加到序列 currentAnimationSequence.Insert(0, moveTween); currentAnimationSequence.Insert(0, rotateTween); } if (activeParts == 0) { Debug.LogError("Merge: 没有有效的部件可以合并"); currentAnimationSequence = null; return; } if (debugMode) Debug.Log($"Merge: 为 {activeParts} 个有效部件创建动画"); // 设置动画完成回调 currentAnimationSequence.OnComplete(() => { isSplit = false; currentAnimationSequence = null; if (debugMode) Debug.Log("Merge: 合并动画完成"); }); currentAnimationSequence.OnKill(() => { if (debugMode) Debug.Log("Merge: 合并动画被中断"); }); currentAnimationSequence.Play(); } /// /// 切换拆分/合并状态 /// public void Toggle() { if (IsInCooldown()) { if (debugMode) Debug.Log("Toggle: 操作冷却中"); return; } if (isSplit) { Merge(); } else { Split(); } } /// /// 停止当前动画 /// public void StopCurrentAnimation() { if (currentAnimationSequence != null && currentAnimationSequence.IsActive()) { if (debugMode) Debug.Log("停止当前动画"); currentAnimationSequence.Kill(); currentAnimationSequence = null; } } /// /// 立即完成拆分 /// public void InstantSplit() { StopCurrentAnimation(); int movedParts = 0; foreach (PartInfo part in parts) { if (part.partTransform == null || !part.partTransform.gameObject.activeInHierarchy) continue; if (!IsTransformValid(part.partTransform)) continue; part.partTransform.position = part.detachedWorldPosition; part.partTransform.rotation = part.detachedWorldRotation; movedParts++; } isSplit = true; if (debugMode) Debug.Log($"InstantSplit: 立即拆分了 {movedParts} 个部件"); } /// /// 立即完成合并 /// public void InstantMerge() { StopCurrentAnimation(); int movedParts = 0; foreach (PartInfo part in parts) { if (part.partTransform == null || !part.partTransform.gameObject.activeInHierarchy) continue; if (!IsTransformValid(part.partTransform)) continue; part.partTransform.position = part.assembledWorldPosition; part.partTransform.rotation = part.assembledWorldRotation; movedParts++; } isSplit = false; if (debugMode) Debug.Log($"InstantMerge: 立即合并了 {movedParts} 个部件"); } /// /// 获取当前状态 /// public bool GetCurrentState() { return isSplit; } /// /// 是否正在动画中 /// public bool IsAnimating() { return currentAnimationSequence != null && currentAnimationSequence.IsActive(); } /// /// 设置动画时长 /// public void SetAnimationDuration(float duration) { animationDuration = Mathf.Max(0.1f, duration); } /// /// 设置拆分距离 /// public void SetSplitDistance(float distance) { splitDistance = Mathf.Max(0.5f, distance); if (parts.Count > 0 && targetParent != null) { CalculateDetachedPositions(); } } /// /// 设置移动缓动类型 /// public void SetMoveEase(Ease ease) { moveEase = ease; } /// /// 设置旋转缓动类型 /// public void SetRotateEase(Ease ease) { rotateEase = ease; } /// /// 获取部件数量 /// public int GetPartCount() { return parts.Count; } /// /// 重新计算拆分位置 /// public void RecalculatePositions() { if (targetParent != null && parts.Count > 0) { CalculateDetachedPositions(); } } #endregion #region 内部方法 /// /// 收集可移动的部件 /// private void CollectMovableParts(Transform parent) { if (parent == null) return; // 收集所有直接子物体 foreach (Transform child in parent) { if (child == null) continue; // 检查这个子物体是否应该被移动 if (ShouldMoveTransform(child)) { PartInfo part = new PartInfo { partTransform = child }; parts.Add(part); if (debugMode) { Debug.Log($"收集部件: {child.name}, 子物体数量: {GetChildCount(child)}"); } } } if (debugMode && parts.Count == 0) { Debug.LogWarning($"没有收集到任何部件,父节点 {parent.name} 有 {parent.childCount} 个子物体"); } } /// /// 判断变换是否应该被移动 /// private bool ShouldMoveTransform(Transform t) { if (t == null) return false; // 总是移动直接子物体,无论它是否为空 return true; } /// /// 获取子物体数量 /// private int GetChildCount(Transform parent) { if (parent == null) return 0; return parent.childCount; } /// /// 检查变换是否有效 /// private bool IsTransformValid(Transform t) { if (t == null) return false; // 检查变换是否被销毁 if (t.Equals(null)) return false; // 检查游戏对象是否有效 if (t.gameObject == null) return false; return true; } /// /// 初始化部件 /// private void InitializeParts() { if (parts.Count == 0) return; if (debugMode) Debug.Log("开始初始化部件数据"); foreach (PartInfo part in parts) { if (part.partTransform == null) continue; part.assembledWorldPosition = part.partTransform.position; part.assembledWorldRotation = part.partTransform.rotation; part.assembledLocalPosition = part.partTransform.localPosition; part.assembledLocalRotation = part.partTransform.localRotation; if (debugMode) { Debug.Log($"初始化部件: {part.partTransform.name}, " + $"世界位置: {part.assembledWorldPosition}, " + $"本地位置: {part.assembledLocalPosition}"); } } CalculateDetachedPositions(); isSplit = false; if (debugMode) Debug.Log("部件初始化完成"); } /// /// 计算拆分位置 /// private void CalculateDetachedPositions() { if (targetParent == null) return; Vector3 center = targetParent.position; for (int i = 0; i < parts.Count; i++) { if (parts[i].partTransform == null) continue; // 获取部件当前位置 Vector3 currentPos = parts[i].assembledWorldPosition; // 计算从中心到当前位置的方向 Vector3 direction = (currentPos - center).normalized; // 如果方向是零向量(部件在中心),则使用随机方向 if (direction == Vector3.zero) { direction = Random.onUnitSphere; } // 计算拆分位置 parts[i].detachedWorldPosition = center + direction * splitDistance; // 添加高度偏移 parts[i].detachedWorldPosition += Vector3.up * (i % 3) * heightOffset; // 添加一些随机旋转 Vector3 randomRotation = new Vector3( Random.Range(-20f, 20f), Random.Range(-180f, 180f), Random.Range(-20f, 20f) ); parts[i].detachedWorldRotation = parts[i].assembledWorldRotation * Quaternion.Euler(randomRotation); if (debugMode) { Debug.Log($"计算拆分位置: {parts[i].partTransform.name}, " + $"从 {currentPos} 到 {parts[i].detachedWorldPosition}"); } } } /// /// 检查是否在冷却中 /// private bool IsInCooldown() { float timeSinceLastOperation = Time.time - lastOperationTime; return timeSinceLastOperation < CLICK_COOLDOWN; } #endregion #region 生命周期方法 private void OnDestroy() { StopCurrentAnimation(); } #endregion }