UnityのIKで視線・足・手を制御する OnAnimatorIK実践(ヒューマノイド②)

前回① (#)で CharacterController と Animator を使い、ヒューマノイドを歩かせる土台を作りました。ただ、アニメだけではキャラが常に正面を向き、足は坂道で地面に刺さったり浮いたりします。人間らしさを一段引き上げるのが IK(インバースキネマティクス)です。この記事では Unity の OnAnimatorIK
を使って、視線・足・手をリアルタイムに制御する方法を、実プロジェクトのコード付きで解説します。

IK(インバースキネマティクス)とは

通常のアニメは「肩→肘→手首」と根元から先端へ回転を決めます(フォワードキネマティクス)。IKはその逆で、「手をここに置きたい」という最終位置を先に決め、肘や肩の角度を自動計算します。これにより、

  • 視線:動く対象を目で追う
  • 足:坂・段差の地面に足裏を合わせる
  • 手:ドアノブ・武器・テニスラケットなど対象を掴む といった「環境に反応する動き」を、アニメを作り直さずに実現できます。 前提:2つの設定を忘れると一切効かない(最重要) IKを書く前に、ここでつまずく人が非常に多いです。

1.モデルが Humanoid リグであること(Avatar の Animation Type = Humanoid)。GenericやLegacyでは OnAnimatorIK
の足・手ゴールが使えません。

2.Animator Controller のレイヤーで「IK Pass」を ON にすること。

using UnityEngine; 
[RequireComponent(typeof(Animator))]
public class HumanoidIKController : MonoBehaviour
{
Animator _animator; 
[Header("視線")]
[SerializeField] Transform lookTarget; 
[Header("足の接地")]
[SerializeField] LayerMask groundMask = ~0;
[SerializeField] float rayUp = 0.5f; // 足の少し上からレイを出す
[SerializeField] float rayDown = 1.2f; // 下方向の探索距離
[SerializeField] float footOffsetY = 0.1f; // 足裏とボーン原点のズレ補正 
[Header("手")]
[SerializeField] Transform rightHandTarget; 
void Awake() => _animator = GetComponent(); void OnAnimatorIK(int layerIndex)
 {
if (_animator == null) return;
ApplyLookAt();
ApplyFootIK(AvatarIKGoal.LeftFoot, "IKLeftFootWeight");
ApplyFootIK(AvatarIKGoal.RightFoot, "IKRightFootWeight");
ApplyHandIK(AvatarIKGoal.RightHand, rightHandTarget);
 }
}

3.視線IK:対象を目で追う
SetLookAtWeight で「どれだけ追うか」、SetLookAtPosition で「どこを見るか」を指定します。

void ApplyLookAt()
{
if (lookTarget == null) { _animator.SetLookAtWeight(0f); return; } // (全体, 体, 頭, 目, 首振りの制限)
_animator.SetLookAtWeight(1f, 0.3f, 0.7f, 1f, 0.6f);
_animator.SetLookAtPosition(lookTarget.position);
}

SetLookAtWeight の引数がコツです。

  • 第2引数 bodyWeight=0.3:体を少しだけひねる(1にすると全身で向いて不自然)
  • 第3引数 headWeight=0.7:主に頭で追う
  • 第5引数 clampWeight=0.6:真後ろまで首が回らないよう制限(人間らしさの肝)

2.足IK:坂・段差に足裏を合わせる 各足の現在のIK位置から真下へレイを飛ばし、地面のヒット点に足を移動+傾きを地面の法線に合わせます。

void ApplyFootIK(AvatarIKGoal goal, string weightParam)
{
// アニメのカーブで歩行中の接地タイミングに合わせて重みを変える
float weight = _animator.GetFloat(weightParam);
if (weight <= 0f) { _animator.SetIKPositionWeight(goal, 0f); return; } 
Vector3 footPos = _animator.GetIKPosition(goal);
Vector3 origin = footPos + Vector3.up * rayUp; 
if (Physics.Raycast(origin, Vector3.down, out var hit, rayUp + rayDown, groundMask))
 {
// 位置:地面 + 足裏オフセット
_animator.SetIKPositionWeight(goal, weight);
_animator.SetIKPosition(goal, hit.point + Vector3.up * footOffsetY); // 回転:地面の傾きに足裏を合わせる Quaternion align = Quaternion.FromToRotation(Vector3.up, hit.normal) * transform.rotation; _animator.SetIKRotationWeight(goal, weight); _animator.SetIKRotation(goal, align); }
} 

ポイント

  • 重み(weight)はアニメのカーブから取る:足が地面に着いている瞬間だけ1、振り上げ中は0にすると、空中で足が固定される不自然さを防げま
    す。Animationクリップに IKLeftFootWeight 等のカーブを追加して制御します。
  • footOffsetY はモデルごとに調整。足が地面に刺さるなら増やし、浮くなら減らします。

3.手IK:対象を掴む ドアノブやラケットなどのターゲットに右手を合わせます。位置と回転の両方を指定します。

 void ApplyHandIK(AvatarIKGoal goal, Transform target)
{
if (target == null) { _animator.SetIKPositionWeight(goal, 0f); return; } _animator.SetIKPositionWeight(goal, 1f);
_animator.SetIKRotationWeight(goal, 1f);
_animator.SetIKPosition(goal, target.position);
_animator.SetIKRotation(goal, target.rotation);
} 

腕が届かない位置にターゲットを置くと肘が不自然に伸び切るので、ターゲットは肩から腕の長さの範囲に置くのが基本です。
よくあるハマりどころ

症状 原因  
IKが一切効かない レイヤーの IK Pass が OFF/リグが Humanoid でない
足が地面に刺さる/浮くfootOffsetY の調整不足、レイのレイヤーマスク漏れ
空中で足が固定される 重みを常に1にしている(カーブで接地時のみ1に)
首が真後ろまで回る SetLookAtWeight の clampWeight が大きすぎる
手や視線がカクつく ターゲットの移動をLerpで滑らかにしていない

まとめ

  • IKは OnAnimatorIK の中だけで設定する
  • 大前提は Humanoidリグ+レイヤーのIK Pass ON
  • 視線=SetLookAtWeight/Position、足=レイで地面に接地+法線で傾き、手=ターゲットに位置・回転を合わせる
  • 足の重みはアニメのカーブで接地時だけ効かせるのが自然さの鍵

次回③では、IKと組み合わせて使う 手続き的な歩行(ProceduralWalk)と重心バランス制御(BalanceController)
を取り上げ、アニメに頼らず足を運ばせる仕組みを解説します。