Unity橱柜门交互:全局坐标转局部坐标代码适配问题
橱柜门VR交互适配任意橱柜朝向的解决方案
在Unity(C#)VR厨房场景中实现橱柜门跟随手部开合的交互时,原脚本仅在橱柜坐标轴与世界坐标轴完全对齐时生效——抽屉需要橱柜Forward与世界Forward一致,柜门需要橱柜Y轴无倾斜才能正常工作。
原问题代码
public class DoorPull : XRBaseInteractable { [Header("Required variables")] public GameObject door; public float openDegree = 90f; public float startingOpenDegree; public float handOffset = 90f; public bool hingeRight; private Vector3 closedRotation; private Vector3 openRotation; private IXRSelectInteractor hand; // Start is called before the first frame update void Start() { closedRotation = door.transform.localRotation.eulerAngles; //sets the closed and max open positions. openRotation = closedRotation; if (hingeRight) { openRotation.y = closedRotation.y - openDegree; } else { openRotation.y = closedRotation.y + openDegree; } //makes sure the door can't start further open than its max open position. if (startingOpenDegree > openDegree) { startingOpenDegree = openDegree; } //starts the door open at users starting open degree var startingOpenRotation = closedRotation; if (hingeRight) { startingOpenRotation.y -= startingOpenDegree; } else { startingOpenRotation.y += startingOpenDegree; } door.transform.localRotation = Quaternion.Euler(startingOpenRotation); } //------------------------above this line works no matter the position or rotation of the cabinet, below this line must be aligned with world y axis to work------------------------------ // Update is called once per frame void Update() { if (interactorsSelecting.Count > 0) { hand = interactorsSelecting[0]; Vector3 lookHere = hand.transform.position; lookHere.y = door.transform.position.y; door.transform.LookAt(lookHere, door.transform.up); var temp = door.transform.rotation.eulerAngles; if (hingeRight) { temp.y -= handOffset; //will have code to stop in open and closed positions } else { temp.y += handOffset; //will have code to stop in open and closed positions } door.transform.rotation = Quaternion.Euler(temp); } } }
解决方案
核心思路是在橱柜的局部空间内计算手部相对位置与旋转,彻底摆脱世界坐标轴对齐的限制。以下是修改后的Update方法及关键说明:
修改后的Update方法
// Update is called once per frame void Update() { if (interactorsSelecting.Count > 0) { hand = interactorsSelecting[0]; // 获取橱柜(柜门父物体)的Transform,确保计算基于橱柜局部空间 Transform cabinetTransform = door.transform.parent; if (cabinetTransform == null) { cabinetTransform = door.transform; } // 将手部世界位置转换为橱柜局部空间位置 Vector3 localHandPos = cabinetTransform.InverseTransformPoint(hand.transform.position); // 忽略局部Y轴高度,仅保留XZ平面位置(对应柜门开合的水平方向) localHandPos.y = cabinetTransform.InverseTransformPoint(door.transform.position).y; // 计算局部空间内柜门指向手部的水平方向 Vector3 localLookDir = localHandPos - cabinetTransform.InverseTransformPoint(door.transform.position); localLookDir.y = 0; if (localLookDir.magnitude < 0.01f) return; // 避免方向为零时的异常 // 转换为局部空间下的旋转(围绕局部Y轴) Quaternion targetLocalRot = Quaternion.LookRotation(localLookDir, cabinetTransform.up); // 应用手部偏移,调整旋转对齐 if (hingeRight) { targetLocalRot *= Quaternion.Euler(0, -handOffset, 0); } else { targetLocalRot *= Quaternion.Euler(0, handOffset, 0); } // 处理欧拉角0-360循环问题,转换为-180~180范围便于角度限制 float currentYRot = targetLocalRot.eulerAngles.y; if (currentYRot > 180) currentYRot -= 360; // 确定旋转角度的上下限(基于闭合状态和最大打开角度) float minYRot, maxYRot; if (hingeRight) { minYRot = closedRotation.y - openDegree; maxYRot = closedRotation.y; } else { minYRot = closedRotation.y; maxYRot = closedRotation.y + openDegree; } // 同样转换为-180~180范围,避免角度循环导致的限制错误 if (minYRot > 180) minYRot -= 360; if (maxYRot > 180) maxYRot -= 360; // 限制旋转角度在合法范围内 float clampedYRot = Mathf.Clamp(currentYRot, Mathf.Min(minYRot, maxYRot), Mathf.Max(minYRot, maxYRot)); // 构建最终局部旋转,保持X、Z轴与初始闭合状态一致(避免不必要倾斜) Quaternion finalLocalRot = Quaternion.Euler(closedRotation.x, clampedYRot, closedRotation.z); // 应用局部旋转,适配任意橱柜朝向 door.transform.localRotation = finalLocalRot; } }
关键修改说明
- 局部空间转换:使用
cabinetTransform.InverseTransformPoint将手部世界坐标转为橱柜局部坐标,确保计算不受世界坐标轴影响,适配任意橱柜旋转状态。 - 水平方向约束:忽略局部Y轴高度,仅在XZ平面计算手部方向,保证柜门开合始终沿水平方向进行。
- 欧拉角循环处理:将0-360范围的欧拉角转换为-180~180,避免角度循环导致的限制逻辑错误。
- 旋转范围限制:严格约束柜门旋转角度在闭合到最大打开角度之间,防止过度旋转。
- 局部旋转应用:最终使用
localRotation设置柜门旋转,确保旋转基于橱柜的局部坐标系,而非世界坐标系。
内容的提问来源于stack exchange,提问作者Jeremy Gillmore
相关产品推荐
相关产品推荐

