595 lines
16 KiB
C#
595 lines
16 KiB
C#
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<PartInfo> parts = new List<PartInfo>();
|
|
|
|
// 状态控制
|
|
private bool isSplit = false;
|
|
private Sequence currentAnimationSequence;
|
|
private float lastOperationTime = 0f;
|
|
private const float CLICK_COOLDOWN = 0.1f;
|
|
private Transform targetParent;
|
|
|
|
#region 公共接口
|
|
|
|
/// <summary>
|
|
/// 设置目标父节点
|
|
/// </summary>
|
|
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} 个部件");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 拆分所有部件
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 合并所有部件
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 切换拆分/合并状态
|
|
/// </summary>
|
|
public void Toggle()
|
|
{
|
|
if (IsInCooldown())
|
|
{
|
|
if (debugMode) Debug.Log("Toggle: 操作冷却中");
|
|
return;
|
|
}
|
|
|
|
if (isSplit)
|
|
{
|
|
Merge();
|
|
}
|
|
else
|
|
{
|
|
Split();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 停止当前动画
|
|
/// </summary>
|
|
public void StopCurrentAnimation()
|
|
{
|
|
if (currentAnimationSequence != null && currentAnimationSequence.IsActive())
|
|
{
|
|
if (debugMode) Debug.Log("停止当前动画");
|
|
currentAnimationSequence.Kill();
|
|
currentAnimationSequence = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 立即完成拆分
|
|
/// </summary>
|
|
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} 个部件");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 立即完成合并
|
|
/// </summary>
|
|
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} 个部件");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取当前状态
|
|
/// </summary>
|
|
public bool GetCurrentState()
|
|
{
|
|
return isSplit;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 是否正在动画中
|
|
/// </summary>
|
|
public bool IsAnimating()
|
|
{
|
|
return currentAnimationSequence != null && currentAnimationSequence.IsActive();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置动画时长
|
|
/// </summary>
|
|
public void SetAnimationDuration(float duration)
|
|
{
|
|
animationDuration = Mathf.Max(0.1f, duration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置拆分距离
|
|
/// </summary>
|
|
public void SetSplitDistance(float distance)
|
|
{
|
|
splitDistance = Mathf.Max(0.5f, distance);
|
|
|
|
if (parts.Count > 0 && targetParent != null)
|
|
{
|
|
CalculateDetachedPositions();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置移动缓动类型
|
|
/// </summary>
|
|
public void SetMoveEase(Ease ease)
|
|
{
|
|
moveEase = ease;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 设置旋转缓动类型
|
|
/// </summary>
|
|
public void SetRotateEase(Ease ease)
|
|
{
|
|
rotateEase = ease;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取部件数量
|
|
/// </summary>
|
|
public int GetPartCount()
|
|
{
|
|
return parts.Count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 重新计算拆分位置
|
|
/// </summary>
|
|
public void RecalculatePositions()
|
|
{
|
|
if (targetParent != null && parts.Count > 0)
|
|
{
|
|
CalculateDetachedPositions();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 内部方法
|
|
|
|
/// <summary>
|
|
/// 收集可移动的部件
|
|
/// </summary>
|
|
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} 个子物体");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 判断变换是否应该被移动
|
|
/// </summary>
|
|
private bool ShouldMoveTransform(Transform t)
|
|
{
|
|
if (t == null) return false;
|
|
|
|
// 总是移动直接子物体,无论它是否为空
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取子物体数量
|
|
/// </summary>
|
|
private int GetChildCount(Transform parent)
|
|
{
|
|
if (parent == null) return 0;
|
|
return parent.childCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查变换是否有效
|
|
/// </summary>
|
|
private bool IsTransformValid(Transform t)
|
|
{
|
|
if (t == null) return false;
|
|
|
|
// 检查变换是否被销毁
|
|
if (t.Equals(null)) return false;
|
|
|
|
// 检查游戏对象是否有效
|
|
if (t.gameObject == null) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 初始化部件
|
|
/// </summary>
|
|
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("部件初始化完成");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算拆分位置
|
|
/// </summary>
|
|
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}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查是否在冷却中
|
|
/// </summary>
|
|
private bool IsInCooldown()
|
|
{
|
|
float timeSinceLastOperation = Time.time - lastOperationTime;
|
|
return timeSinceLastOperation < CLICK_COOLDOWN;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 生命周期方法
|
|
|
|
private void OnDestroy()
|
|
{
|
|
StopCurrentAnimation();
|
|
}
|
|
|
|
#endregion
|
|
} |