You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中如何实现Transform绕目标在X/Z轴旋转并保持随机高度?

Unity绕任意轴旋转时保持距离与随机高度的解决方案

问题场景

原本代码固定绕Y轴((0,1,0))旋转时,对象能正常绕目标水平旋转并随机改变高度;但将旋转轴改为Z轴((0,0,1))后,对象会逐渐远离目标,无法保持绕转状态,需要实现绕X/Z轴旋转时仍能维持到目标的固定距离,并保留随机高度变化的功能。

问题根源

  1. 距离维护缺失:原代码仅在初始或radius变更时设置位置,旋转过程中没有强制保持对象到目标的距离为radius,当旋转轴改变后,旋转动作会逐渐拉开对象与目标的距离。
  2. 高度方向固定:原高度逻辑硬编码修改Y轴位置,但绕Z轴旋转时,“高度”应该对应X轴方向,固定Y轴会导致运动方向与旋转轴冲突,进一步加剧偏移。
  3. 初始位置与移动目标错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 19:35:46