Unity中如何实现Transform绕目标在X/Z轴旋转并保持随机高度?
Unity绕任意轴旋转时保持距离与随机高度的解决方案
问题场景
原本代码固定绕Y轴((0,1,0))旋转时,对象能正常绕目标水平旋转并随机改变高度;但将旋转轴改为Z轴((0,0,1))后,对象会逐渐远离目标,无法保持绕转状态,需要实现绕X/Z轴旋转时仍能维持到目标的固定距离,并保留随机高度变化的功能。
问题根源
- 距离维护缺失:原代码仅在初始或
radius变更时设置位置,旋转过程中没有强制保持对象到目标的距离为radius,当旋转轴改变后,旋转动作会逐渐拉开对象与目标的距离。 - 高度方向固定:原高度逻辑硬编码修改Y轴位置,但绕Z轴旋转时,“高度”应该对应X轴方向,固定Y轴会导致运动方向与旋转轴冲突,进一步加剧偏移。
- 初始位置与移动目标错误:
Start方法和MoveTo协程的目标位置是固定的(0,0,radius),仅适用于Y轴旋转场景,其他轴下这个位置无法保证到目标的正确距离。
修改后的完整代码
using System.Collections; using System.Collections.Generic; using UnityEngine; public class RotateAroundTarget : MonoBehaviour { public Transform target; public Vector3 axisToRotate = Vector3.up; public float verticalSpeed = 20; public float radius = 10; public bool moveToRadius = false; private float targetOffset; private float oldRadius; private bool moveToRadiusCoroutineOnce = true; // 存储垂直于旋转轴的方向向量,用于定义"高度"方向 private Vector3 verticalDirection; void Start() { // 初始化垂直方向:取旋转轴的垂直向量(这里用叉乘生成一个垂直方向) verticalDirection = GetPerpendicularAxis(axisToRotate).normalized; // 初始化目标偏移值(对应原逻辑的targetHeight) targetOffset = GetNewTargetOffset(); oldRadius = radius; if (!moveToRadius) { // 基于旋转轴计算初始位置:目标位置 + 旋转轴垂直的径向向量 * radius Vector3 radialDirection = Vector3.Cross(axisToRotate, verticalDirection).normalized; transform.position = target.position + radialDirection * radius; } } void Update() { if (moveToRadius && moveToRadiusCoroutineOnce) { // 计算到目标的正确径向位置,作为移动目标 Vector3 radialDirection = Vector3.Cross(axisToRotate, verticalDirection).normalized; StartCoroutine(MoveTo(target.position + radialDirection * radius, 5f)); moveToRadiusCoroutineOnce = false; } if (radius != oldRadius) { oldRadius = radius; moveToRadiusCoroutineOnce = true; } // 执行绕目标旋转 transform.RotateAround(target.position, axisToRotate, 20 * Time.deltaTime); // 强制保持到目标的距离为radius Vector3 toTarget = transform.position - target.position; transform.position = target.position + toTarget.normalized * radius; // 处理垂直于旋转轴的偏移(原"高度"逻辑) float maxOffsetChange = verticalSpeed * Time.deltaTime; // 计算当前在垂直方向上的偏移量 float currentOffset = Vector3.Dot(transform.position - target.position, verticalDirection); float targetDelta = targetOffset - currentOffset; if (maxOffsetChange >= Mathf.Abs(targetDelta)) { // 移动到目标偏移位置并生成新的目标值 Vector3 radialDirection = Vector3.Cross(axisToRotate, verticalDirection).normalized; // 计算新位置:目标 + 径向向量*radius + 垂直方向*targetOffset transform.position = target.position + radialDirection * radius + verticalDirection * targetOffset; targetOffset = GetNewTargetOffset(); } else { // 逐步移动到目标偏移 float allowedChange = Mathf.Clamp(targetDelta, -maxOffsetChange, maxOffsetChange); transform.position += verticalDirection * allowedChange; // 再次修正距离,避免移动导致偏离 toTarget = transform.position - target.position; transform.position = target.position + toTarget.normalized * radius; } } // 生成随机偏移值(替代原GetNewTargetHeight) private float GetNewTargetOffset() { return Random.Range(0, 5f); } // 获取与给定轴垂直的任意轴 private Vector3 GetPerpendicularAxis(Vector3 axis) { if (axis == Vector3.up) return Vector3.forward; if (axis == Vector3.forward) return Vector3.up; return Vector3.Cross(axis, Vector3.up).normalized; } private IEnumerator MoveTo(Vector3 endPos, float moveDurationInSeconds) { var fromPosition = transform.position; var toPosition = endPos; var passedTime = 0f; while (passedTime < moveDurationInSeconds) { var lerpFactor = passedTime / moveDurationInSeconds; var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor); transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor); passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime); yield return null; } } }
关键修改说明
- 动态垂直方向:通过
GetPerpendicularAxis方法生成与旋转轴垂直的方向向量,替代原固定的Y轴作为“高度”方向,适配任意旋转轴。 - 强制距离维护:每次旋转和高度调整后,都通过
normalized * radius修正对象到目标的距离,确保不会偏离。 - 自适应初始位置:初始位置和
MoveTo的目标位置基于旋转轴计算,保证初始状态下对象就处于正确的径向位置。 - 偏移逻辑重构:将原Y轴高度修改改为基于垂直方向的偏移计算,确保“高度”变化始终垂直于旋转平面,不会干扰绕转动作。
内容的提问来源于stack exchange,提问作者Daniel Lip
相关产品推荐
相关产品推荐

