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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 14:46:08