Unity中基于稳定局部坐标系给方向向量应用Yaw和Pitch的方法
解决VR火箭手机制中身体转动后操控失效的问题
问题核心
你的代码中Vector3 launchDirection = Quaternion.Euler(accumulatedPitch, accumulatedYaw, 0) * baseForward直接在世界空间应用欧拉角旋转初始方向,只有当初始方向与世界正前方(0,0,1)对齐时才有效。玩家转动身体后,全局欧拉角的旋转轴不再匹配发射时的手部局部坐标系,导致操控逻辑完全错位。
解决方案思路
- 保存发射时手部参考Transform的初始全局旋转,以此作为局部操控坐标系的基准
- 将累积的Yaw/Pitch增量转换为局部坐标系内的旋转,而非全局空间
- 通过初始旋转将局部旋转转换回世界空间,得到正确的火箭飞行方向
修改后的完整代码
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 ); } } }
关键修改点说明
- 移除baseForward变量:直接通过初始旋转+局部旋转推导世界空间方向,避免单独存储方向带来的坐标系错位问题
- 修正旋转计算逻辑:
- 先计算
localRotation:在发射时的局部坐标系内应用Yaw/Pitch增量 - 再通过
initialReferenceRotation * localRotation将局部旋转转换为世界空间旋转,确保操控始终基于发射时的手部朝向
- 先计算
- 调整delta旋转计算:用
Quaternion.Inverse(initialReferenceRotation) * currentRotation将当前世界旋转转换为相对于初始状态的局部旋转增量,确保Yaw/Pitch的变化是基于发射时的坐标系,而非全局空间
这样修改后,无论玩家转动身体到哪个方向,火箭的操控都会保持与发射时手部朝向的相对关系,彻底解决身体转动后操控失效的问题。
内容的提问来源于stack exchange,提问作者Amit Tzur
相关产品推荐
相关产品推荐

