【Unity】RPGを作るチュートリアルその6 キャラクターの移動制御

【Unity】RPGを作るチュートリアルその6 キャラクターの移動制御

シンプルなRPGをUnityで作るチュートリアルシリーズの6回目です。

第5回では操作キャラクターと同様にNPCが歩行する際のアニメーションを作成しました。操作キャラクター用のAnimatorControllerの遷移条件を使いつつアニメーションクリップを差し替えることのできるAnimatorOverrideControllerを使って、NPCのアニメーションを多少省力化しつつ実装しました。今回はキャラクターがTilemapのマス目に沿って移動するように機能を実装していきましょう。

 

 

制作環境

MacBook Pro 2023 Apple M2 Max

Unity6 (6000.0.30f1) Silicon

 

作業内容と順序

シンプルなRPGを作る上でどんな作業が必要か、どんな順番で作っていくと良さそうか、別ページで検討しました。基本的にこの流れに沿って進めていきます。

 

前回の内容

前回はNPCキャラクターのアニメーションを作成しました。

 

キャラクターの移動の実装方針

見下ろし型の2DのRPGを想定しているため、タイルのマス目に沿って移動させるようにします。方向キーの入力があったら、その方向に移動させますが、移動先のマスが通行可能か確認するようにします。また、方向キーを入力したままなら、移動が完了後に次の移動先の判定に進むようにします。

移動先が通行可能かどうかの判定は、Collider用のTilemapとゲームオブジェクトのColliderで確認します。どちらも衝突用途ではなくTriggerを使った判定用途で実装予定です。

操作キャラクターの移動に使う通行可能かの判定は、NPCの移動時にも使えそうなので今回かNPCの実装時にクラスに切り出しておきます。

また、操作キャラクターの位置に関しては、Unityのゲーム内空間の座標に加えて、論理的な座標を保持するようにします。これはTilemap上のタイルの位置と関連付けやすくして、移動先のタイルの論理的な座標からUnityのゲーム内空間の座標を取得しやすくするためです。

 

スクリプト用フォルダの追加

今後スクリプトファイルを作成していくにあたって、スクリプトファイルを配置するフォルダを作成します。Assetsの下にフォルダを作成し、[Scripts] にリネームします。

フォルダの作成
フォルダの作成

 

作成した「Scripts」フォルダを開き、C#スクリプトを作成しましょう。Projectウィンドウでコンテキストメニューを開き、[Create] -> [Scripting] -> [MonoBehaviour Script] を選択します。Unity6になってScriptableObject用のテンプレートも選べて便利ですね(Editor拡張勢)

MonoBehaviourのスクリプトファイルを作成
MonoBehaviourのスクリプトファイルを作成

 

名前は [PlayerMover] にしました。この中身については、後ほど作成していきましょう。

ファイルの作成
ファイルの作成

 

命名規則や名前空間

コーディング規約、命名規則に関しては基本的にMicrosoft社の「一般的な C# のコード規則」に(なるべく)従います。

名前空間としては<組織名>.<製品名>といった感じで会社名や組織名を含めるのが良いのですが、今回は製品名だけにしましょうか。名前もそのまま「SimpleRpg」にしちゃいましょう。

アセンブリは分けずにいきます。

今回はなるべく1ファイル1クラスにするようにします。エディタのツリー表示の段階で中身のクラスが分かるようにしたいというのが目的です。

チュートリアルでの解説を目的として、ドキュメントコメントなども適宜残していきます。

 

移動機能の追加

まずは通行可能かどうかの判定を入れずに移動処理を入れていきます。これまでに作成したTilemapのゲームオブジェクトについて、「Tilemap」のコンポーネントを確認すると、タイルのアンカーの値がXとYでそれぞれ0.5になっているのが確認できます。これはタイルの中央がアンカーになっているので、指定のタイルの論理的な座標を取得した際に、このアンカーの値を加算することでゲーム内空間の座標に変換することができます。

Tile Anchorはタイルの中央
Tile Anchorはタイルの中央

 

定義値用クラスの作成

アニメーションを切り替えるため、スクリプト内ではAnimatorに対してパラメータ名と値を伝える必要があります。パラメータ名に関しては複数のクラスで使うことを考えて、stringとして事前に定義しておきましょう。定義値を保持するクラスは今回はstaticなクラスにして、定義値のフィールドはpublic static readonlyをつける形で定義しましょう。

カテゴリ単位でクラス分けすることにしましょうかね。例えばアニメーション関連の設定をまとめる形で「AnimationSettings」のクラスを作って、この中にパラメータ名などを定義していきます。

また、パラメータの取りうる値に関してはEnumで定義しておきます。移動する方向を4つの候補から選ぶので、今回は値の範囲が決まっているEnumで実装します。

ここで定義値クラスのスクリプトファイルを保存する場所を作っておきましょう。先ほど作成した「Assets/Scripts」のフォルダに移動し、2つのフォルダを作成します。ひとつは定義値用クラスのスクリプトを格納する [Consts] 、もうひとつはEnumのスクリプトファイルを格納する [Enums] にします。

フォルダの作成
フォルダの作成

 

フォルダを作成したら、まずは定義値用クラスを作成していきます。先ほど作成した「PlayerMover」のクラスを実装するにあたって、定義値を使いたいので先に実装してしまいましょう。

作成した「Consts」のフォルダに移動してコンテキストメニューを開き、[Create] -> [Scripting] -> [Empty C# Script] から空のC#スクリプトを作成します。こちらを選ぶとStart()やUpdate()のメソッドがないテンプレートが使われるので、定義値用クラスの作成に便利です。

空のC#スクリプトを作成
空のC#スクリプトを作成

 

作成したファイルの名前は [AnimationSettings] にしました。記載する内容は多くないので、このまま中身も書いてしまいましょう。

定義値用クラスのファイルを作成
定義値用クラスのファイルを作成

 

中身は以下の通りです。

名前空間は上で触れた通り「SimpleRpg」にしています。クラスをstaticにして、フィールドもpublic static readonlyにしています。

今回追加したい定義値はAnimatorウィンドウで設定したパラメータ名で、その値をフィールドとして追加しています。

 

上記の内容を保存したら、Enumの方もファイルを作成しちゃいます。「Assets/Scripts/Enums」に移動して、同様にコンテキストメニューから空のC#スクリプトを作成します。名前は [MoveAnimationDirection] にしました。こちらもそのまま内容を書いちゃいしまょう。

アニメーションの方向を定義するクラス
アニメーションの方向を定義するクラス

 

私はクラス名を長くしてしまう傾向がありますが、今回のチュートリアルではこのまま進めさせてくださいな。狙いとしては、クラス名だけでどんな役割があるのか分かる、メソッド名だけで何をするのか分かる、といったものを目指しています。

キャラクターの移動アニメーションは前方、右、後方、左の4方向があり、Animatorでの遷移条件と対応させています。Enumで項目を定義する際には自動で0から値が割り振られますが、今回は値までしっかり定義しておきます。

 

移動処理用クラスの実装

定義値用クラスの「AnimationSettings」、Enumの「MoveAnimationDirection」が作成できたら、移動処理用クラスの「PlayerMover」の中身を実装していきましょう。

 

スクリプトの全文

先に中身の全文を提示して、それぞれ解説をしていきます。(コメントめっちゃ入れたので長いです)

 

個別の解説

それぞれ解説していきます。

まずはフィールドの宣言部分です。Tilemapの参照の「_baseTilemap」と移動時間の「_moveTime」については、Inspectorウィンドウから設定できるように[SerializeField]をつけています。

「_posOnTile」はTilemap上の位置を表していて、基準となる座標から右に○マス目、上に○マス目、なんて感じで保持します。この座標を保持しておくことで、Unityのワールド座標への変換を行なって実際の位置を計算します。

移動に関してはコルーチンで実装する予定で、移動中フラグがtrueになっている場合、キー入力があってもコルーチンを起動しないようにします。

 

続いてStart()から呼び出すメソッドです。CheckComponents()のメソッドではコンポーネントへの参照を確認し、参照がnullならGetComponentで参照を取得します。このクラスだとAnimatorへの参照を取得して、移動時の向き変更に使用します。GetCurrentPositionOnTilemap()のメソッドでは、操作キャラクターのワールド座標を取得して、Tilemap上の座標に変換しています。

 

Update()からはキー入力を確認するメソッドのCheckMoveInput()を呼びます。移動中フラグがtrueの場合はこの段階で抜けてしまいましょう。このメソッド内では、入力されたキーに応じて、現在位置から見た移動先、アニメーションの方向を取得して、次のMovePlayer()のメソッドに渡しています。

キーを押しっぱなしにした時に連続移動するように、Input.GetKeyを使っています。また、斜め移動にならないよう、「else if」を使ってどれか1方向のみの値を取得するようにしています。アニメーションの方向に関しては、0が正面で、1が右で……と覚えておくのが大変なので、Enumの要素名でコーディングできるようにしています。

 

MovePlayer()のメソッドでは入力されたキーの情報をもとに処理を進めます。キー入力がない場合は、方向のベクトルが(0, 0)になるので処理を抜けています。それ以降は移動処理に入るので移動中フラグをtrueにしています。

渡された移動先の情報からTilemapのグリッド上の位置を計算し、それをもとにワールド座標を計算しています。CellToWorld()で取得した位置はタイルのアンカーが考慮されておらず、マス目の左下のワールド座標が返されるため、アンカーの値を加算しています。

アニメーションの向きを変更するためにSetIntegerで値をセットしています。int型でセットする必要があるため、Enumからキャストして渡しています。

操作キャラクターのゲームオブジェクトの現在位置も取得したら、それをコルーチンに渡して実際の移動処理に入ります。

 

MovePlayerProcess()のコルーチン内では、フィールドで定義した「_moveTime」を使って移動の完了時刻を計算し、while文の条件として計算しています。現在の時刻が完了時刻を過ぎていたらwhile文内の処理を抜けて最終的な位置をセットしてフラグをfalseにしています。

位置の計算にはVector3.Lerp()メソッドを使って、移動元の位置から移動先の位置までの間の割合に応じた位置を取得してセットしています。割合を計算するための処理の経過時間は、現在時刻から開始時刻を引くことで算出して、それを「_moveTime」で割ることで割合にしています。

 

スクリプトのアタッチ

スクリプトの編集が終わったら、「Player」のゲームオブジェクトにアタッチしましょう。Hierarchyウィンドウで「Player」のゲームオブジェクトを選択し、Inspectorウィンドウで [Add Component] ボタンから [PlayerMover] のスクリプトをアタッチします。

スクリプトをアタッチ
スクリプトをアタッチ

 

「PlayerMover」のスクリプトでは、「Base Tilemap」の項目はシーン内の「TilemapBase」をアサインします。右の丸ボタンからシーン内の「TilemapBase」を選ぶか、Hierarchyウィンドウからドラッグ&ドロップしましょう。

 

動作確認

スクリプトをアタッチしたら動作確認を行います。「Player」のゲームオブジェクトの位置を(7.5, 7.5, 0)などのようにアンカーを考慮した位置に変更しておくと確認が楽です。カメラの位置も見やすいようにずらしておきましょう。

ゲームを実行して、キー入力に応じて操作キャラクターが移動することを確認しましょう。現時点ではカメラの追従やColliderによる移動禁止処理などは入れていないので、画面外も行けるようになっています。

 

ちょっと直したいところ

移動の動作に関しては問題なさそうですが、それとは別にちょっと気になったこととして、主人公オブジェクトがNPCの後ろに行ってしまってました。なので、表示順を変更しておきましょう。

Hierarchyウィンドウで「Player」のゲームオブジェクトを選択し、Inspectorウィンドウの「SpriteRenderer」にて、「Order in Layer」の値を [1] に変更しましょう。操作キャラクターが他のキャラクターの後ろに表示されていると、操作時に混乱を招く可能性がありますからね。

表示順を修正
表示順を修正

 

修正を行なったら、動作確認して完了です。

この後は、

  • カメラを操作キャラクターに追従させる機能
  • Colliderを使った移動を禁止するタイルの設定

を実装していきます。カメラに関してはシンプルに操作キャラクターを映すために追従させることとして、常に操作キャラクターが画面の中央に表示されるようにします。狭いマップなどではカメラを固定させたり、あるいは領域外に差し掛かりそうなら追従を止めたりなどの処理があると良いのですが、チュートリアル内で入れると終わらない気がするので、チュートリアル完了後の拡張パック的な位置付けで対応したいと思います。

 

今回のブランチ

 

まとめ

今回はキャラクターの移動の制御を行いました。マス目に沿って移動させたいことから、Tilemapのグリッド上の位置を使ってワールド座標に変換しています。この変換は覚えておくと使い道も多いので便利です。RPGではイベントを発生させる時に操作キャラクターの位置も大切になってくるので、グリッド上の位置は保持しておくとこちらも便利です。

また、移動時にはキーを押しっぱなしにしてそのまま進んでいくように、GetKeyDownやGetKeyUpを使わずにGetKeyを使用しています。逆に人に話しかけたり、何かを調べる時には押しっぱなしにする必要はないのでGetKeyUpで対応していきましょう。

次回はカメラが操作キャラクターを追従する処理を実装します。

 

     

ゲーム開発の攻略チャートを作りました!

CTA-IMAGE

「ゲームを作ってみたいけど、何から手を付けていいか分からない!」


そんなお悩みをお持ちの方向けに、todoがアプリをリリースした経験を中心に、ゲーム作りの手順や考慮すべき点をまとめたe-bookを作成しました。ゲーム作りはそれ自体がゲームのように楽しいプロセスなので、「攻略チャート」と名付けています。


ゲームを作り始めた時にぶつかる壁である「何をしたら良いのか分からない」という悩みを吹き飛ばしましょう!