①CharacterController移動とAnimator設計
ゲームやシミュレーションで「人間らしく歩くキャラクター」を作ろうとすると、最初の壁が移動の土台です。物理に任せると暴れ、Transformを直接動かすと壁をすり抜ける——。この記事では、Unity URPで実際にヒューマノイドロボットを街中で歩かせている自作プロジェクトをもとに、CharacterController による移動と Animatorの設計、そして初心者が必ずハマる「腰(Hips)の暴走」の対処までを、実コード付きで解説します。
この記事で作るもの
- WASD(または新Input System)で前後左右に歩くヒューマノイド
- 歩行速度に応じて Idle ↔ Walk ↔ Run が滑らかに切り替わる Animator
- 壁をすり抜けず、地面に沿って歩く安定した移動 対象:Unity の基本操作がわかり、Humanoid リグのモデル(Mixamo 等)を用意できる人。 なぜ Rigidbody ではなく CharacterController なのか 人型キャラの「歩行」では、物理的に正確な挙動よりも思った通りに動く操作感が大切です。Rigidbody
は外力で簡単に転倒・回転してしまい、人間らしい直立歩行の制御が難しい。一方 CharacterController は、 - カプセルコライダーで壁・段差の衝突を自動処理
- 重力・接地判定(isGrounded)を扱いやすい
- 回転や転倒は自分で制御できる ため、歩行キャラの土台に向いています。
1.CharacterController で移動する
まずは入力を速度に変換し、CharacterController.Move() で動かします。
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class HumanoidController : MonoBehaviour
{
[SerializeField] float walkSpeed = 1.8f;
[SerializeField] float runSpeed = 4.5f;
[SerializeField] float turnSpeed = 540f; // 度/秒
[SerializeField] float gravity = -9.81f;
CharacterController _cc;
Animator _animator;
float _verticalVel;
void Awake()
{
_cc = GetComponent<CharacterController>();
_animator = GetComponent<Animator>();
// 重要:ルートモーションは使わない(後述)
_animator.applyRootMotion = false;
}
void Update()
{
// 入力(新Input Systemなら Keyboard.current で置き換え)
float h = Input.GetAxisRaw("Horizontal");
float v = Input.GetAxisRaw("Vertical");
bool running = Input.GetKey(KeyCode.LeftShift);
Vector3 input = new Vector3(h, 0f, v).normalized;
float targetSpeed = running ? runSpeed : walkSpeed;
// カメラ基準などにせず、まずはワールド基準でシンプルに
Vector3 move = input * targetSpeed;
// 進行方向へ体を向ける
if (input.sqrMagnitude > 0.01f)
{
Quaternion look = Quaternion.LookRotation(input);
transform.rotation = Quaternion.RotateTowards(
transform.rotation, look, turnSpeed * Time.deltaTime);
}
// 重力
if (_cc.isGrounded && _verticalVel < 0f) _verticalVel = -2f;
_verticalVel += gravity * Time.deltaTime;
move.y = _verticalVel;
_cc.Move(move * Time.deltaTime);
// Animatorへ速度を渡す(正規化:0=Idle, 0.5=Walk, 1=Run)
float normalized = input.sqrMagnitude < 0.01f
? 0f
: (running ? 1f : 0.5f);
_animator.SetFloat("Speed", normalized, 0.1f, Time.deltaTime);
}
}
ポイントは SetFloat の第3引数 dampTime(0.1秒)。これでパラメータが急変せず、Idle↔Walk↔Run の切り替えが滑らかになります。
2. Animator を設計する Blend Tree を1つ作るだけで歩行は組めます。
- パラメータ:Speed(Float)
- Blend Tree(1D, しきい値):
- 0.0 → Idle クリップ
- 0.5 → Walk クリップ
- 1.0 → Run クリップ
3. 最大のハマりどころ:腰(Hips)が暴走する ここが本記事のキモです。applyRootMotion = false にしても、Locomotionクリップに焼き込まれた腰の平行移動が残ることがあります。収録時に歩いた距離(数メートル)がアニメに含まれていると、
- 体だけがルート(当たり判定)から離れていく
- ルート回転のたびに体が振り回されて「瞬間移動」して見える
- CharacterController と表示位置がズレて「壁をすり抜ける」 という症状が出ます。
- 原因は「ルートは動いていないのに、腰ボーンがアニメ内で勝手に移動している」こと。
- 解決:LateUpdate で腰をルート真上に固定する Update で直しても Animator が後から上書きするため効きません。必ず
LateUpdate(アニメ適用後)で、腰のワールド水平位置をルート真上に固定します。上下動(bob)はアニメに任せたいので Yだけ残すのがコツです。 - Transform _hips; void Start()
{
_hips = _animator.GetBoneTransform(HumanBodyBones.Hips);
} void LateUpdate()
{
if (_hips == null) return; Vector3 hw = _hips.position;
// X/Z はルート真上に固定、Y(上下のbob)だけアニメの値を残す
_hips.position = new Vector3(transform.position.x, hw.y, transform.position.z);
} - ⚠️ ローカルXZの固定や「Idle姿勢からの射影」では直りません。Mixamoのローカル軸回転やキャプチャ値の汚染が残るためで、ワールド座標で固定するのが確実です。これに気づくまで数日溶かしました……。 まとめ
- 人型歩行の土台は CharacterController が扱いやすい
- Animator は Speed 1パラメータ+Blend Tree で十分
- SetFloat の dampTime で切り替えを滑らかに
- applyRootMotion = false + LateUpdate で腰をルート真上に固定して暴走・すり抜けを防ぐ 次回②では、この上に IK(OnAnimatorIK)で視線・足・手を制御して、地形に足を合わせる方法を解説します。
解決:LateUpdate で腰をルート真上に固定する Update で直しても Animator が後から上書きするため効きません。必ずLateUpdate(アニメ適用後)で、腰のワールド水平位置をルート真上に固定します。上下動(bob)はアニメに任せたいので Yだけ残すのがコツです。
Transform _hips; void Start()
{
_hips = _animator.GetBoneTransform(HumanBodyBones.Hips);
} void LateUpdate()
{
if (_hips == null) return; Vector3 hw = _hips.position;
// X/Z はルート真上に固定、Y(上下のbob)だけアニメの値を残す
_hips.position = new Vector3(transform.position.x, hw.y, transform.position.z);
}
⚠️ ローカルXZの固定や「Idle姿勢からの射影」では直りません。Mixamoのローカル軸回転やキャプチャ値の汚染が残るためで、ワールド座標で固定するのが確実です。これに気づくまで数日溶かしました……。
まとめ
- 人型歩行の土台は CharacterController が扱いやすい
- Animator は Speed 1パラメータ+Blend Tree で十分
- SetFloat の dampTime で切り替えを滑らかに
- applyRootMotion = false + LateUpdate で腰をルート真上に固定して暴走・すり抜けを防ぐ
次回②では、この上に IK(OnAnimatorIK)で視線・足・手を制御して、地形に足を合わせる方法を解説します。

