EnergyEfficiencyManagement/Assets/Zion/Scripts/Utils/ModelSpreadController.cs

624 lines
17 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 DG.Tweening;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 模型拆装动画控制器
/// 功能:实现模型部件从中心点向外平移远离的拆装动画
/// 特点使用DOTween实现平滑动画部件从中心点向外平移不旋转
/// </summary>
public class ModelSpreadController : MonoBehaviour
{
/// <summary>
/// 部件信息结构
/// </summary>
[System.Serializable]
public class PartInfo
{
public Transform partTransform; // 部件变换组件
[HideInInspector] public Vector3 assembledWorldPosition; // 组装时的世界位置
[HideInInspector] public Quaternion assembledWorldRotation; // 组装时的世界旋转
[HideInInspector] public Vector3 detachedWorldPosition; // 拆开后的世界位置
[HideInInspector] public Quaternion detachedWorldRotation; // 拆开后的世界旋转
}
[Header("动画参数")]
[SerializeField] private float animationDuration = 1.0f; // 动画总时长
[SerializeField] private float spreadDistance = 3.0f; // 散开距离
[SerializeField] private Ease moveEase = Ease.OutCubic; // 移动动画缓动类型
[Header("部件信息")]
[SerializeField] private List<PartInfo> parts = new List<PartInfo>(); // 部件列表
[Header("部件收集设置")]
[SerializeField] private bool includeInactive = false; // 是否包含非激活的物体
[SerializeField] private bool skipEmptyTransforms = true; // 是否跳过空变换节点
// 状态控制
private bool isSplit = false; // 当前是否已拆分
private Sequence currentAnimationSequence; // 当前动画序列
private float lastOperationTime = 0f; // 上次操作时间
private const float CLICK_COOLDOWN = 0.1f; // 操作冷却时间
private Vector3 centerPoint = Vector3.zero; // 动画中心点
private Transform targetParent; // 目标父节点
#region
/// <summary>
/// 设置目标父节点
/// 参数parent - 包含所有要拆装的部件的父节点
/// </summary>
public void SetTarget(Transform parent)
{
if (parent == null)
{
Debug.LogError("SetTarget: 父节点不能为空");
return;
}
// 停止当前动画
StopCurrentAnimation();
// 清空现有部件
parts.Clear();
targetParent = parent;
// 收集所有直接子物体
CollectAllChildren(parent);
//CollectAllChildrenRecursive(parent);
if (parts.Count == 0)
{
Debug.LogWarning($"SetTarget: 目标父节点 {parent.name} 下没有找到部件");
return;
}
// 初始化部件数据
InitializeParts();
}
/// <summary>
/// 拆分所有部件
/// 动画流程:所有部件从各自当前位置向外平移远离中心点
/// </summary>
public void Split()
{
if (parts.Count == 0)
{
Debug.LogError("Split: 请先调用 SetTarget 设置目标");
return;
}
if (isSplit)
{
Debug.Log("Split: 已处于拆分状态");
return;
}
if (IsInCooldown())
{
Debug.Log("Split: 操作过于频繁");
return;
}
StopCurrentAnimation();
lastOperationTime = Time.time;
// 计算中心点
CalculateCenterPoint();
// 计算远离中心点的位置
CalculateDetachedPositions();
// 使用DOTween创建动画序列
currentAnimationSequence = DOTween.Sequence();
int activeParts = 0;
Debug.Log($"{parts.Count}");
foreach (PartInfo part in parts)
{
if (part.partTransform == null || !part.partTransform.gameObject.activeInHierarchy)
{
continue;
}
if (!IsTransformValid(part.partTransform))
{
continue;
}
activeParts++;
// 创建向外平移的动画
var moveTween = part.partTransform.DOMove(
part.detachedWorldPosition,
animationDuration
).SetEase(moveEase);
// 添加到序列
currentAnimationSequence.Join(moveTween);
}
if (activeParts == 0)
{
Debug.LogError("Split: 没有有效的部件可以拆分");
currentAnimationSequence = null;
return;
}
// 设置动画完成回调
currentAnimationSequence.OnComplete(() =>
{
isSplit = true;
currentAnimationSequence = null;
});
currentAnimationSequence.Play();
}
/// <summary>
/// 合并所有部件
/// 动画流程:所有部件从当前位置平移回到组装位置
/// </summary>
public void Merge()
{
if (parts.Count == 0)
{
Debug.LogError("Merge: 请先调用 SetTarget 设置目标");
return;
}
if (!isSplit)
{
Debug.Log("Merge: 已处于合并状态");
return;
}
if (IsInCooldown())
{
Debug.Log("Merge: 操作过于频繁");
return;
}
StopCurrentAnimation();
lastOperationTime = Time.time;
// 使用DOTween创建动画序列
currentAnimationSequence = DOTween.Sequence();
int activeParts = 0;
foreach (PartInfo part in parts)
{
if (part.partTransform == null || !part.partTransform.gameObject.activeInHierarchy)
{
continue;
}
if (!IsTransformValid(part.partTransform))
{
continue;
}
activeParts++;
// 创建回到组装位置的动画
var moveTween = part.partTransform.DOMove(
part.assembledWorldPosition,
animationDuration
).SetEase(moveEase);
// 添加到序列
currentAnimationSequence.Join(moveTween);
}
if (activeParts == 0)
{
Debug.LogError("Merge: 没有有效的部件可以合并");
currentAnimationSequence = null;
return;
}
// 设置动画完成回调
currentAnimationSequence.OnComplete(() =>
{
isSplit = false;
currentAnimationSequence = null;
});
currentAnimationSequence.Play();
}
/// <summary>
/// 切换拆分/合并状态
/// 如果当前是合并状态则拆分,如果是拆分状态则合并
/// </summary>
public void Toggle()
{
if (IsInCooldown())
{
Debug.Log("Toggle: 操作过于频繁");
return;
}
if (isSplit)
{
Merge();
}
else
{
Split();
}
}
/// <summary>
/// 停止当前动画
/// 立即停止所有正在进行的动画
/// </summary>
public void StopCurrentAnimation()
{
if (currentAnimationSequence != null && currentAnimationSequence.IsActive())
{
currentAnimationSequence.Kill();
currentAnimationSequence = null;
}
}
/// <summary>
/// 立即完成拆分
/// 不播放动画,直接跳转到拆分状态
/// </summary>
public void InstantSplit()
{
StopCurrentAnimation();
CalculateCenterPoint();
CalculateDetachedPositions();
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;
}
/// <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;
}
/// <summary>
/// 获取当前状态
/// 返回true-已拆分false-已合并
/// </summary>
public bool GetCurrentState()
{
return isSplit;
}
/// <summary>
/// 是否正在播放动画
/// 返回true-动画播放中false-无动画播放
/// </summary>
public bool IsAnimating()
{
return currentAnimationSequence != null && currentAnimationSequence.IsActive();
}
/// <summary>
/// 设置动画总时长
/// 参数duration - 动画时长最小值0.1
/// </summary>
public void SetAnimationDuration(float duration)
{
animationDuration = Mathf.Max(0.1f, duration);
}
/// <summary>
/// 设置散开距离
/// 参数distance - 散开距离最小值0.5
/// </summary>
public void SetSpreadDistance(float distance)
{
spreadDistance = Mathf.Max(0.5f, distance);
if (parts.Count > 0 && targetParent != null)
{
CalculateDetachedPositions();
}
}
/// <summary>
/// 获取部件数量
/// 返回:收集到的部件数量
/// </summary>
public int GetPartCount()
{
return parts.Count;
}
#endregion
#region
/// <summary>
/// 收集所有直接子物体
/// 将目标父节点的所有直接子物体收集为部件
/// </summary>
private void CollectAllChildren(Transform parent)
{
if (parent == null) return;
// 收集所有直接子物体
foreach (Transform child in parent)
{
if (child == null) continue;
PartInfo part = new PartInfo
{
partTransform = child
};
parts.Add(part);
}
}
/// <summary>
/// 递归收集所有有效的部件
/// 参数parent - 开始收集的父节点
/// </summary>
private void CollectAllChildrenRecursive(Transform parent)
{
if (parent == null) return;
// 检查当前节点是否需要跳过
if (ShouldSkipTransform(parent))
{
// 跳过当前节点,但递归检查其子节点
foreach (Transform child in parent)
{
if (!includeInactive && !child.gameObject.activeSelf) continue;
CollectAllChildrenRecursive(child);
}
return;
}
// 当前节点是有效的部件
PartInfo part = new PartInfo
{
partTransform = parent
};
parts.Add(part);
}
/// <summary>
/// 判断是否应该跳过此变换节点
/// 返回true-应该跳过false-应该作为部件收集
/// </summary>
private bool ShouldSkipTransform(Transform transform)
{
if (transform == null) return true;
// 如果设置了跳过空变换节点,则检查当前节点是否为空
if (skipEmptyTransforms && IsEmptyTransform(transform))
{
return true; // 跳过空节点
}
// 检查是否有Renderer组件
Renderer renderer = transform.GetComponent<Renderer>();
if (renderer != null)
{
return false; // 有Renderer不跳过
}
// 检查是否有MeshFilter或SkinnedMeshRenderer
MeshFilter meshFilter = transform.GetComponent<MeshFilter>();
SkinnedMeshRenderer skinnedMeshRenderer = transform.GetComponent<SkinnedMeshRenderer>();
if (meshFilter != null || skinnedMeshRenderer != null)
{
return false; // 有模型组件,不跳过
}
// 如果没有Renderer但有子节点则可能是一个容器节点
if (transform.childCount > 0)
{
return true; // 有子节点且自身没有渲染,跳过
}
// 没有Renderer没有子节点检查是否有其他重要的3D组件
Collider collider = transform.GetComponent<Collider>();
Rigidbody rigidbody = transform.GetComponent<Rigidbody>();
if (collider != null || rigidbody != null)
{
return false; // 有其他3D组件不跳过
}
// 默认不跳过
return false;
}
/// <summary>
/// 判断变换节点是否为空
/// 空节点没有Renderer没有MeshFilter没有SkinnedMeshRenderer
/// </summary>
private bool IsEmptyTransform(Transform transform)
{
if (transform == null) return true;
// 检查是否有Renderer组件
if (transform.GetComponent<Renderer>() != null) return false;
// 检查是否有MeshFilter
if (transform.GetComponent<MeshFilter>() != null) return false;
// 检查是否有SkinnedMeshRenderer
if (transform.GetComponent<SkinnedMeshRenderer>() != null) return false;
// 检查是否有Terrain
if (transform.GetComponent<Terrain>() != null) return false;
return true;
}
/// <summary>
/// 检查变换组件是否有效
/// 参数t - 要检查的变换组件
/// 返回true-有效false-无效
/// </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 CalculateCenterPoint()
{
if (targetParent == null || parts.Count == 0)
{
centerPoint = Vector3.zero;
return;
}
// 使用所有部件的平均位置作为中心点
Vector3 sum = Vector3.zero;
int count = 0;
foreach (PartInfo part in parts)
{
if (part.partTransform == null) continue;
sum += part.assembledWorldPosition;
count++;
}
if (count > 0)
{
centerPoint = sum / count;
}
else
{
centerPoint = targetParent.position;
}
}
/// <summary>
/// 初始化部件数据
/// 记录部件的初始位置和旋转
/// </summary>
private void InitializeParts()
{
if (parts.Count == 0) return;
foreach (PartInfo part in parts)
{
if (part.partTransform == null) continue;
part.assembledWorldPosition = part.partTransform.position;
part.assembledWorldRotation = part.partTransform.rotation;
}
// 计算中心点
CalculateCenterPoint();
// 计算远离中心点的位置
CalculateDetachedPositions();
isSplit = false;
}
/// <summary>
/// 计算远离中心点的位置
/// 每个部件沿着从中心点到部件当前位置的方向向外移动固定距离
/// </summary>
private void CalculateDetachedPositions()
{
if (parts.Count == 0) return;
for (int i = 0; i < parts.Count; i++)
{
if (parts[i].partTransform == null) continue;
// 计算从中心点到部件当前位置的方向
Vector3 direction = (parts[i].assembledWorldPosition - centerPoint).normalized;
// 如果方向为零向量(部件就在中心点),则使用随机方向
if (direction == Vector3.zero)
{
direction = Random.onUnitSphere;
}
// 计算远离中心点的位置
parts[i].detachedWorldPosition = centerPoint + direction * spreadDistance;
// 保持部件原有的旋转
parts[i].detachedWorldRotation = parts[i].assembledWorldRotation;
}
}
/// <summary>
/// 检查是否在操作冷却时间内
/// 防止频繁点击导致动画冲突
/// 返回true-冷却中false-可操作
/// </summary>
private bool IsInCooldown()
{
float timeSinceLastOperation = Time.time - lastOperationTime;
return timeSinceLastOperation < CLICK_COOLDOWN;
}
#endregion
#region
/// <summary>
/// 销毁时清理资源
/// 停止所有动画,防止内存泄漏
/// </summary>
private void OnDestroy()
{
StopCurrentAnimation();
}
#endregion
}