【Unity】人型モデルを動かす(Blend Tree・CharacterController・Input Systemで作るTPS移動の基礎)
はじめに
はじめに
ゲーム開発をしたく、Unityの入門書を読み終えたまではいいが ……
入門レベル以降の技術書がない!
という問題にぶち当たりました。
いや、あるにはあるのですが、バージョンが古いんですよね。
そこで、調べながら備忘録を残しているのですが、折角なので読みやすくまとめた記事にしたいと思い、Unityの「初級から中級までの架け橋」になるシリーズをはじめます🎉
本記事は記念すべき第1弾『人型モデルを動かす(Blend Tree・CharacterController・Input Systemで作るTPS移動の基礎)』となります。
Humanoid型(人型)アバターを動かしてみたぞ🏃♀️#unity pic.twitter.com/obdUitbb0Y
— 辛島信芳@Androidアプリ開発🦖 (@quiet_sea) July 22, 2026
Starter Assetsから流用するのは「アニメーションクリップ(動きのデータ)」だけです。既存のアセットをそのまま使うのではなくBlend Tree / Animator Controller / CharacterController / 移動スクリプト / カメラ制御など、すべてを構築していくのが、本記事の最大の特徴です。読み終える頃には、初級者から中級者へとステップアップするための実践的な技術が習得できているでしょう。
流れとしては、前回の記事「仮想アバター入門」の【章『Unity』】の続きとなります。
🖋️ VRoid製モデル(VRM0.xで出力)をUnityに取り込み(UniVRMを使用)。のところまで済んでいるものとします。
前回の「仮想アバター入門」ではcluster(ソーシャルVR)にアバターをアップし使ってますが、今回は【Unity上で人型モデルを動かす】というゲーム開発関連の内容となります。
🛠️ 動作検証環境
・macOS Tahoe 26.5.1
・VRoid Studio 製のアバター(VRM0.xで出力)
・Unity 6.4(6000.4.0f1)
・Starter Assets - ThirdPerson | URP 1.1.7
・Cinemachine 3.1.7
本記事の主旨

『Starter Assets - ThirdPerson』でキャラクターを動かす記事は、すでに数多くあります。
ただ、その多くは「付属の完成品プレハブ(PlayerArmature)や既製スクリプト(ThirdPersonController.cs)をそのまま使う」ものです。
数分で動くのは大きな魅力ですが、初学者にとっては中身がブラックボックスになりがちで、エラーが出た瞬間に手も足も出なくなるという弱点があります(実際、既製スクリプトはアニメーションイベントやCinemachineなど依存が多く、どれか一つ欠けるだけで警告が出ます)。
そこで本記事では、Starter Assetsからは「アニメーションクリップ(動きのデータ)」だけを借ります。Blend Tree / Animator Controller / CharacterControllerv / 移動スクリプト / カメラは、すべて自分の手で組み立てます。
時間はかかりますが「入力→移動→アニメーション」という因果関係が最初から最後まで自分の視界に入るため、動かなくなっても、どこを見ればいいかが分かる状態を保てます。初級から中級への橋渡しとして、遠回りに見えて一番の近道だと考えています。
本記事の構成

本記事は5つの章の積み上げ式になっています。前の章の成果物の上に次の章が乗っていくので、順番どおりに進めるのがおすすめです。
第1章「はじめに」
シリーズの主旨と本記事の全体構成、頻出用語をまとめた用語集。手を動かす前に、全体の「地図」を手に入れます。第2章「Unity上でVRMモデルを動かす」
本記事の心臓部です。素材の準備(Starter Assetsからアニメーションだけ借りる)→動きの司令塔(Animator Controller / Blend Tree)→体を動かす仕組み(CharacterController+自作スクリプト)の順で、「歩く・走る」までを一気に作ります。第3章「カメラ追従の実装」
まず仕組みが目に見える自作カメラを作り、そのあとUnity公式のCinemachineに乗り換えます。第4章「床の作成と落下防止」
床の作成にて舞台を整え、あわせて「視点が回らない」「ダッシュしない」という実際にハマったつまずきの対処もここにまとめました。第5章「ジャンプを実装する」
物理(コード)と見た目(Animator)の2フェーズに分けて仕上げます。
読み終える頃には、WASD(方向キーも対応)と視点操作で自由に動き回り、走って、跳べるVRoidアバターが手元に完成しています。
用語集
本記事に登場する用語をまとめました。読んでいて分からない言葉が出てきたら、ここに戻って確認してください。
📝 かなりの分量となるので、この「用語集」の項は、一旦、飛ばして『Unity上でVRMモデルを動かす』に進んで構いません。
■ モデル・アバター関連
VRM
3Dの人型アバターをアプリやサービスをまたいで共通で扱うための、日本発のファイル形式。本記事ではVRoid Studioから「VRM 0.x」形式で出力したモデルを使います。VRM 0.x / VRM 1.0
VRMのバージョン。0.xはスケルトンが「正規化」されており、他モデル用のHumanoidアニメーションが素直に適用できるのが利点。1.0は新しい仕様ですが正規化が仕様から外れたため、外部モーションの適用はモデル依存になります。VRoid Studio
ピクシブ社が提供する、絵を描く感覚でキャラクターを作れる無料の3Dモデル作成ソフト。作ったモデルはVRM形式で出力できます。UniVRM
VRMファイルをUnityで読み書きするための標準パッケージ。VRMをUnityに取り込むとプレハブが自動生成されます。Unity 6では最新版(0.131系)を使うこと(古い版はモデルがピンクになる原因になります)。Humanoid(ヒューマノイド)
Unityが定める「人型」の共通骨格規格。この規格に沿っていれば、別のモデル用に作られたアニメーションを流用できます。リターゲティング
あるモデル用のアニメーションを、体格の違う別のモデルに適用し直す仕組み。本記事が「Starter Assetsのアニメーションだけ借りる」ことができるのは、Humanoid規格とこの仕組みのおかげです。
■ アニメーション関連
アニメーションクリップ
「歩く」「跳ぶ」といった1つの動きを記録したデータ。本記事ではStarter Assetsに同梱のクリップ(Idle / Walk_N / Run_N など)を使います。FBX
3Dモデルやアニメーションを格納する汎用ファイル形式。Starter Assetsのアニメーションクリップは、FBXファイルの中に入っています。Animator(アニメーター)
モデルに付いている「アニメーション再生装置」のコンポーネント。どの骨格(Avatar)に、どの再生ルール(Animator Controller)を使うかを持ちます。Animator Controller(アニメーターコントローラー)
「いつ・どのアニメーションを再生するか」を決める設計図。キャラクターの動きをコントロールする司令塔です。ステート(State)
Animator Controller内の「状態」ひとつひとつのこと。本記事ではLocomotion(移動)・Jump・Fall・Landの4つのステートを作ります。Blend Tree(ブレンドツリー)
1つのステートの中で、複数のアニメーションを数値に応じて滑らかに混ぜ合わせる仕組み。Speedが0で待機、2で歩き、6で走り、と連続的に変化させられます。パラメーター
スクリプトからAnimator Controllerへ値を渡すための変数。Float(小数)、Bool(オン/オフ)、Trigger(一度きりの合図)などの型があります。遷移(Transition)
ステートからステートへ切り替わる「矢印」のこと。どんな条件で、何秒かけて切り替わるかを設定します。Has Exit Time
遷移の設定項目。オンにすると「アニメーションの再生が終わるのを待ってから」次のステートへ移ります。入力で即座に反応させたい遷移ではオフにします。ルートモーション(Apply Root Motion)
アニメーション自体が持っている「キャラクターを移動させる力」。本記事ではスクリプトで移動させるため、二重移動(滑るバグ)を防ぐ目的でオフにします。
■ 移動・物理関連
CharacterController(キャラクターコントローラー)
「キャラクターを動かすこと」専用のコンポーネント。カプセル型の当たり判定を持ち、段差や坂を考慮しながら移動できます。Rigidbody(リジッドボディ)
Unityの物理エンジンでオブジェクトを動かすためのコンポーネント。リアルな物理計算が働くぶんキャラクター操作の制御が難しいため、本記事ではCharacterControllerを採用しています。コライダー(Collider)
「見えない当たり判定」。床(Plane)には最初から付いているため、キャラクターが上に乗れます。isGrounded
CharacterControllerが持つ「今、地面に接しているか」の判定。ジャンプの発動条件や、Animatorへの接地状態の通知に使います。
■ 入力関連
Input System(新Input System)
Unityの新しい入力の仕組み。キーボード・ゲームパッド・タッチなどを「アクション」という抽象的な単位でまとめて扱えます。旧方式(Input.GetAxis)に代わるUnity 6の標準です。InputSystem_Actions
Unity 6で新規プロジェクトを作ると自動生成される入力設定ファイル。Move(WASD/矢印)、Look(視点)、Sprint(左Shift)、Jump(スペース)などが最初から定義済みです。バインド(Binding)
アクションと、実際のキーやボタンを結びつける設定。たとえばSprintアクションは初期状態で「左Shiftのみ」にバインドされています。
■ 描画関連
レンダーパイプライン
Unityの「映像描画システム」。プロジェクト作成時にBuilt-in(伝統的な方式)かURP(現在の標準)かを選びます。URP(Universal Render Pipeline)
現在のUnityの標準的な描画システム。スマホから高性能PCまで幅広い環境で、軽く綺麗に描画できるよう作り直されたものです。シェーダー
質感や影などを描画するプログラム。描画システムと合わないシェーダーは、ピンク(マゼンタ)表示になります。マテリアル
オブジェクト表面の「素材」設定。どのシェーダーを使うかの参照と、色やテクスチャなどの設定値のセットです。
■ カメラ関連
TPS(三人称視点)
本来は「三人称視点のシューティングゲーム」を指す略語ですが、転じて「キャラクターを背後からカメラで追いかける視点・操作方式」全般を指してよく使われます。本記事の操作方式(カメラの向いている方向へ前進)はTPSの王道スタイルです。Cinemachine(シネマシーン)
Unity公式の高機能カメラツール。視点の自由回転や障害物回避などを、コードを書かずに設定だけで実現できます。Cinemachine Brain
Main Cameraに付く「橋渡し役」のコンポーネント。CinemachineCameraが計算した位置・回転を実際のカメラに反映します。これがないと、Cinemachineの計算結果が画面に映りません。
Unity上でVRMモデルを動かす
概要
「VRM 0.x」はUniVRMインポート時にHumanoid Avatarが自動生成され、正規化(T-Pose・スケール/回転なし)済みなので、他モデル用のHumanoidアニメーションがそのまま効きます。また、Animatorコンポーネントも既に付いています。あとは自作の「Animator Controller」「CharacterController」「移動スクリプト」を足すだけです。
📝 もしアバターが「VRM 1.0」だったら?
基本的に作業フローは同じです。また、VRM1.0でもAnimatorとHumanoid Avatarはインポート時に自動生成されます。
ただし、1つだけ注意点があります。 VRM1.0では「正規化」(T-Poseベイク)が仕様から外れました。0.xは「全ノードの回転が0=T-Pose」が保証されていたため外部モーションが素直に効きましたが、1.0にはその保証がなく、公式ドキュメントも「モデルによっては正しく動作しない場合がある」と明記しています。
アバターの確認
アバターのオブジェクトのインスペクターを確認します。

Animatorコンポーネント
アバター 欄にモデルのアバター(人型アイコン)が入っているか?を確認。VRM Humanoid Description コンポーネントが存在しているか?
このコンポーネントがアタッチされていれば、VRM(Humanoid規格)として正しくセットアップアップされている。
■ Input Systemの確認

Assets 直下に InputSystem_Actions があるか?

「編集」→「プロジェクト設定」→「物理」→「プレイヤー」→「その他の設定」→「アクティブな入力処理」。
必要に応じ、「新」「旧」「両方」切り分け。
「Starter Assets」を入手する
公式の無料アニメーションの入手しましょう。
本記事では「Starter Assets - ThirdPerson | URP」を使用します。

アセットストア( https://assetstore.unity.com )にアクセス(Unityアカウントでログインします)。

「Starter Assets」で検索し「Starter Assets - ThirdPerson | URP」を入手し、インポートします。

「StarterAssets」の確認。
Starter Assets「Starter Assets」とは?
『 Assets/StarterAssets/ThirdPersonController/Scripts/ 』には、以下のような一式が入っています。

つまり、移動スクリプトが最初から入っているので ……
モデルに『CharacterController + PlayerInput + StarterAssetsInputs + ThirdPersonController』 を貼り付ける。
アニメーターに 「StarterAssetsThirdPerson」 を入れる。
の2点だけで歩くように出来ます(自作コードはゼロ)。

しかし、Starter Assetsに依存しすぎると、私のような初心者はエラー対応が困難です。
また、ある程度は、自作した方がスキルも身につきます。
そこで、Starter Assetsからはアニメーションクリップ(Idle:待機 / Walk_N:歩き / Run_N:走り)だけを借りて、「Animator Controller」「CharacterController」「移動スクリプト」の3点は自分で組む方針にしたいと思います(これが効率よくスキルを身につける最適解)。
「URP (Universal Render Pipeline)」とは?
そもそも、URP(Universal Render Pipeline)って何?って感じなので調査。

Unityでプロジェクト作成時に「ビルトイン (Built-in)」か「 URP (Universal Render Pipeline)」のどちらかを選ぶが、これは『映像描画システム』の違いである。
ビルトイン(Built-in Render Pipeline)
Unityの誕生初期からずっと使われてきた伝統的なシステム。歴史が長い分、ネット上の古いAsset(素材)や過去のチュートリアルは、このビルトインを前提にしていることが非常に多い。
【補足】 VRChat(PC版)などの少し古い歴史を持つプラットフォームでは、今でもこちらが基準になっていることがある。URP(Universal Render Pipeline)
現在のUnityの新しいスタンダード(標準)。スマホから高性能PCまで、どんな環境でも「より軽く」「より綺麗に」映像を処理できるようにゼロから作り直された最新システム。
なるほど、VRChatなどのプラットフォームが、あえて「少し古い特定のUnityバージョン(例えばUnity 2022.3.22f1など)」を指定するのは、ビルトイン(Built-in)環境での互換性を保つためだったのですね〜。
モデルがピンク(マゼンダ)になったら?

🧩 原因
古い描画規格(ビルトイン)で作られた素材を、新しい規格(URP)のプロジェクトに導入したことで、「シェーダー(質感を決めるプログラム)の互換性がなくなり、正常に描画されない状態」になっているためです。Unityがエラーを知らせるために、強制的にマゼンタ色で表示しています。
もっとも、今回はアニメーションクリップしか使わないので、そのまま放置して構いません。 個別に直したいマテリアルがあれば、インスペクターの「シェーダー」 を Universal Render Pipeline → Lit に手動変更などを試してみましょう。
もしくは、「レンダーパイプラインコンバーター」で一括変換させます。
メニュー:ウィンドウ → レンダリング → レンダーパイプラインコンバーター。

上記は「Built-in」を「URP(Universal Render Pipeline)」にする形(2つのプルダウン)になっているので、Material Reference Converter や Material Shader Converter にチェックをしてScan押下。スキャンされるので、確認後、コンバートをします。
■ 「Built-in」プロジェクトを「URP(Universal Render Pipeline)」プロジェクトにする
そもそも論として、私、プロジェクト自体を「Built-in」にしてたようなので、「URP(Universal Render Pipeline)」にしたいと思います。
スタータアセットも、先ほど「Starter Assets - ThirdPerson | URP」を入手したので、いずれにせよURPにする必要がありそうです。(最初から「URPのプロジェクト」にしておけば良かった ……)
編集 → プロジェクト → 設定グラフィックス。

Default Render Pipeline が「なし」なので、『StarterAssetsURP...』を選択。

「URP」タグを選択すると「These are the settings that are currently active on the project.(これらが現在プロジェクトで有効になっていますよ)」と表示。
アバターはピンクになります。
これは、以前の「Built-in」プロジェクト時にインポートしたので、その時にUniVRMが「これはBuilt-inプロジェクトなので、それに合わせたマテリアル生成をしよう」と判断し、Built-inに適合したアバターになっているためです。
で、早速「レンダーパイプラインコンバーター」で一括変換させてみたのですがエラーが出て上手く変換できませんでした。
結局は、アバターのプレハブアセットごと削除して、アバターのVRMをインポートし直すことで解決しました。
もっとも、一番良いのは「プロジェクト作成の段階で『URP』を選択」すること。今回はいい教訓になりました。
「アニメーションクリップ」の設定確認
Starter Assetsの「アニメーションクリップ(動きのデータ)」ですが、今回は「歩行」「待機」「走行」はいずれも『ループ再生』させようと思うので設定を確認します。

CharacterAnimations フォルダ(Assets/StarterAssets/ThirdPersonController/Character/Animations/ )内の使うFBX(Idle、Walk_N、Run_N など)を選択。

Rigタブ
「アニメーションタイプ」をヒューマノイド(人型)にする。Animationタブ
「時間をループ」をオンにする。移動や待機など、連続させる動きにする(「ポーズをループ」もオンにすると繋ぎ目が滑らかになる)
📝 変更した場合「Apply(適用)」押下での反映が必要となります。

また、今回のように「スクリプトでキャラを動かす(ルートモーションを使わない)」場合、アニメーションが勝手にキャラクターの座標を動かしてしまわないように、アニメーション側の移動要素をその場に固定(ベイク)する必要があります。
「アニメーターコントローラー」を新規作成する(Blend Tree構成)
アバター用のアニメーターコントローラー(キャラクターの動きをコントロールする司令塔)を作りましょう。
■ アニメーターコントローラー作成

Assets 内に「Animators」フォルダを作成
そのフォルダ内で(右クリック)作成 → アニメーション → アニメーターコントローラー(コントローラー名は任意)。
ダブルクリックして、アニメーターウィンドウを開きます。
■ パラメーター作成

左上の「パラメーター」タブ → 「+」 → 「Float(少数点が使える数字の型)」 → 名前を Speed にする。
📝 このパラメーターは、スクリプトから「今、時速〇〇kmで動いてるよ」という数字を受け取るためのものとなります。
■ ブレンドツリーを作成

Base Layer内で(右クリック)「ステートを作成」 → 「新しいブレンドツリーから」。

まず「ステート(State)」とはキャラクターの「現在の状態」を表します。
このステートの進化系が『ブレンドツリー(Blend Tree)』で、複数のアニメーションを数字の大きさに合わせて滑らかに混ぜ合わせる(ブレンドする)ことが出来ます。
📝 今回で言うと、Speedパラメーターの調整を「待機、歩き、走り(複数のアニメーション)」に滑らかに混ぜ合わせる装置を作っていく形となります

名前はLocomotionとでもしておきます。

このBlend Tree(Locomotion)をダブルクリックして編集画面に入る。
画面内にある Blend Tree というグレーの四角をクリック。
・ブレンドタイプ が 1D になっていることを確認。
・Parameter がさっき作った Speed になっていることを確認。
📝 ブレンドタイプを1D(1 Dimension(1次元)の略)とは、一直線の数直線という意味です。今回は 「Speed(スピード)」という1つの数字 が、0 ➔ 2 ➔ 6 と大きくなるにつれて、待機 ➔ 歩き ➔ 走りと一直線に変化していきます。 このように【たった1つのパラメータ(条件)を使ってアニメーションを混ぜる】設定のことを、Unityでは 1D と呼んでいます。
「+」ボタン を押し、メニューから 「モーションフィールドを追加」 を選ぶ操作を 3回繰り返します。

「Automate Thresholds」のチェックを外します。
これがオンになっていると、Unityが自動的に 0、0.5、1 のように、均等な間隔で数値を割り振ってしまいます。今回は、自分でスピードの数値(Threshold)を指定したいので、オフにします。◎ボタン押下し「Idle」「Walk_N」「Run_N」を選択。(Thresholdは Idle:0、Walk_N:2、Run_N:6とする)

Base Layerに戻り、Locomotion が【既定状態(オレンジ色)】であることを確認(EntryからLocomotionへ矢印が出ていればOK)。
📝 Speedにおいて『待機は「0」、歩きは「2」、走る「6」』という設定になります。Speedが1ならゆっくり歩き、Speedが4なら早歩きな感じになります(ブレンドツリーは、複数のアニメーションを数字の大きさに合わせて滑らかに混ぜ合わすことが出来るため)
「アニメーターコントローラー」をアバターに適用する
「アニメーターコントローラー」をアバターにセットしましょう。

ヒエラルキーからアバターのオブジェクトを選択。
インスペクターの「Animator項目のコントローラー」に、作成した「アニメーターコントローラー」をドラッグ&ドロップして入れます。
「ルートモーションを適用」はオフにします。
ルートモーションとは、アニメーション自体が持っている「前に進む力」のことです。 今回はスクリプトを記述し動かすため、オンにすると「アニメの移動 + スクリプトの移動」のように二重発生(滑るようなバグが起きる)しまうためオフにします。

ゲーム開始「▶」で確認。
これまで「Tポーズ」で固まっていたアバターが、すっと手を下ろし自然な「待機(Idle)」モーションで立っているはずです。
【補足】カメラを良い感じの位置にセットする
ゲーム開始でアバターが小さかったり視界から外れることがあります。このような時は、撮影している「カメラ(Main Camera)」の場所を調整すると良いでしょう。

ヒエラルキーからアバターのオブジェクトをダブルクリック。
これにより、アバターが画面の真ん中にドアップで映る。右ドラッグやマウスホイールなどで見やすいアングルにする。
ヒエラルキーから Main Camera を1回だけクリックして選択。
上部メニュー「ゲームオブジェクト」→「ビューに揃える」 を選択。
⌨️ Cmd + Shift + F(WindowsはCtrl + Shift + F)
ゲーム開始「▶」で確認
「キャラクターコントローラー」をアバターに適用する
■「キャラクターコントローラー」の適用
通常の「Rigidbody(物理エンジン)」も使えますが「坂道で滑り落ちる」などのリアルな物理計算が働いてしまい『キャラクターとしての操作感』を初心者が作るのは大変です。そこで『キャラクターコントローラー』を適用したいと思います。
📝 キャラクターコントローラー
「キャラクターを動かすこと」専用の機能(コンポーネント)です。 追加すると、シーン画面のアバターの足元に「当たり判定(緑色の線のカプセル)」が現れます。「キャラクター専用」に作られているため、アバターの身長に合わせることで、「段差を自動で登る」「壁に引っかからずに滑る」といった機能が正しく働くようになります。
キャラクターコントローラーというは「キャラクターを動かすこと」だけに特化したものです。

ヒエラルキーから、アバターを選択。
「コンポーネントを追加」ボタン押下。
検索窓に character と入力し、出てきた Character Controller を選択。
■「当たり判定」の調整
シーン画面のアバターの足元に緑色の線のカプセルが現れます。これがアバターの「当たり判定」となります。これをアバターの身長にぴったり合わせます(162.9cmを例とする)。

高さ(Height)
1.629にします(Unityの「1」は「1メートル」)。中心(Center)
Yの枠を 0.819 にする(XとZは0のまま)。
(高さ「半分」を中心点にすると、カプセルの底がちょうど足の裏(Y=0)に合わさります)半径(Radius)
0.3 にする。これがカプセルの「太さ」になります。肩幅より少し狭いくらいが、狭い通路も引っかからず歩きやすいです。ステップオフセット(Step Offset)
0.3 にする。これくらいにしておくと、ちょっとした階段や段差でつまずかずに自動で登ってくれます。
アバターを動かすスクリプト
アバターを動かすスクリプト(C#、新Input System対応、カメラ相対、回転、重力)を貼り付けましょう。

Assetsフォルダ内にScriptsというフォルダを新規作成し、その中で「作成」→「Scripting」→「MonoBehaviour スクリプト」。

ファイル名は「PlayerLocomotion」に変更。
using UnityEngine;
using UnityEngine.InputSystem; // 新Input System(最新のコントローラー入力の仕組み)を使うための宣言。
// [RequireComponent] とは?
// 「このスクリプトを使うなら、CharacterControllerも絶対に必要ですよ」とUnityに教える。
// これを書いておくと、スクリプトをアタッチした時にCharacterControllerも自動でくっついてきます(付け忘れ防止)。
[RequireComponent(typeof(CharacterController))]
public class PlayerLocomotion : MonoBehaviour
{
// [Header] はInspector上で見出しを表示し、設定画面を見やすくするための飾りです。
// [SerializeField] をつけると、Unityの画面上(Inspector)から数字を自由に変更できるようになります。
[Header("移動スピードの設定")]
[SerializeField] private float walkSpeed = 2f; // 歩く速さ(1秒間に2メートル進む)。Blend Treeの「2」と同じにします。
[SerializeField] private float runSpeed = 6f; // 走る速さ(1秒間に6メートル進む)。Blend Treeの「6」と同じにします。
[SerializeField] private float rotationSmooth = 0.12f; // 振り向く時の滑らかさ。数字が小さいほどシャキッと機敏に振り向きます。
[Header("重力(落下)の設定")]
[SerializeField] private float gravity = -15f; // 重力の強さ。マイナスは「下向き」を意味します。
[SerializeField] private float groundedGravity = -2f; // 地面に足がついている時、少しだけ下へ押し付ける力(坂道でフワフワ浮かないための工夫です)。
[Header("カメラの参照")]
[SerializeField] private Transform cameraTransform; // 「どっちが前か」を決めるための基準となるカメラを登録する枠です。
// --- 内部で使う部品(変数)の準備 ---
private CharacterController _controller; // さっき付けた「見えないバリア(移動用コンポーネント)」を操作するためのリモコン
private Animator _animator; // 「司令塔(アニメーション)」を操作するためのリモコン
private InputAction _moveAction; // スティックの「前後左右」の入力を受け取る箱
private InputAction _sprintAction; // 「ダッシュボタン(Shiftキー等)」の入力を受け取る箱
private float _verticalVelocity; // 今、縦方向(上下)にどれくらいのスピードが出ているかを記憶するメモ
private float _turnSmoothVelocity; // 滑らかに振り向く計算をするため、Unityが裏で使う専用のメモ帳
// Animatorの "Speed" という文字を、処理が軽い「数字(Hash)」に変換しておく。
// (※ 毎フレーム「Speed」という文字を探すとゲームが重くなるため)
private readonly int _speedHash = Animator.StringToHash("Speed");
// Awake は、ゲームが始まる「一番最初の一瞬」だけ呼ばれる準備の関数です。
private void Awake()
{
// アバターにくっついているコンポーネントを探して、リモコンに登録します。
_controller = GetComponent<CharacterController>();
_animator = GetComponent<Animator>();
// Unity 6標準の Input System から、「Move(移動)」と「Sprint(ダッシュ)」の入力を探して登録します。
_moveAction = InputSystem.actions.FindAction("Move");
_sprintAction = InputSystem.actions.FindAction("Sprint");
// もしInspectorでカメラが設定されていなかったら、自動的に「Main Camera」を探して設定してあげる。
if (cameraTransform == null && Camera.main != null)
{
cameraTransform = Camera.main.transform;
}
}
// Update は、ゲーム中「毎フレーム(1秒間に約60回)」ずっと呼ばれ続けるメインの関数です。
private void Update()
{
// --------------------------------------------------------
// 1. プレイヤーの操作(入力)を読み取る
// --------------------------------------------------------
// スティックの傾き具合を受け取ります(x:左右, y:前後)。全く倒していなければ 0、全開なら 1 や -1 になります。
Vector2 input = _moveAction.ReadValue<Vector2>();
// ダッシュボタンが押されているか確認します。(ボタンが存在しない場合は false になります)
bool isRunning = _sprintAction != null && _sprintAction.IsPressed();
// --------------------------------------------------------
// 2. どちらに進むか(カメラを基準にした方向)を計算する
// --------------------------------------------------------
// 単純に「前(Z軸)」に進むのではなく、「今カメラが向いている方向」を『前』として計算します。(TPSゲームの王道です)
Vector3 camForward = cameraTransform != null ? cameraTransform.forward : Vector3.forward;
Vector3 camRight = cameraTransform != null ? cameraTransform.right : Vector3.right;
// カメラが下や上を向いていても、キャラクターが空を飛んだり地面に潜ったりしないよう、上下(Y)の角度は無視します。
camForward.y = 0f;
camRight.y = 0f;
camForward.Normalize(); // 方向の長さを「1」に綺麗に整えます
camRight.Normalize();
// プレイヤーの入力(前後・左右)と、カメラの方向を掛け合わせて、最終的な「進むべき方向(矢印)」を作ります。
Vector3 moveDir = camForward * input.y + camRight * input.x;
// スティックをどれくらい強く倒しているか(0.0 〜 1.0)を計算します。
float inputMagnitude = Mathf.Clamp01(moveDir.magnitude);
// 斜めにスティックを倒した時、前や横より速く動いてしまうバグ(斜め移動問題)を防ぐため、方向の長さを「1」に整えます。
if (moveDir.sqrMagnitude > 0.0001f)
{
moveDir.Normalize();
}
// --------------------------------------------------------
// 3. 実際の「移動スピード」を決定する
// --------------------------------------------------------
// 走っていれば走るスピード(6)、そうでなければ歩くスピード(2)を選び、そこにスティックの倒し具合(0〜1)を掛け算します。
// (例:歩き状態でスティックを半分倒したら、2 * 0.5 = 時速1になります)
float targetSpeed = (isRunning ? runSpeed : walkSpeed) * inputMagnitude;
// --------------------------------------------------------
// 4. キャラクターを進行方向へ「滑らかに」振り向かせる
// --------------------------------------------------------
// スティックが少しでも倒されていれば(入力があれば)振り向きます。
if (inputMagnitude > 0.01f)
{
// 進みたい方向が、東西南北の「何度(角度)」にあたるかを計算します。
float targetAngle = Mathf.Atan2(moveDir.x, moveDir.z) * Mathf.Rad2Deg;
// 現在の角度から、目標の角度に向かって「フワッ」と滑らかに回転させます(ロボットのようにカクッとならないための工夫)。
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref _turnSmoothVelocity, rotationSmooth);
// 計算した角度をアバターの体に適用します。
transform.rotation = Quaternion.Euler(0f, angle, 0f);
}
// --------------------------------------------------------
// 5. 重力(落下)の計算
// --------------------------------------------------------
if (_controller.isGrounded && _verticalVelocity < 0f)
{
// 足が地面についている時は、軽く地面へ押し付ける力(-2)をかけ続けます。こうすると坂道で跳ねません。
_verticalVelocity = groundedGravity;
}
else
{
// 空中にいる時(落下中)は、時間経過とともにドンドン下向きのスピードを加速させます(現実の重力と同じです)。
_verticalVelocity += gravity * Time.deltaTime;
}
// --------------------------------------------------------
// 6. 実際にキャラクターを動かす
// --------------------------------------------------------
// 平面の移動(進む方向 × スピード)を作ります。
Vector3 horizontal = moveDir * targetSpeed;
// 平面の移動に、縦の移動(重力)を合体させます。
Vector3 velocity = horizontal + Vector3.up * _verticalVelocity;
// 最後に、CharacterControllerのリモコンの「Move」ボタンを押して、アバターを動かします。
// ※Time.deltaTime を掛けることで、パソコンの性能に関わらず全員同じスピードで動くようになります。
_controller.Move(velocity * Time.deltaTime);
// --------------------------------------------------------
// 7. アニメーション(司令塔)に現在のスピードを報告する
// --------------------------------------------------------
// スクリプトで計算した現在のスピード(targetSpeed)を、司令塔の "Speed" メーターに伝えます。
// これにより、さっき作ったBlend Treeが自動で反応し、アバターが歩いたり走ったりします。
_animator.SetFloat(_speedHash, targetSpeed);
}
// アニメーションからの合図を受け取る箱(これがないとエラーが出ます)
private void OnFootstep(AnimationEvent animationEvent)
{
// 将来、ここに「足音の音(SE)を鳴らす」プログラムを追加することができます
}
private void OnLand(AnimationEvent animationEvent)
{
// 将来、ここに「着地のドスッという音を鳴らす」プログラムを追加することができます
}
}PlayerLocomotion.cs のコード。
📝 スクリプトの冒頭で using UnityEngine.InputSystem; という一文を書いて新Input Systemを指定。これにより、スティックやキーボード操作も自動的に「Move(移動)」としてまとめて受け取れることができます(プログラムを別々に書き分ける必要がない)

ヒエラルキーでアバターを選択し、インスペクタ上の「コンポーネントを追加」押下。検索窓に player と入れて、先ほどの PlayerLocomotion を追加します。
■ スクリプトの設定項目

Walk Speed(歩く速さ)は 2、Run Speed(走る速さ)は6とする。
Blend Treeで作ったThreshold(しきい値)の 2 と 6 にピッタリ合わせることで、プログラムの動きとアニメーションが完全に一致します
Camera Transformには、ヒエラルキーのMain Cameraをドラッグ&ドロップで入れます(または「◉」ボタンをクリックして、リストから Main Camera を選択)。
これを入れることで、「カメラが向いている方向」を「前」として歩くようになります。空欄でも自動で探してくれますが、明記しておくと安全です
ゲーム開始「▶」で確認 …… と言いたいところですが、PlayerLocomotion.csで重力について実装しているので、床がないと下に落ちます。
後述の【カメラ追従の実装】【床(Plane)の作成と落下防止】を終えてからテストをしましょう。
カメラ追従の実装
スクリプトを自作する
ゲーム開始でアバターが移動すると、カメラが置いてけぼりになって画面から消えてしまいます。 アバターの背後を追いかけるTPS(三人称視点)カメラを作りましょう。
using UnityEngine;
// アバターの背後をスーッと追いかける、シンプルなカメラ用スクリプトです。
// Hierarchyの「Main Camera」にアタッチして使います。
public class FollowCamera : MonoBehaviour
{
[Header("追いかける対象")]
[SerializeField] private Transform target; // アバター(model_test)を登録する枠です
[Header("カメラの位置と角度")]
// アバターから見て「どれくらい離れた位置」にカメラを置くかの設定です。
// X:左右 (0で真後ろ) / Y:上下 (2.0fなら2m上) / Z:前後 (-3.5fなら3.5m後ろ)
[SerializeField] private Vector3 offset = new Vector3(0f, 2.0f, -3.5f);
[SerializeField] private float followSmooth = 10f; // カメラが追いつく時の滑らかさ(ゴム紐のようなイメージ)
[SerializeField] private float lookHeight = 1.2f; // アバターの「どの高さ」を見つめるか(1.2なら胸〜顔あたり)
// 💡【超重要】Update ではなく「LateUpdate」を使う理由
// Updateは「アバターの移動」などに使われます。
// もしカメラもUpdateで動かしてしまうと、アバターとカメラが同時に動こうとして画面がガクガク震えることがあります。
// LateUpdate(遅れて実行されるUpdate)を使うことで、「アバターが移動を終えた後の最新の位置」を
// カメラが確実に追いかけるようになり、映像がヌルッと滑らかになります!
private void LateUpdate()
{
// もしTarget(アバター)が設定されていなかったら、エラーを防ぐために何もしないで戻ります。
if (target == null) return;
// --------------------------------------------------------
// 1. カメラが行くべき「目標地点」を決める
// --------------------------------------------------------
// アバターの現在地に、オフセット(上と後ろのズレ)を足した場所がゴールです。
Vector3 desiredPosition = target.position + offset;
// --------------------------------------------------------
// 2. カメラを目標地点へ「滑らかに」移動させる
// --------------------------------------------------------
// Lerp(ラープ)は「現在の位置」から「目標の位置」へ、指定したスピードで滑らかに近づける魔法の計算です。
transform.position = Vector3.Lerp(transform.position, desiredPosition, followSmooth * Time.deltaTime);
// --------------------------------------------------------
// 3. カメラの「レンズの向き」をアバターに合わせる
// --------------------------------------------------------
// 足元(target.position)をそのまま見ると画面の下半分が地面になってしまうため、
// 上方向(Vector3.up)に「lookHeight(1.2m)」を足して、アバターの背中や頭のあたりを見つめるようにします。
transform.LookAt(target.position + Vector3.up * lookHeight);
}
}Assets/Scripts内にFollowCamera.csを作成(作成手順は前項を参考)。
■ カメラに「スクリプト」と「目標」をセットする
作ったスクリプトを、最初からあるカメラに貼り付けましょう。

ヒエラルキーのMain Camera をクリックして選択。
インスペクター) の一番下にある空きスペースへ、先ほど作った FollowCamera.cs をドラッグ&ドロップして追加。
追加された Follow Camera の設定項目にある ターゲット(追いかける対象) の枠に、ヒエラルキーのアバターをドラッグ&ドロップで入れます(または「◉」ボタン →リストから選択)。
📝 カメラの設定:オフセット
「アバターから見て、どこにカメラを置くか」を表しています。
・X (0) = 左右のズレ(0なので真後ろ)
・Y (2.0) = 高さ(アバターより2メートル高い位置
・Z (-3.5) = 前後(マイナスなので、アバターの3.5メートル後ろ)
とすれば、「背後の少し高いところから見下ろす」という王道のアングルになります。
Cinemachine(シネマシーン)
今回、学習のためFollowCameraを自作しましたが、実はUnity公式で『Cinemachine(シネマシーン)』という、高機能なカメラツールが用意されてます。
視点(アングル)の自由度
マウスや右スティックを使って、アバターの周りをぐるぐると自由に見回すこと(視点移動)ができます。壁などの「障害物」への対応
「Cinemachine Collider(コライダー)」という追加機能を使うと、カメラとアバターの間に壁が来たとき、自動的に壁を避けてカメラが前にズーム(接近)してくれます。演出やカメラワーク
「Noise(ノイズ)」という設定をいじるだけで、息遣いに合わせたリアルな手ぶれ感を出せます。 また、イベントシーンで「アバターと敵のドアップ」切り替えを、映画のように滑らかに行うこともできます。
など、自作では難しいことを簡単な設定のみでやってくれます。
インストールしましょう。まずは「パッケージマネージャー」画面を表示します。(上部メニュー)「ウィンドウ」→「Package Management」→「パッケージマネージャー」。

「Unity レジストリ」を選択。検索窓に「cinemachine」と入力、『Cinemachine』を選択して「インストール」押下。
自作の「FollowCamera」から『Cinemachine』への切り替えましょう。

メインカメラをオフにする
ヒエラルキー「Main Camera」 を選択。インスペクターのFollow Camera (スクリプト)のチェックを外して「オフ」にする。Cinemachineカメラの作成(Cinemachineのインストール済みを前提)(上部メニュー)「ゲームオブジェクト」→「Cinemachine」→「Cinemachine Camera」。そうすると、ヒエラルキーに「CinemachineCamera」オブジェクトが追加され、Main Camera には Cinemachine Brain(脳みそ)という部品が自動的にくっ付く。
追いかける対象(アバター)をセットしましょう。

ヒエラルキーの「CinemachineCamera」を選択。インスペクターのTracking Targetに、ヒエラルキーのアバターをドラッグ&ドロップ。
カメラのスタイルを「軌道追従」にしましょう。

インスペクターのProcedural Components(手続き型コンポーネント)欄のPosition Controlを「Orbital Follow(軌道追従)」にする。
Rotation Controlを『Hard Look At』にする。
「コンポーネントを追加」押下、 検索窓に Input Axis と入力し、『Cinemachine Input Axis Controller』を追加。
Driven Axes欄の「Look Orbit X」と「Look Orbit Y」を『Look』にする。
※ Look At Offset などを調整する
設定項目の用語解説
Orbital Follow(軌道追従)
ターゲットの周囲を「衛星」のようにぐるぐると回る軌道(オービット)に沿って、カメラを配置/追従させるための機能です。 単純な追従(親子関係にするなど)とは異なり、ターゲットが向いている方向とは無関係に、マウス操作だけでカメラ位置をターゲットの周囲の好きな角度に移動させることができます。TPSゲームにおける「自由視点カメラ」を実現するための、まさに心臓部となる設定です。Hard Look At(直視モード)
カメラがターゲット(アバター)を「常に真っ直ぐ見つめ続ける」ための回転制御設定です。「Hard」は「滑らかさ(ラグ)を挟まない」という意味で、カメラがターゲットを常に中心に捉え続けようとするため、TPSカメラの視界が安定します。Cinemachine Input Axis Controller(入力制御リモコン)
マウスやトラックパッドから送られてくる「操作データ」を、Cinemachineの「カメラ回転データ」に変換して中継する役割を持ちます。これがないと、どれだけ指を動かしてもカメラに命令が届かないため、操作の橋渡し役として必須のコンポーネントです。Look Orbit X / Y(回転軸の指定)
カメラを回すための「軸」です。
・Look Orbit X (横軸):左右への回転です(首を横に振る動き)。
・Look Orbit Y (縦軸):上下への回転です(首を縦に振る動き)。 これらを入力(Look)と結びつけることで、トラックパッドの動きが「左右・上下の視点移動」として機能するようになります。
床(Plane)の作成と落下防止
床のオブジェクトを作成する
アバターに付けた移動スクリプト(重力の処理)によって、今の状態では空中に置いても無限に落下してしまいます。足場となる「地面」を作りましょう。
(上段メニュー)「ゲームオブジェクト」→「3D オブジェクト」 → 「平面(Plane)」 。オブジェクト名を Ground に変更。
インスペクターで『位置(Position)』 が X: 0, Y: 0, Z: 0 になっていることを確認。
また、Scale(スケール) を X: 5, Y: 1, Z: 5 などに変更して、広々とした地面にしておく。
📝 外部から取り込む3Dアセットとは違い、Unity側で作成する「3D オブジェクト」には最初から「コライダー(見えない当たり判定)」が組み込まれているため、特別な設定をしなくてもアバターが床に乗ってくれます。
■ 床が「ピンク色」になった場合
もし、床が「ピンク色」になった場合は、表面の素材(マテリアル)の設定エラーです。以下の手順で直します。
プロジェクトウィンドウのAssets内の任意のフォルダにて右クリック:作成 → マテリアル を選択。
新しいマテリアルが作成されるので、そのまま画面上のピンクの床に向かってドラッグ&ドロップして割り当てる。
アバターの足元を合わせる
アバターを選択し、位置(Position)の 『位置(Position)』 のY が 1 になっているか確認(床に合わせて、埋もれたりしないようにする)。また、アバターを囲んでいる緑色のカプセル(CharacterController)の底が、床の表面にピタッと接しているかも確認します。

ゲーム開始「▶」で、アバターの移動が出来るかなどを確認します。
【補足】「ポインタ(トラックパッド/マウス)」による視点方向の変換が出来ない場合
※ 原因は様々ですが、ここでは典型例を1つ挙げます。
Cinemachineは「どんなフレームレートの環境でも同じ速さで動くように」と、受け取った入力値に自動的に Time.deltaTime(約 1/60 秒)を掛け算して補正しようとします。
ところが、Deltaタイプ(トラックパッドやマウス特有の、フレームごとの相対的な移動量)の入力にこれをやると二重にフレーム換算され、値が約60分の1になってしまいます。したがって、自動補正をオフにする必要があります。

Cinemachine Input Axis ControllerのDriven Axes欄を見ます。
「Look Orbit X」を展開し『Cancel Delta Time』をオンにします(必要に応じ「Look Orbit Y」も)。
【補足】シフト(Shift)キー押してもダッシュしない場合
■ 左シフト(Shift)を押すこと
以下、Unity公式のデフォルトテンプレートの「InputSystem_Actions」の Sprint アクションのバインド内容の抜粋です。
"name": "Sprint",
"type": "Button",
"interactions": "", ← 特殊なインタラクションなし
"path": "<Keyboard>/leftShift" ← 左Shiftのみつまりダッシュは『左シフト(Shift)』のみです。
📝 Input Actionsエディタで Action Maps「Player」→ Actions「Sprint」 を選び、バインドを追加すると、右Shiftも割り当てることができます。
■ 入力は『英数』にしておく
入力を「かな」ではなく『英数』にしてください。
「かな」入力中はmacOSのIME側もShiftキーを監視 / 処理しようとするため、キーの状態通知がIME側の処理に割り込まれ、Unity Input Systemに「Shiftが押された」という情報が安定して届かなくなります。
📝 未検証ですが、Windowsでも似た現象が起きるかも知れません。
ジャンプを実装する
主旨
これまで作った PlayerLocomotion.cs(歩行、ダッシュ、カメラ相対移動)に『ジャンプ』を追加実装したいと思います。
一気に完成形を目指すと「物理的にジャンプする(C#コード)」と「見た目も跳んでいるように見せる(Animator Controller)」の両方を同時に触ることになり、どこが原因で動かないのか分かりにくくなります。そこで今回は、フェーズを2つに分けます。
物理的にジャンプする
アバターの座標が実際に宙に浮く。見た目はまだIdle/Walkのまま。見た目も跳んでいるように見せる
Animator Controllerを拡張し、空中用のポーズを追加する
一応、フェーズ1だけでも「ジャンプできた」と体感できます。
【フェーズ1】物理的にジャンプする(コード追加のみ)
CharacterControllerは「位置の計算」と「アニメーションの見た目」が完全に独立した仕組みです。よって、アニメーションを一切触らなくても、コードだけでアバターの座標を宙に浮かせることができます。
PlayerLocomotion.cs に、以下を追記していきます。
📝 追記箇所は『💡』をつけてます(全体コードで区別しやすいよう)
【1】フィールド(変数)の追加
クラスの上のほう、[Header("重力(落下)の設定")] の下あたりに追加します。
//💡ジャンプの最高到達点(メートル)。1.5なら大体キャラの背丈くらい跳びます。
[Header("ジャンプの設定")]
[SerializeField] private float jumpHeight = 1.5f;
//💡「ジャンプボタン(スペースキー等)」の入力を受け取る箱
private InputAction _jumpAction; 初学者にとっては「初速◯m/s」より「◯メートル跳ぶ」の方が直感的に調整しやすいので、今回「ジャンプの速さ」ではなく「高さ」で設定してます。なお、高さから必要な初速を逆算する計算は、後述のコードが自動でやってくれます。
【2】Awake() 内に追加
_sprintAction = InputSystem.actions.FindAction("Sprint"); の下に1行足します。
//💡Unity6標準のInputSystem_Actionsには、最初から"Jump"(スペースキー)が用意されています。
_jumpAction = InputSystem.actions.FindAction("Jump");Unity 6で新規プロジェクトを作ると自動生成される InputSystem_Actions には、Move・Look・Sprintと並んで、Jumpアクションも最初からスペースキーに割り当て済みです。新しくアクションを作る必要はありません。
【3】Update() 内、重力処理のすぐ「前」に追加
既存コードの「5. 重力(落下)の計算」ブロックの直前に、以下を挿入します。
// --------------------------------------------------------
// 💡4.5. ジャンプの処理(回転の処理と、重力の処理の「間」に置きます)
// --------------------------------------------------------
// 「地面に足がついている」かつ「今まさにジャンプボタンが押された瞬間」だけ反応します。
// isGroundedのチェックがないと、空中で何度もボタン連打→無限に跳び続けてしまいます。
if (_controller.isGrounded && _jumpAction.WasPressedThisFrame())
{
// 物理の公式(自由落下の式)を逆算して、「欲しい高さ」ちょうどに届く初速を求めます。
// v = √(2 × 高さ × 重力の強さ) ※gravityはマイナスの値なので、掛け算した結果がプラスになるよう-2倍しています
_verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
}WasPressedThisFrame() とは「ボタンが押されているかどうか」ではなく「今フレームでちょうど押された瞬間かどうか」を判定するメソッドです。Sprintで使った IsPressed()(押しっぱなしの間ずっとtrue)とは違い、こちらは1回押すごとに1回だけtrueになるため、スペースキーを押しっぱなしにしても連続ジャンプしません。
📝 処理の順番自体に厳密な決まりはありませんが「⑤重力の計算」がこの_verticalVelocityの値を読んで上書き・加算していくため、重力の計算より前に初速をセットしておく必要があります。これにより、ジャンプした直後のフレームから正しく重力が効き始めます。
■ ここまでの全体コード(PlayerLocomotion.cs)
using UnityEngine;
using UnityEngine.InputSystem; // 新Input System(最新のコントローラー入力の仕組み)を使うための宣言。
// [RequireComponent] とは?
// 「このスクリプトを使うなら、CharacterControllerも絶対に必要ですよ」とUnityに教える。
// これを書いておくと、スクリプトをアタッチした時にCharacterControllerも自動でくっついてきます(付け忘れ防止)。
[RequireComponent(typeof(CharacterController))]
public class PlayerLocomotion : MonoBehaviour
{
// [Header] はInspector上で見出しを表示し、設定画面を見やすくするための飾りです。
// [SerializeField] をつけると、Unityの画面上(Inspector)から数字を自由に変更できるようになります。
[Header("移動スピードの設定")]
[SerializeField] private float walkSpeed = 2f; // 歩く速さ(1秒間に2メートル進む)。Blend Treeの「2」と同じにします。
[SerializeField] private float runSpeed = 6f; // 走る速さ(1秒間に6メートル進む)。Blend Treeの「6」と同じにします。
[SerializeField] private float rotationSmooth = 0.12f; // 振り向く時の滑らかさ。数字が小さいほどシャキッと機敏に振り向きます。
[Header("重力(落下)の設定")]
[SerializeField] private float gravity = -15f; // 重力の強さ。マイナスは「下向き」を意味します。
[SerializeField] private float groundedGravity = -2f; // 地面に足がついている時、少しだけ下へ押し付ける力(坂道でフワフワ浮かないための工夫です)。
//💡ジャンプの最高到達点(メートル)。1.5なら大体キャラの背丈くらい跳びます。
[Header("ジャンプの設定")]
[SerializeField] private float jumpHeight = 1.5f;
//💡「ジャンプボタン(スペースキー等)」の入力を受け取る箱
private InputAction _jumpAction;
[Header("カメラの参照")]
[SerializeField] private Transform cameraTransform; // 「どっちが前か」を決めるための基準となるカメラを登録する枠です。
// --- 内部で使う部品(変数)の準備 ---
private CharacterController _controller; // さっき付けた「見えないバリア(移動用コンポーネント)」を操作するためのリモコン
private Animator _animator; // 「司令塔(アニメーション)」を操作するためのリモコン
private InputAction _moveAction; // スティックの「前後左右」の入力を受け取る箱
private InputAction _sprintAction; // 「ダッシュボタン(Shiftキー等)」の入力を受け取る箱
private float _verticalVelocity; // 今、縦方向(上下)にどれくらいのスピードが出ているかを記憶するメモ
private float _turnSmoothVelocity; // 滑らかに振り向く計算をするため、Unityが裏で使う専用のメモ帳
// Animatorの "Speed" という文字を、処理が軽い「数字(Hash)」に変換しておく。
// (※ 毎フレーム「Speed」という文字を探すとゲームが重くなるため)
private readonly int _speedHash = Animator.StringToHash("Speed");
// Awake は、ゲームが始まる「一番最初の一瞬」だけ呼ばれる準備の関数です。
private void Awake()
{
// アバターにくっついているコンポーネントを探して、リモコンに登録します。
_controller = GetComponent<CharacterController>();
_animator = GetComponent<Animator>();
// Unity 6標準の Input System から、「Move(移動)」と「Sprint(ダッシュ)」の入力を探して登録します。
_moveAction = InputSystem.actions.FindAction("Move");
_sprintAction = InputSystem.actions.FindAction("Sprint");
//💡Unity6標準のInputSystem_Actionsには、最初から"Jump"(スペースキー)が用意されています。
_jumpAction = InputSystem.actions.FindAction("Jump");
// もしInspectorでカメラが設定されていなかったら、自動的に「Main Camera」を探して設定してあげる。
if (cameraTransform == null && Camera.main != null)
{
cameraTransform = Camera.main.transform;
}
}
// Update は、ゲーム中「毎フレーム(1秒間に約60回)」ずっと呼ばれ続けるメインの関数です。
private void Update()
{
// --------------------------------------------------------
// 1. プレイヤーの操作(入力)を読み取る
// --------------------------------------------------------
// スティックの傾き具合を受け取ります(x:左右, y:前後)。全く倒していなければ 0、全開なら 1 や -1 になります。
Vector2 input = _moveAction.ReadValue<Vector2>();
// ダッシュボタンが押されているか確認します。(ボタンが存在しない場合は false になります)
bool isRunning = _sprintAction != null && _sprintAction.IsPressed();
// --------------------------------------------------------
// 2. どちらに進むか(カメラを基準にした方向)を計算する
// --------------------------------------------------------
// 単純に「前(Z軸)」に進むのではなく、「今カメラが向いている方向」を『前』として計算します。(TPSゲームの王道です)
Vector3 camForward = cameraTransform != null ? cameraTransform.forward : Vector3.forward;
Vector3 camRight = cameraTransform != null ? cameraTransform.right : Vector3.right;
// カメラが下や上を向いていても、キャラクターが空を飛んだり地面に潜ったりしないよう、上下(Y)の角度は無視します。
camForward.y = 0f;
camRight.y = 0f;
camForward.Normalize(); // 方向の長さを「1」に綺麗に整えます
camRight.Normalize();
// プレイヤーの入力(前後・左右)と、カメラの方向を掛け合わせて、最終的な「進むべき方向(矢印)」を作ります。
Vector3 moveDir = camForward * input.y + camRight * input.x;
// スティックをどれくらい強く倒しているか(0.0 〜 1.0)を計算します。
float inputMagnitude = Mathf.Clamp01(moveDir.magnitude);
// 斜めにスティックを倒した時、前や横より速く動いてしまうバグ(斜め移動問題)を防ぐため、方向の長さを「1」に整えます。
if (moveDir.sqrMagnitude > 0.0001f)
{
moveDir.Normalize();
}
// --------------------------------------------------------
// 3. 実際の「移動スピード」を決定する
// --------------------------------------------------------
// 走っていれば走るスピード(6)、そうでなければ歩くスピード(2)を選び、そこにスティックの倒し具合(0〜1)を掛け算します。
// (例:歩き状態でスティックを半分倒したら、2 * 0.5 = 時速1になります)
float targetSpeed = (isRunning ? runSpeed : walkSpeed) * inputMagnitude;
// --------------------------------------------------------
// 4. キャラクターを進行方向へ「滑らかに」振り向かせる
// --------------------------------------------------------
// スティックが少しでも倒されていれば(入力があれば)振り向きます。
if (inputMagnitude > 0.01f)
{
// 進みたい方向が、東西南北の「何度(角度)」にあたるかを計算します。
float targetAngle = Mathf.Atan2(moveDir.x, moveDir.z) * Mathf.Rad2Deg;
// 現在の角度から、目標の角度に向かって「フワッ」と滑らかに回転させます(ロボットのようにカクッとならないための工夫)。
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref _turnSmoothVelocity, rotationSmooth);
// 計算した角度をアバターの体に適用します。
transform.rotation = Quaternion.Euler(0f, angle, 0f);
}
// --------------------------------------------------------
// 💡4.5. ジャンプの処理(回転の処理と、重力の処理の「間」に置きます)
// --------------------------------------------------------
// 「地面に足がついている」かつ「今まさにジャンプボタンが押された瞬間」だけ反応します。
// isGroundedのチェックがないと、空中で何度もボタン連打→無限に跳び続けてしまいます。
if (_controller.isGrounded && _jumpAction.WasPressedThisFrame())
{
// 物理の公式(自由落下の式)を逆算して、「欲しい高さ」ちょうどに届く初速を求めます。
// v = √(2 × 高さ × 重力の強さ) ※gravityはマイナスの値なので、掛け算した結果がプラスになるよう-2倍しています
_verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
}
// --------------------------------------------------------
// 5. 重力(落下)の計算
// --------------------------------------------------------
if (_controller.isGrounded && _verticalVelocity < 0f)
{
// 足が地面についている時は、軽く地面へ押し付ける力(-2)をかけ続けます。こうすると坂道で跳ねません。
_verticalVelocity = groundedGravity;
}
else
{
// 空中にいる時(落下中)は、時間経過とともにドンドン下向きのスピードを加速させます(現実の重力と同じです)。
_verticalVelocity += gravity * Time.deltaTime;
}
// --------------------------------------------------------
// 6. 実際にキャラクターを動かす
// --------------------------------------------------------
// 平面の移動(進む方向 × スピード)を作ります。
Vector3 horizontal = moveDir * targetSpeed;
// 平面の移動に、縦の移動(重力)を合体させます。
Vector3 velocity = horizontal + Vector3.up * _verticalVelocity;
// 最後に、CharacterControllerのリモコンの「Move」ボタンを押して、アバターを動かします。
// ※Time.deltaTime を掛けることで、パソコンの性能に関わらず全員同じスピードで動くようになります。
_controller.Move(velocity * Time.deltaTime);
// --------------------------------------------------------
// 7. アニメーション(司令塔)に現在のスピードを報告する
// --------------------------------------------------------
// スクリプトで計算した現在のスピード(targetSpeed)を、司令塔の "Speed" メーターに伝えます。
// これにより、さっき作ったBlend Treeが自動で反応し、アバターが歩いたり走ったりします。
_animator.SetFloat(_speedHash, targetSpeed);
}
// アニメーションからの合図を受け取る箱(これがないとエラーが出ます)
private void OnFootstep(AnimationEvent animationEvent)
{
// 将来、ここに「足音の音(SE)を鳴らす」プログラムを追加することができます
}
private void OnLand(AnimationEvent animationEvent)
{
// 将来、ここに「着地のドスッという音を鳴らす」プログラムを追加することができます
}
}■ 動作確認
ゲーム開始「▶」で確認します。
スペースキー押下時に、アバターがふわっと浮いて着地すれば成功です。
📝 モーションは未実装なので、床だけが動いているような感じに見えるかも知れません。
【フェーズ2】見た目も跳んでいるように見せる(Animator拡張)
『Starter Assets - ThirdPerson(Assets/StarterAssets/ThirdPersonController/CharacterAnimations)』には、ジャンプ用のクリップ「Jump(ジャンプ開始)」「 Fall(空中)」「 Land(着地)」の3状態が用意されてます。
■「アニメーターコントローラー」にパラメータを追加

model_test_Controller をダブルクリックしてアニメーターウィンドウを開く
「パラメーター」タブで「+」 → Bool を選び、名前を Grounded(接地しているかどうか)にする。
同様に「+」→ Trigger で Jump(ジャンプの合図)も追加する
📝 なぜBoolとTriggerを使い分けるのか?
Groundedは「地面についているか、いないか」という状態なので、ずっとON/OFFが持続するBool型が向いています。一方 Jump は「今この瞬間、跳んだ」という一度きりの出来事の合図なので、Trigger型(1回消費されると自動でリセットされる)が向いています。
■ 新しいステート(State)を追加する


Base Layerの空きスペースで右クリック「ステートを作成」→「空」 を3つ作る
1つ目のMotionに JumpStart を設定し、名前を Jump にする
2つ目のMotionに InAir を設定し、名前を Fall にする
3つ目のMotionに JumpLand を設定し、名前を Land にする
既存の Locomotion(Idle/Walk/RunのBlend Tree)とあわせて、Locomotion / Jump / Fall / Land の4つのステートが並んだ状態になります。
■ 状態の遷移(矢印)を組む

上の表の4本の矢印は、それぞれ以下の手順で作成します(1本につき、この一連の操作を1セット行います)。
① 矢印を引く

出発点にしたいステート(例:Locomotion)を右クリック
出てきたメニューから Make Transition を選ぶ
マウスカーソルに矢印の先端がくっついてくるので、そのまま到達点にしたいState(例:Jump)まで動かしてクリック
StateとStateの間に白い矢印線が引かれれば成功
② 条件(Conditions)を設定する
矢印を引いただけでは「いつ切り替わるか」が決まっていないので、続けて設定します。

今作った矢印そのものをクリックして選択する(ステートではなく、線の部分をクリック)
右側のインスペクターに遷移の詳細が表示される
一番下あたりにある Conditions の欄の + ボタンをクリック
ドロップダウンから、パラメータ名(例:Grounded)を選ぶ
その隣の条件(true / false)を選ぶ
📝 例えば「Locomotion → Jump」の矢印なら、Conditionsに Grounded / false を設定します。Grounded条件を使わず「時間経過で自動遷移」させたい矢印(Jump→Fall、Land→Locomotion)は、Conditionsは空のままでOKです。
③ 時間終了あり(Has Exit Time) / Transition Durationを調整する

時間終了あり(Has Exit Time)
チェックボックス。「アニメーションの再生が終わるまで待ってから遷移する」かどうか。Locomotion→Jump、Fall→Landのように入力(Groundedの変化)で即座に切り替えたい遷移は、チェックを外す
Jump→Fall、Land→Locomotionのようにモーションの再生完了を待ちたい遷移は、チェックを入れる
Transition Duration:数値スライダー。次のステートへ何秒かけてブレンドするか。目安として 0.1〜0.25秒 程度

これを、4本の矢印(Locomotion→Jump、Jump→Fall、Fall→Land、Land→Locomotion)それぞれについて繰り返します。
■ スクリプトに Grounded の反映を追加
・クラス上部の_speedHashの隣に追加
//💡Animatorの "Grounded" という文字も、事前にHash化しておきます(クラス上部の_speedHashの隣に追加)
private readonly int _groundedHash = Animator.StringToHash("Grounded");
private readonly int _jumpHash = Animator.StringToHash("Jump");・Update()の最後、Speedを報告している行のすぐ下に追加
//💡「今、地面についているか」を毎フレームAnimatorへ伝える
_animator.SetBool(_groundedHash, _controller.isGrounded);・さらに【フェーズ1】で追加した「ジャンプの処理」ブロックの中、_verticalVelocity = ... の行の直後に1行追加します。
if (_controller.isGrounded && _jumpAction.WasPressedThisFrame())
{
_verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
//💡「今まさに跳んだ」という合図をAnimatorに送り、Jumpモーションを再生させる
_animator.SetTrigger(_jumpHash);
}■ 全体コード(PlayerLocomotion.cs)
using UnityEngine;
using UnityEngine.InputSystem; // 新Input System(最新のコントローラー入力の仕組み)を使うための宣言。
// [RequireComponent] とは?
// 「このスクリプトを使うなら、CharacterControllerも絶対に必要ですよ」とUnityに教える。
// これを書いておくと、スクリプトをアタッチした時にCharacterControllerも自動でくっついてきます(付け忘れ防止)。
[RequireComponent(typeof(CharacterController))]
public class PlayerLocomotion : MonoBehaviour
{
// [Header] はInspector上で見出しを表示し、設定画面を見やすくするための飾りです。
// [SerializeField] をつけると、Unityの画面上(Inspector)から数字を自由に変更できるようになります。
[Header("移動スピードの設定")]
[SerializeField] private float walkSpeed = 2f; // 歩く速さ(1秒間に2メートル進む)。Blend Treeの「2」と同じにします。
[SerializeField] private float runSpeed = 6f; // 走る速さ(1秒間に6メートル進む)。Blend Treeの「6」と同じにします。
[SerializeField] private float rotationSmooth = 0.12f; // 振り向く時の滑らかさ。数字が小さいほどシャキッと機敏に振り向きます。
[Header("重力(落下)の設定")]
[SerializeField] private float gravity = -15f; // 重力の強さ。マイナスは「下向き」を意味します。
[SerializeField] private float groundedGravity = -2f; // 地面に足がついている時、少しだけ下へ押し付ける力(坂道でフワフワ浮かないための工夫です)。
//💡ジャンプの最高到達点(メートル)。1.5なら大体キャラの背丈くらい跳びます。
[Header("ジャンプの設定")]
[SerializeField] private float jumpHeight = 1.5f;
//💡「ジャンプボタン(スペースキー等)」の入力を受け取る箱
private InputAction _jumpAction;
[Header("カメラの参照")]
[SerializeField] private Transform cameraTransform; // 「どっちが前か」を決めるための基準となるカメラを登録する枠です。
// --- 内部で使う部品(変数)の準備 ---
private CharacterController _controller; // さっき付けた「見えないバリア(移動用コンポーネント)」を操作するためのリモコン
private Animator _animator; // 「司令塔(アニメーション)」を操作するためのリモコン
private InputAction _moveAction; // スティックの「前後左右」の入力を受け取る箱
private InputAction _sprintAction; // 「ダッシュボタン(Shiftキー等)」の入力を受け取る箱
private float _verticalVelocity; // 今、縦方向(上下)にどれくらいのスピードが出ているかを記憶するメモ
private float _turnSmoothVelocity; // 滑らかに振り向く計算をするため、Unityが裏で使う専用のメモ帳
// Animatorの "Speed" という文字を、処理が軽い「数字(Hash)」に変換しておく。
// (※ 毎フレーム「Speed」という文字を探すとゲームが重くなるため)
private readonly int _speedHash = Animator.StringToHash("Speed");
//💡Animatorの "Grounded" という文字も、事前にHash化しておきます(クラス上部の_speedHashの隣に追加)
private readonly int _groundedHash = Animator.StringToHash("Grounded");
private readonly int _jumpHash = Animator.StringToHash("Jump");
// Awake は、ゲームが始まる「一番最初の一瞬」だけ呼ばれる準備の関数です。
private void Awake()
{
// アバターにくっついているコンポーネントを探して、リモコンに登録します。
_controller = GetComponent<CharacterController>();
_animator = GetComponent<Animator>();
// Unity 6標準の Input System から、「Move(移動)」と「Sprint(ダッシュ)」の入力を探して登録します。
_moveAction = InputSystem.actions.FindAction("Move");
_sprintAction = InputSystem.actions.FindAction("Sprint");
//💡Unity6標準のInputSystem_Actionsには、最初から"Jump"(スペースキー)が用意されています。
_jumpAction = InputSystem.actions.FindAction("Jump");
// もしInspectorでカメラが設定されていなかったら、自動的に「Main Camera」を探して設定してあげる。
if (cameraTransform == null && Camera.main != null)
{
cameraTransform = Camera.main.transform;
}
}
// Update は、ゲーム中「毎フレーム(1秒間に約60回)」ずっと呼ばれ続けるメインの関数です。
private void Update()
{
// --------------------------------------------------------
// 1. プレイヤーの操作(入力)を読み取る
// --------------------------------------------------------
// スティックの傾き具合を受け取ります(x:左右, y:前後)。全く倒していなければ 0、全開なら 1 や -1 になります。
Vector2 input = _moveAction.ReadValue<Vector2>();
// ダッシュボタンが押されているか確認します。(ボタンが存在しない場合は false になります)
bool isRunning = _sprintAction != null && _sprintAction.IsPressed();
// --------------------------------------------------------
// 2. どちらに進むか(カメラを基準にした方向)を計算する
// --------------------------------------------------------
// 単純に「前(Z軸)」に進むのではなく、「今カメラが向いている方向」を『前』として計算します。(TPSゲームの王道です)
Vector3 camForward = cameraTransform != null ? cameraTransform.forward : Vector3.forward;
Vector3 camRight = cameraTransform != null ? cameraTransform.right : Vector3.right;
// カメラが下や上を向いていても、キャラクターが空を飛んだり地面に潜ったりしないよう、上下(Y)の角度は無視します。
camForward.y = 0f;
camRight.y = 0f;
camForward.Normalize(); // 方向の長さを「1」に綺麗に整えます
camRight.Normalize();
// プレイヤーの入力(前後・左右)と、カメラの方向を掛け合わせて、最終的な「進むべき方向(矢印)」を作ります。
Vector3 moveDir = camForward * input.y + camRight * input.x;
// スティックをどれくらい強く倒しているか(0.0 〜 1.0)を計算します。
float inputMagnitude = Mathf.Clamp01(moveDir.magnitude);
// 斜めにスティックを倒した時、前や横より速く動いてしまうバグ(斜め移動問題)を防ぐため、方向の長さを「1」に整えます。
if (moveDir.sqrMagnitude > 0.0001f)
{
moveDir.Normalize();
}
// --------------------------------------------------------
// 3. 実際の「移動スピード」を決定する
// --------------------------------------------------------
// 走っていれば走るスピード(6)、そうでなければ歩くスピード(2)を選び、そこにスティックの倒し具合(0〜1)を掛け算します。
// (例:歩き状態でスティックを半分倒したら、2 * 0.5 = 時速1になります)
float targetSpeed = (isRunning ? runSpeed : walkSpeed) * inputMagnitude;
// --------------------------------------------------------
// 4. キャラクターを進行方向へ「滑らかに」振り向かせる
// --------------------------------------------------------
// スティックが少しでも倒されていれば(入力があれば)振り向きます。
if (inputMagnitude > 0.01f)
{
// 進みたい方向が、東西南北の「何度(角度)」にあたるかを計算します。
float targetAngle = Mathf.Atan2(moveDir.x, moveDir.z) * Mathf.Rad2Deg;
// 現在の角度から、目標の角度に向かって「フワッ」と滑らかに回転させます(ロボットのようにカクッとならないための工夫)。
float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref _turnSmoothVelocity, rotationSmooth);
// 計算した角度をアバターの体に適用します。
transform.rotation = Quaternion.Euler(0f, angle, 0f);
}
// --------------------------------------------------------
// 💡4.5. ジャンプの処理(回転の処理と、重力の処理の「間」に置きます)
// --------------------------------------------------------
// 「地面に足がついている」かつ「今まさにジャンプボタンが押された瞬間」だけ反応します。
// isGroundedのチェックがないと、空中で何度もボタン連打→無限に跳び続けてしまいます。
if (_controller.isGrounded && _jumpAction.WasPressedThisFrame())
{
// 物理の公式(自由落下の式)を逆算して、「欲しい高さ」ちょうどに届く初速を求めます。
// v = √(2 × 高さ × 重力の強さ) ※gravityはマイナスの値なので、掛け算した結果がプラスになるよう-2倍しています
_verticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);
//💡「今まさに跳んだ」という合図をAnimatorに送り、Jumpモーションを再生させる
_animator.SetTrigger(_jumpHash);
}
// --------------------------------------------------------
// 5. 重力(落下)の計算
// --------------------------------------------------------
if (_controller.isGrounded && _verticalVelocity < 0f)
{
// 足が地面についている時は、軽く地面へ押し付ける力(-2)をかけ続けます。こうすると坂道で跳ねません。
_verticalVelocity = groundedGravity;
}
else
{
// 空中にいる時(落下中)は、時間経過とともにドンドン下向きのスピードを加速させます(現実の重力と同じです)。
_verticalVelocity += gravity * Time.deltaTime;
}
// --------------------------------------------------------
// 6. 実際にキャラクターを動かす
// --------------------------------------------------------
// 平面の移動(進む方向 × スピード)を作ります。
Vector3 horizontal = moveDir * targetSpeed;
// 平面の移動に、縦の移動(重力)を合体させます。
Vector3 velocity = horizontal + Vector3.up * _verticalVelocity;
// 最後に、CharacterControllerのリモコンの「Move」ボタンを押して、アバターを動かします。
// ※Time.deltaTime を掛けることで、パソコンの性能に関わらず全員同じスピードで動くようになります。
_controller.Move(velocity * Time.deltaTime);
// --------------------------------------------------------
// 7. アニメーション(司令塔)に現在のスピードを報告する
// --------------------------------------------------------
// スクリプトで計算した現在のスピード(targetSpeed)を、司令塔の "Speed" メーターに伝えます。
// これにより、さっき作ったBlend Treeが自動で反応し、アバターが歩いたり走ったりします。
_animator.SetFloat(_speedHash, targetSpeed);
//💡「今、地面についているか」を毎フレームAnimatorへ伝える
_animator.SetBool(_groundedHash, _controller.isGrounded);
}
// アニメーションからの合図を受け取る箱(これがないとエラーが出ます)
private void OnFootstep(AnimationEvent animationEvent)
{
// 将来、ここに「足音の音(SE)を鳴らす」プログラムを追加することができます
}
private void OnLand(AnimationEvent animationEvent)
{
// 将来、ここに「着地のドスッという音を鳴らす」プログラムを追加することができます
}
}■ 動作確認
ゲーム開始「▶」で確認します。
Humanoid型(人型)アバターを動かしてみたぞ🏃♀️#unity pic.twitter.com/obdUitbb0Y
— 辛島信芳@Androidアプリ開発🦖 (@quiet_sea) July 22, 2026
スペースキー押下時に、アバターのモーションが付加されていれば成功です。
■ よくあるつまずき

こちら、よくあるつまづきです。ご参考までに。
いいなと思ったら応援しよう!
ここまで読んでくれてありがとうございます!記事は基本的に無料で出していくので、気が向いたら応援してもらえると嬉しいです☕