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

Unity中基于稳定局部坐标系给方向向量应用Yaw和Pitch的方法

解决VR火箭手机制中身体转动后操控失效的问题

问题核心

你的代码中Vector3 launchDirection = Quaternion.Euler(accumulatedPitch, accumulatedYaw, 0) * baseForward直接在世界空间应用欧拉角旋转初始方向,只有当初始方向与世界正前方(0,0,1)对齐时才有效。玩家转动身体后,全局欧拉角的旋转轴不再匹配发射时的手部局部坐标系,导致操控逻辑完全错位。

解决方案思路

  1. 保存发射时手部参考Transform的初始全局旋转,以此作为局部操控坐标系的基准
  2. 将累积的Yaw/Pitch增量转换为局部坐标系内的旋转,而非全局空间
  3. 通过初始旋转将局部旋转转换回世界空间,得到正确的火箭飞行方向

修改后的完整代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LeftHandControllerScript : MonoBehaviour
{
    [Header("Rocket Hand Settings")]
    public float rocketForce = 50f;
    public float accelerationTime = 2f; // Time to reach full speed

    private Rigidbody rb;
    private float elapsedTime = 0f;

    [Header("Direction Settings")]
    public Transform handTransform;
    public Transform aimDirectionReference;
    public float rotationSmoothSpeed = 5f;
    public float rotationSensitivity = 0.01f;
    public float yawDeadZone = 2f;
    public float pitchDeadZone = 2f; // degrees
    
    private Quaternion initialReferenceRotation;
    private float accumulatedYaw = 0f;
    private float accumulatedPitch = 0f;
    

    void Start()
    {
        if (aimDirectionReference != null){
            initialReferenceRotation = aimDirectionReference.rotation;
        }
        
        rb = GetComponent<Rigidbody>();
        if (rb == null)
        {
            Debug.LogError("No Rigidbody found on Left Hand!");
        }

        elapsedTime = 0f; // Reset time when launched
        Debug.Log("Rocket hand launched!");
    }

    void Update()
    {
        elapsedTime += Time.deltaTime;

        // Calculate acceleration
        float accelerationFactor = Mathf.Clamp01(elapsedTime / accelerationTime);
        float currentSpeed = rocketForce * accelerationFactor;

        // 核心修正:基于初始局部坐标系计算飞行方向
        // 1. 创建局部坐标系内的旋转(Yaw绕局部Y轴,Pitch绕局部X轴)
        Quaternion localRotation = Quaternion.Euler(accumulatedPitch, accumulatedYaw, 0);
        // 2. 将局部旋转转换为世界空间旋转(初始基准旋转 + 局部操控旋转)
        Quaternion worldRotation = initialReferenceRotation * localRotation;
        // 3. 得到最终飞行方向
        Vector3 launchDirection = worldRotation * Vector3.forward;

        rb.velocity = launchDirection * currentSpeed;

        // 更新Yaw/Pitch增量(基于初始参考的局部变化)
        if (aimDirectionReference != null)
        {
            Quaternion currentRotation = aimDirectionReference.rotation;
            // 计算相对于初始状态的旋转增量(世界空间转局部空间)
            Quaternion deltaRotation = Quaternion.Inverse(initialReferenceRotation) * currentRotation;
            
            Vector3 deltaEuler = deltaRotation.eulerAngles;
            // 归一化到[-180, 180]范围
            deltaEuler.x = Mathf.DeltaAngle(0, deltaEuler.x);
            deltaEuler.y = Mathf.DeltaAngle(0, deltaEuler.y);

            // 处理Yaw死区与灵敏度
            float yawChange = Mathf.Abs(deltaEuler.y) < yawDeadZone ? 0f :
                (deltaEuler.y > 0 ? deltaEuler.y - yawDeadZone : deltaEuler.y + yawDeadZone);
            yawChange *= rotationSensitivity;
            accumulatedYaw += yawChange;

            // 处理Pitch死区与灵敏度
            float pitchChange = Mathf.Abs(deltaEuler.x) < pitchDeadZone ? 0f :
                (deltaEuler.x > 0 ? deltaEuler.x - pitchDeadZone : deltaEuler.x + pitchDeadZone);
            pitchChange *= rotationSensitivity;
            accumulatedPitch += pitchChange;

            // 限制Pitch范围,避免过度垂直翻转
            accumulatedPitch = Mathf.Clamp(accumulatedPitch, -60f, 60f);
        }

        // 更新手部朝向(平滑过渡到飞行方向)
        if (handTransform != null)
        {
            Quaternion targetRotation = Quaternion.LookRotation(launchDirection, Vector3.up);
            handTransform.rotation = Quaternion.Slerp(
                handTransform.rotation,
                targetRotation,
                rotationSmoothSpeed * Time.deltaTime
            );
        }
    }
}

关键修改点说明

  1. 移除baseForward变量:直接通过初始旋转+局部旋转推导世界空间方向,避免单独存储方向带来的坐标系错位问题
  2. 修正旋转计算逻辑:
    • 先计算localRotation:在发射时的局部坐标系内应用Yaw/Pitch增量
    • 再通过initialReferenceRotation * localRotation将局部旋转转换为世界空间旋转,确保操控始终基于发射时的手部朝向
  3. 调整delta旋转计算:用Quaternion.Inverse(initialReferenceRotation) * currentRotation将当前世界旋转转换为相对于初始状态的局部旋转增量,确保Yaw/Pitch的变化是基于发射时的坐标系,而非全局空间

这样修改后,无论玩家转动身体到哪个方向,火箭的操控都会保持与发射时手部朝向的相对关系,彻底解决身体转动后操控失效的问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 03:57:17