Unity父物体旋转时保持脚部IK目标相对偏移的C#实现
解决Unity腿部IK父物体旋转后脚部目标位置错误问题
问题场景
我搭建Leg IK系统时,采用世界空间位置设置每只脚的targetPos。父物体未旋转时脚部位置正常,但父物体绕Y轴旋转后,脚部目标因处于世界空间固定不动,导致放置错误。之前用手动调整近似值实现临时效果,但硬编码数值无法适配任意旋转角度(比如单次旋转从30度改为45度就失效),需要一种基于数学计算的方法,根据脚部相对父物体的偏移量,计算任意角度下正确的世界空间目标位置,并用C#在Unity中实现。
核心解决思路
- 记录脚部相对于父物体(body)的初始局部偏移量,而非直接依赖世界位置
- 当父物体旋转时,将局部偏移量通过父物体的旋转转换为世界空间位置,让目标位置自动跟随父物体旋转调整
- 用相对位置计算替代硬编码的偏移值,适配任意旋转角度
修正后的代码实现
// 新增:存储脚部相对于body的初始局部偏移 private Vector3 initialLocalOffset; private void Start() { // 计算初始局部偏移:将脚部世界位置转换为相对于body的局部坐标 initialLocalOffset = body.InverseTransformPoint(transform.position); currentPosition = newPosition = oldPosition = targetPos = transform.position; currentNormal = newNormal = oldNormal = transform.up; lerp = 1; StartCoroutine(LerpCycle()); } void Update() { transform.position = currentPosition; transform.up = currentNormal; } public void RotateR() { // 基于初始局部偏移,计算调整后的目标局部位置 Vector3 targetLocalPos = initialLocalOffset; if (rightLeg) { // 将世界空间的调整量转换为body的局部空间方向 targetLocalPos -= body.InverseTransformVector(Vector3.right * (stepLength * 0.2f)); targetLocalPos -= body.InverseTransformVector(Vector3.forward * (stepLength * 0.2f)); } else { targetLocalPos += body.InverseTransformVector(Vector3.right * (stepLength * 0.35f)); // targetLocalPos += body.InverseTransformVector(Vector3.forward * (stepLength * f)); } // 将局部位置转换为世界空间位置,自动适配body的旋转 targetPos = body.TransformPoint(targetLocalPos); RayToNewPos(); } public void RotateL() { // 可参照RotateR的逻辑实现左腿的旋转处理 } void RayToNewPos() { Ray ray = new Ray(Vector3.up * 2f + targetPos, Vector3.down); if (Physics.Raycast(ray, out RaycastHit info, 10, terrainLayer.value)) { if (!stepping) { stepping = true; delayed = false; } lerp = 0; newPosition = targetPos; // 确保脚部不会低于地面 if (newPosition.y < 0.224f) newPosition.y = 0.223f; newNormal = info.normal; } } IEnumerator LerpCycle() { while (true) { if (lerp < 1) { Vector3 tempPosition = Vector3.Lerp(oldPosition, newPosition, lerp); tempPosition.y += Mathf.Sin(lerp * Mathf.PI) * stepHeight; currentPosition = tempPosition; currentNormal = Vector3.Lerp(oldNormal, newNormal, lerp); lerp += Time.deltaTime * speed; // 原左右脚的延迟逻辑可按需恢复 // if (rightLeg && !delayed) // { // delayed = true; // yield return new WaitForSeconds(rightFootDelay); // } yield return new WaitForFixedUpdate(); } else { oldPosition = newPosition; } yield return null; } }
关键代码说明
initialLocalOffset:在Start阶段记录脚部相对于父物体的初始局部位置,确保后续计算的基准正确InverseTransformVector:将世界空间的调整方向转换为父物体的局部空间方向,保证调整量随父物体旋转同步变化TransformPoint:将调整后的局部位置转换为世界空间位置,自动适配父物体的任意旋转角度,彻底解决硬编码数值的局限性
内容的提问来源于stack exchange,提问作者Mad Hatter
相关产品推荐
相关产品推荐

