UnityでのTPSの移動方法
以前の記事でUnityのゲーム制作で最初にやるべきことはプラットフォームをどれにするか決めることだと述べた。プラットフォーム選び
これからゲーム作品を作るにあたり自分はPCで3DのTPS(三人称視点つまり主人公の後方に視点がある)アクションゲームを作ってみようと思う。
まずは3Dマップでキャラを移動させる方法を紹介していく(PCゲーム以外を作る人以外でも参考になると思うので、ぜひ確認してみてほしい)。
詳しく説明するつもりだが、さすがに操作やC#など基礎がわかってない場合は理解が難しいと思うので、先に以下を確認してほしい。
Unityの基礎まとめ
C#の基礎まとめ
TPSでの移動方法目次
プロジェクトビューにフォルダ作成キャラが乗る床を作成
アセットストアでキャラグラフィックを入手
キャラをプレハブ化する
カメラ実装
アニメーション設定
移動操作設定
追記(前後アニメーション追加)
目次にもどる
プロジェクトビューにフォルダ作成
プロジェクトビューにAssetsのフォルダが最初から存在する。その下にAppのフォルダを作り、その下にAnimation、Animatorcontroller、Material、Prefab、Scriptのフォルダを作成しておこう。また、既にあるScenesのフォルダをAppの下に移動させよう。
目次にもどる
キャラが乗る床を作成
ゲームオブジェクトのPlaneを作成してキャラが乗る床を作ろう。目次にもどる
アセットストアでキャラグラフィックを入手
アセットストアで3Dキャラモデルを入手しよう。今回は以下の3Dキャラモデルを使っていく(2023年3月現在はフリー)。これをダウンロードしてほしい。なおアセットストアが何かやダウンロードの仕方がよくわからない場合は以下の記事を確認してほしい。
アセットストアの使い方
目次にもどる
キャラをプレハブ化する
3Dキャラモデルをインポート後、Asset→Kevin lglesias→Basic Motions→Prefabsを開く。そのフォルダの中にあるBasicMotionDummyをヒエラルキー欄にドラッグ&ドロップする。
ヒエラルキー欄にゲームオブジェクトとして3Dキャラモデルが入るので、名前をPlayerに変更しよう。
その上でPlayerをApp→Prefabのフォルダにドラッグ&ドロップする(先ほど作ったフォルダ)。その際以下の表示が出るので「Original Prefab」を選択しよう。
これでPlayerのプレハブ化が完了した。プレハブ化が何かわからない場合は以下の記事を確認してほしい。
プレハブ化とインスタンス化について
目次にもどる
カメラ実装
TPSではPlayerが動き回れるだけでなく追従して動くカメラが必要だ。まずはMain CameraをPlayerの子にしよう。そしてMain CameraをPlayerの真後ろの少し高いところに配置し、Playerが映るようにしよう。
ゲームオブジェクトの親子関係がよくわからない場合は以下の記事を確認してほしい。
ゲームオブジェクトの親子関係について
参考までに自分の場合はMain CameraのTransformを以下のように設定した(Main Cameraは子なので表示されているのは親であるPlayerを原点とした位置・回転・大きさである)。
目次にもどる
アニメーション設定
Playerが停止した状態、走っている状態のアニメーションを設定していく(今回はダウンロードしたアセットの中にAnimationClipなど必要なものは入っている)。アニメーションについてよくわからない場合は以下の記事を先に確認してほしい。
アニメーションの基本
先ほど制作したApp→Animatorcontrollerのフォルダを開く。
そこで右クリックして、Create→Animator controllerを選択する。
作られたファイルをPlayercontrollerに名前変更する。
ファイルをダブルクリックするとAnimatorビューが開く。
パラメータタブを選択し、+付近を押し、float(浮動小数7桁まで有効)を選択する。
パラメータの名前は「MoveSpeed」にする。
レイアウトエリア(以下の部分)を選択し右クリックし、「Create State」→「Empty」を選択。
「New State」というステートが追加されるので選択し、インスペクター欄で名前を「Idle」に変更する(Idleには何もしていない。つまり静止しているという意味がある)。
以上と同様の方法で「Run」も作成しよう。
続いて各ステートで使うアニメーションを設定していく。
「Idle」を選択しインスペクター欄の以下のボタンを選択する。
検索欄を使って使うAnimationClipを絞り込む。今回は「Basic Motions@idle 01」を選択する。
同様の方法で「Run」は「Basic Motions@Sprint01 - Forwards」を選択しよう。
「Idle」のステートを右クリックし、「Make Transition」を選択する。
以下のように矢印が出るので、その先をマウスで「Run」のほうに持っていきクリックする。Idle→Runのように矢印が固定されればOKだ。
この矢印は遷移(移り変わり)の設定である。
同じようにRun→Idleの遷移も作成しよう。
Idle→Runの遷移を選択しインスペクター欄の「Has Exit Time」のチェックを外そう。
Run→Idleでも同様に「Has Exit Time」のチェックを外そう。
「Has Exit Time」のチェックがついていると、アニメーション変化の条件を満たした際に現在のアニメーション再生が終了してから次のステートの再生に移る。チェックを外すことでわずかな時間(ほぼ即時)で次のステート再生に移るようになる。
Idle→Runの遷移を選択しConditionの項目の+マークを選択する。
以下のように「MoveSpeed」、「Greater」、「0.01」のように設定を行う。
遷移の条件が移動速度が0.01より大きい時となる。
Run→Idleでも同様に設定する。「MoveSpeed」、「Less」、「0.01」のように設定を行う。
遷移の条件が移動速度が0.01より小さい時となる。
プロジェクトビューのAPP→Prefabにある先ほど作ったPlayerを選択し、インスペクター欄のAnimatorを確認しよう。
Controllerにある◎ボタンを押し、Playercontrollerを選択しよう。
Avatarにある◎ボタンを押し、BasicMotionsDummyModelAvatarを選択しよう。
Apply Root Motionの項目はチェックを外そう。この項目はAnimatorでの動きをゲームオブジェクトの位置にも影響させるかどうかを示している。チェックをつけているとスクリプトでのキャラ移動設定に影響が出てしまう。キャラ移動の設定はスクリプトのみで行いたいのでチェックを外す。
これで、プレハブ化したゲームオブジェクトのPlayerとアニメーションの紐つけができた。
目次にもどる
移動操作設定
Playerが何もキーを押さないと停止し、WASDキーを押すと走るように設定していく。先ほど制作したAPP→Scriptフォルダにスクリプトを作成して開こう。スクリプトやC#についてよく知らない場合は以下の記事を確認しよう。
C#の基礎まとめ
以下のように記述した。
1つずつ説明していく。
Startメソッドでは以下のような記述をしている。
Playerのanimatorのコンポーネントを取得しanimに代入している。
マウスの矢印マークはゲーム中に出現すると邪魔なので非表示しつつ動かないようにしている。
startposにPlayerの最初の位置を代入している。
Updateメソッドでは最初に移動設定を行う。transformやVector3がわからない場合は以下の記事を確認してほしい。
Transformの基礎
Vector3の基礎
まずY軸以外の向きを一旦0としている。
この移動設定の後で向きを変える設定をするので、この移動設定を行う際にPlayerの向きが変わっている可能性がある。
Y軸はキャラの進む方向を決めるために変えてはならない。
一方でX軸やZ軸が0ではなく傾いたまま移動させると移動が不自然となる。例えばX軸が50度に傾いたPlayerの移動を考えてほしい。
以上のように下を向くことになる。この状態で前進させると床を貫通して突き進むことになる(後退させれば空中へ向かって進んでいく)。
よって、Vector3の構造体であるmukiとmukikiokuにPlayerの向き(回転)を代入する。
よってmuki.x = 0f;とmuki.z = 0f;とした上でtransform.eulerAnglesに代入している。
続いて移動設定を行っていく。Inputがわからない場合は先に以下の記事を確認してほしい。
Inputの基礎
Y軸以外は傾いていない状態で以下のように移動の記述をしている。
moveZとmoveXでキー入力の値を受け取った後はmoveDirection = new Vector3(moveX, 0, moveZ).normalized * normalSpeed * Time.deltaTime;としている。
normalizedで正規化しつつnormalSpeedをかけて移動速度の調整を行いmoveDirectionに代入している。
正規化がわからない場合は以下を確認してほしい。
Vector3.normalizedによる正規化
正規化を行ったことで斜め移動でも前後左右の移動と比較して移動速度が変化しなくなる。
this.transform.Translate(moveDirection.x, moveDirection.y, moveDirection.z);としてmoveDirectionの分だけPlayerが相対的に移動する。
更にTime.deltaTimeもかけている。これは前回のフレームから今のフレームがはじまるまでに何秒たっているかを返す。
Updateメソッドでは毎フレーム処理が行われる。一定時間内に呼べるフレーム数はゲーム機の性能によって変化する。よってTime.deltaTimeをかけない場合はゲーム機の性能によって移動処理の回数が変化し移動速度が変化してしまう。Time.deltaTimeをかけてやることでフレーム数が多ければ1回の処理の移動量が減る調整が可能となり、ゲーム機の性能が違っても同じ移動速度を実現できる。
anim.SetFloat("MoveSpeed", moveDirection.magnitude);と記述している。
Startメソッドでanimの中にはPlayerのAnimatorのコンポーネントを代入している。
"MoveSpeed"はFloat型パラメータである(アニメーション設定の際に作成した)。
moveDirection.magnitudeの部分は"MoveSpeed"に送る値となる。
moveDirectionのベクトルの長さの値を送っているということ。magnitudeやベクトルがよくわからない場合は以下を確認してほしい。
ベクトルの長さ・magnitude
WASDや矢印キーを押してない時にはInput.GetAxisにより、moveZやmoveXには0が代入されている。よってmoveDirectionのベクトルも0となる。0の値がMoveSpeedに送られる。先ほどアニメーションの遷移の設定でMoveSpeedにより0.01を境にAnimationClipが切り替わるようにしている。
0.01より小さくなるので「Basic Motions@idle 01」のAnimationClipが再生される(静止時のアニメーション)。WASDや矢印キーを押しているならmoveDirectionの値が0.01より大きくなりMoveSpeedへ代入されるため、「Basic Motions@Sprint01 - Forwards」のAnimationClipが再生される(移動時のアニメーション)。
続いてtransform.eulerAngles = mukikioku;と記述している。
移動の設定ではY軸以外の向きを一旦0にして行った。続いて向きの設定を行うのでmukikiokuに代入しておいた正しい向きの値をtransform.eulerAnglesに入れている。
ここからはPlayerの向きの設定である。
mx = Input.GetAxis("Mouse X")とmy = Input.GetAxis("Mouse Y")により、マウスの移動量をmxとmyに代入している。
mxにxkaitensokudo、myにykaitensokudoをかけることで回転する角度を出して、transform.Rotate(-my, mx, 0);でPlayerの回転の計算を行っている。更に移動の時と同様にTime.deltaTimeをかけてゲーム機性能によって回転速度が変化しないようにしている。
回転やtransform.Rotateがわからない場合は以下を確認してほしい。
transformによる回転
この後行うのはPlayerのX軸の向きの制限である。upseigenに290、downseigenに70をあらかじめ代入してある。その上で以下の記述をしている。
回転計算終了後のPlayerの向きをmuki = transform.eulerAngles;で取得している。
この後はif文により条件分岐を行っていく。if文がよくわからない場合は以下の記事へ。
if文条件分岐
なぜPlayerのX軸の向きの制限を行うかと言えば、ありえない不自然な動作を行わないようにするためだ。人がたったまま位置・姿勢を変えずに頭を下げて見られるのは、頑張っても90度近くまでだ。同様に見上げられる角度にも制限がある。
今回はX軸を回転できる角度を上下70度に設定してみる。
回転の向きはX軸の始点から終点を見た時に反時計周りである。よって下に70度向きたいならmuki.x =70f(downseigenの値)とすればよく、上に70度向きたいならmuki.x =290f(upseigenの値)として、mukiをtransform.eulerAnglesに代入すればいい。
if文については何度か条件分岐を繰り返しているが、総合すると以下のような処理をしている。
muki.xが70f以下→処理なし
muki.x が70fを超え180f以下→muki.x=70fとして向き制限
muki.x が180fを超え290f未満→muki.x=290fとして向き制限
muki.x が290f以上→処理なし。
このように上下70度を超えた時に上下70度の値を代入することで向きを制限している。
向き制限を行った後でtransform.eulerAngles = mukiとすることでPlayerの向きに反映される。
以上のスクリプトをPlayerにアタッチして再生してみよう。以下のようにWASDキーまたは矢印キーで前後左右に移動。マウスで向きを変えられる。移動だけはTPSらしくなった。
目次にもどる
追記(前後左右アニメーション追加)
前後左右移動はできるようになったのだが、どの方向に移動しても同じアニメーションなのにはやや違和感がある。そこで、前進、右方向へ移動、左方向へ移動、後退など移動方向によって別のアニメーションが表示されるようにしてみた。
PlayercontrollerのRunのステートは消し、以下のようにForward、Back、Right、Leftのステートを作成。
各ステートで再生するAnimationClipを指定。
Forward…2Hand-Sword-Run-Forward
Back…2Hand-Sword-Run-Backward
Right…2Hand-Sword-Run-Right
Left…2Hand-Sword-Run-Left
パラメータはBool型でForward、Back、Right、Leftを作成する。
Forward、Back、Right、Leftの各パラメータがtrueになった時、パラメータ名に対応するステートに遷移するように設定する(Idleからの遷移はもちろん、Forward、Back、Right、Left間でも遷移を設定)。
MoveSpeedが0.01より小さい時、Forward、Back、Right、Leftの各ステートからIdleへ遷移するように設定。
スクリプトの移動の記述に以下を追加。
最初にfloat zmoveZ = Mathf.Abs(moveDirection.z);と記述をしてある(moveDirection.zは前後の移動で使用した変数)。Mathf.Abs(moveDirection.z)とすることでmoveDirection.zの絶対値(原点からの距離)を求めて、それをzmoveZに代入した。
同様にfloat zmoveX = Mathf.Abs(moveDirection.x);としてmoveDirection.x(左右の移動で使用した変数)の絶対値をzmoveXに代入した。
続いてif (zmoveZ > 0f || zmoveX > 0f)と条件分岐している(zmoveZが0より大きい またはzmoveXが0より大きい)。この条件に当てはまらない場合、最後のelseの処理が行われ、Forward、Back、Right、Leftのパラメータをfalseにする(条件に当てはまらないなら移動していない)。
if (zmoveZ > 0f || zmoveX > 0f)の条件を満たした場合、次はif (zmoveZ > zmoveX)の条件分岐となる。前後移動と左右移動の大きさを比べるために記述している。zmoveXよりzmoveZのほうが大きかった場合、左右移動より前後移動のほうが大きいことがわかる。つまり右移動または左移動のアニメーションよりも、前進・後退のアニメーションを優先して出すべきであることがわかる。
zmoveZのほうが大きかった場合のif文の中にはif (moveDirection.z >= 0f)という条件分岐があり、前と後ろのどちらに移動しているか判断している。
この条件に当てはまった場合、前進のアニメーションを出すべきなので、Forwardのパラメータのみtrue BackとRightとLeftのパラメータはfalseにする。
この条件に当てはまらなかった場合、後退のアニメーションを出すべきなので、Backのパラメータのみtrue ForwardとRightとLeftのパラメータはfalseにする。
moveDirection.z = 0fだった場合は考えなくていい。最初にif (zmoveZ > 0f || zmoveX > 0f)の条件分岐を行っているのでmoveDirection.z = 0fであるはずがない。
同様にif (zmoveZ > zmoveX)の条件に当てはまらなかった場合は左右のどちらに移動しているかをif (moveDirection.x >= 0)の条件分岐で判断してパラメータを操作している。
以上のように記述することで前進、後退、右移動、左移動のアニメーションが、移動方向に応じて出るようになる。今回の追記の内容を実行した場合、この先の記事でRunステートからAttackやJumpに遷移させる内容が出てくるが、その際はForward、Back、Right、LeftステートからAttackやJumpに遷移させると読みかえてほしい。
以下の記事で今回のスクリプトに記述を加え、ジャンプや落下などを可能にしてみた。
ジャンプ実装