先看下最终要实现的效果:
当查找这个问题的资料时,发现多数的方案都是将物体设置为平台的子对象。
但是如果平台是非均匀缩放时,物体在移动或旋转时就会发生变形。
参考:Unity中父对象是非均匀缩放时出现倾斜或剪切现象
那有没有其他方法呢?
其实我们也可以不将物体设置为平台的子对象,而是通过管理刚体的实现。
先看代码:
using UnityEngine;
using System.Collections.Generic;// CarryRigidbodies类负责让一系列Rigidbody对象跟随Unity GameObject移动。
public class CarryRigidbodies : MonoBehaviour
{// 存储所有应随本GameObject移动的Rigidbody对象的列表。public List<Rigidbody> rigidbodies = new List<Rigidbody>();// 存储上一帧物体的位置,用于计算移动距离。public Vector3 LastPosition;// 缓存当前GameObject的Transform组件。Transform _transform;// Start方法在脚本实例被激活时调用一次。void Start(){_transform = transform; // 初始化_transform为当前GameObject的Transform。LastPosition = _transform.position; // 初始化LastPosition为当前位置。}// LateUpdate在每一帧的Update函数调用之后调用。void LateUpdate(){// 如果列表中有Rigidbody对象,则处理它们的移动。if(rigidbodies.Count > 0){// 遍历所有的Rigidbody。for(int i = 0; i < rigidbodies.Count; i++){Rigidbody rb = rigidbodies[i];// 计算自上一帧以来的位置变化。Vector3 velocity = _transform.position - LastPosition;// 将位置变化应用到每个Rigidbody上。rb.transform.Translate(velocity, _transform);}}// 更新LastPosition为当前帧的位置。LastPosition = _transform.position;}// 当发生碰撞开始时调用。private void OnCollisionEnter(Collision other) {// 尝试从碰撞对象中获取Rigidbody组件。Rigidbody rb = other.collider.GetComponent<Rigidbody>();if(rb != null){// 如果存在Rigidbody,则添加到列表中。Add(rb);} }// 当碰撞结束时调用。private void OnCollisionExit(Collision other) {// 尝试从碰撞对象中获取Rigidbody组件。Rigidbody rb = other.collider.GetComponent<Rigidbody>();if(rb != null){// 如果存在Rigidbody,则从列表中移除。Remove(rb);} }// 添加一个Rigidbody到列表中,如果它尚未存在于列表中。void Add(Rigidbody rb){if(!rigidbodies.Contains(rb)){rigidbodies.Add(rb);}}// 从列表中移除一个Rigidbody,如果它存在于列表中。void Remove(Rigidbody rb){if(rigidbodies.Contains(rb)){rigidbodies.Remove(rb);}}
}
上面脚本的核心实现原理是通过监听一个物体的位置变化,并将这个变化应用到与其相连的其他物体上,使得这些物体可以同步移动。这里面涉及的核心概念和方法包括:
位置追踪:
- 脚本通过记录物体的当前位置和上一帧的位置来计算位置的变化量(即位移向量)。这个位置变化量反映了物体在每一帧之间的移动。
同步位移:
- 使用Unity的Transform.Translate方法,将计算出的位移向量应用到所有附着的Rigidbody上。这个方法允许物体根据相对于其自身或另一个变换(在此案例中是脚本所依附的物体的变换)的位移向量进行移动。
- 在LateUpdate方法中执行这个位移同步,是因为LateUpdate在所有Update方法之后执行,可以获取到所有物体的最终位置更新后,再统一进行位移操作,确保同步性和位置的准确性。
碰撞侦测与动态列表管理:
- 使用OnCollisionEnter和OnCollisionExit方法动态地管理附着的Rigidbody列表。这两个方法在Unity物理引擎处理碰撞的开始和结束时被调用。
- 当一个物体开始与脚本附着的物体碰撞时,OnCollisionEnter被触发,脚本检查碰撞物体是否有Rigidbody组件,如果有,则加入到管理列表中。
- 当碰撞结束时,OnCollisionExit触发,对应的Rigidbody从列表中移除。
性能考虑:
- 脚本中使用了变量_transform来缓存所附加GameObject的Transform组件的引用,这是为了减少在每次调用时重复获取这个组件的开销,优化性能。
那如何实现物体跟随平台同步旋转呢?
为了让附着的物体能够随着平台同步旋转,我们需要修改代码以跟踪平台的旋转变化,并将这些变化应用到所有附着的Rigidbody上。这可以通过计算每帧的旋转差异并应用这个差异来实现。下面是如何改进原有代码来支持旋转同步的详细步骤:
1. 添加跟踪旋转的变量: 在类中增加一个变量来存储上一帧的旋转:
public Quaternion LastRotation;
2. 初始化旋转变量: 在Start方法中初始化这个变量:
void Start()
{_transform = transform;LastPosition = _transform.position;LastRotation = _transform.rotation;
}
3. 计算旋转差异并应用: 在LateUpdate方法中,计算旋转的差异并应用到所有的Rigidbody上:
void LateUpdate()
{if (rigidbodies.Count > 0){Vector3 velocity = _transform.position - LastPosition;Quaternion rotationDelta = _transform.rotation * Quaternion.Inverse(LastRotation);for (int i = 0; i < rigidbodies.Count; i++){Rigidbody rb = rigidbodies[i];rb.transform.Translate(velocity, Space.World);rb.transform.rotation = rotationDelta * rb.transform.rotation;}}LastPosition = _transform.position;LastRotation = _transform.rotation;
}
这段代码中,旋转差异rotationDelta是通过计算当前旋转与上一帧旋转的相对旋转得到的。这个旋转差异然后应用于所有附着的Rigidbody上,更新它们的旋转状态。
通过这种方式,任何附着的Rigidbody不仅会随着平台的移动而移动,也会随着平台的旋转而旋转,实现完全的同步。这对于制作需要物体随着移动平台旋转和平移的游戏场景(如旋转的平台或转盘等)非常有用。