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

Unity VR轮子绕自身Z轴旋转脚本的问题解决与实现

Unity 绕自身Z轴旋转的可抓取轮子实现方案

需求

实现Unity中仅绕Z轴旋转的轮子交互脚本,核心要求:

  • 可抓取轮子使其跟随手部运动
  • 松开后再次从任意位置抓取,不会重置轮子当前旋转状态

初始尝试与问题

初始编写的LookAtScript_v2脚本如下:

public class LookAtScript_v2 : MonoBehaviour
{
    // Testing vars.
    [SerializeField]
    TextMeshPro DegreesFromUpToSnapText; // <- Text used for debugging purposes only.

    // Visible attributes.
    [SerializeField]
    private Transform target; // <- Variable that represents the target we're following.

    [SerializeField]
    private bool watchingTheObject = true; // <- Variable that mocks the grabbing and letting go of the object.
    
    [SerializeField]
    [Range(1f, 36f)] // <- Change here the range of snaps if needed (future functionality).
    private int SnapPoints = 1;

    // Background attributes.
        // Variables used for rotating the wheel on one axis without it always snapping towards the hand.
    private float alphaAngle = 0;

    // Let go of the wheel only once tracker
    bool letGo = true;

    void Update()
    {
        if(target != null && watchingTheObject)
        {
            letGo = false;

            Vector2 vectorToTarget = target.position - transform.position;
            // rotatedVector is a vector that points in "the direction of our target" - the angle alpha (angle between our target vector and the direction
            // our wheel is already facing). 
            Vector2 rotatedVector;

            // Calculate the angle alpha only the first time we grab the wheel, afterwards it should be constant
            if (alphaAngle == 0)
            {
                alphaAngle = Vector2.SignedAngle(vectorToTarget, transform.right);
            }
            
            // We use rotatedVector to point in the direction where the x axis is currently looking
            // this allows us to rotate the wheel from its current position, without it always rotating to face our target.
            rotatedVector = Quaternion.AngleAxis(alphaAngle, Vector3.forward) * vectorToTarget;

            // Use transform.right to look in the same direction as our rotatedVector.
            transform.right = rotatedVector.normalized;
        }
        else if(!watchingTheObject && !letGo)
        {
            alphaAngle = 0;
            letGo = true;
        }
    }
}

遇到的问题

该脚本使用Vector2进行计算,导致轮子始终朝向世界Z轴,无法适配任意朝向的父物体,也无法基于局部坐标保持原有旋转逻辑。

解决方案

层级结构调整

需创建三层物体结构:

  • 父物体(用于整体定位)
    • 枢轴点(Pivot Point,作为旋转中心,需与轮子中心重合)
    • 轮子本体(Valve,挂载脚本,仅绕自身Z轴旋转)

最终可用脚本 LookAtScript_v3

using UnityEngine;

public class LookAtScript_v3 : MonoBehaviour
{
    [SerializeField]
    private Transform target; // 跟随目标,替代VR手部

    [SerializeField]
    private bool watchingTheObject = false; // 模拟抓取/松开状态(无VR设备时测试用)

    [SerializeField]
    private Transform pivotPoint; // 轮子旋转的枢轴中心点

    private float alphaAngle = 0; // 轮子当前朝向与目标方向的初始夹角
    private float newAngle = 0;
    private float previousAngle = 0; // 用于判断旋转方向(顺时针/逆时针)

    private float rotationSpeed = 400f; // 旋转平滑速度

    void Update()
    {
        if(target != null && watchingTheObject)
        {
            Vector3 vectorToTarget = target.position - pivotPoint.position;
            Vector3 vectorToTargetLocal = pivotPoint.transform.InverseTransformDirection(vectorToTarget);
            vectorToTargetLocal = Quaternion.Euler(pivotPoint.rotation.eulerAngles) * vectorToTargetLocal;
            
            // 首次抓取时计算初始夹角
            if (alphaAngle == 0)
            {
                alphaAngle = Vector3.SignedAngle(vectorToTargetLocal, transform.up, pivotPoint.forward);
            }

            Vector3 rotatedVectorToTarget = Quaternion.AngleAxis(alphaAngle, transform.forward) * vectorToTargetLocal;

            // 获取目标旋转角度(绕局部Z轴)
            float targetAngle = Vector3.SignedAngle(pivotPoint.up, rotatedVectorToTarget, pivotPoint.forward);

            // 获取轮子当前绕局部Z轴的角度
            float currentAngle = transform.localEulerAngles.z;
            
            // 将角度转换为-180~180范围,避免旋转跳变
            if (currentAngle - alphaAngle > 180) currentAngle -= 360;
            if (targetAngle - alphaAngle > 180) targetAngle -= 360;
            
            // 平滑旋转到目标角度
            newAngle = Mathf.MoveTowardsAngle(currentAngle, targetAngle, rotationSpeed * Time.deltaTime);

            // 应用旋转(仅绕局部Z轴)
            transform.localEulerAngles = new Vector3(0, 0, newAngle);

            previousAngle = newAngle;
        }
        else if(!watchingTheObject)
        {
            alphaAngle = 0;
        }
    }
}

效果说明

该脚本可实现:

  • 轮子仅绕自身局部Z轴旋转
  • 松开后再次抓取,会保留当前旋转状态
  • 不受父物体整体朝向影响,适配任意场景布局

内容的提问来源于stack exchange,提问作者Viktor291

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:44:56