i-school - 2Dリアルタイムストラテジー 手順7
 この手順では、ゲームのベース機能部分である画面のタップ処理の制御について実装を行います。

<実装動画 画面をタップした際に配置可能な位置である場合には、そのタップした位置にキャラを移動する>
動画ファイルへのリンク


 以下の内容で順番に実装を進めていきます。

手順7 −タイルのタップ感知制御の実装−
11.移動可能なタイルをタップした際にキャラを移動する処理を実装する



 新しい学習内容は、以下の通りです。

 ・Vector3Int 構造体
 ・Camera.ScreenToWorldPoint メソッド
 ・GridLayout.WorldToCell メソッド
 ・Tilemap.GetColliderType メソッド
 ・NavMeshAgent に用意されている変数とメソッド



11.移動可能なタイルをタップした際にキャラを移動する処理を実装する

1.設計


 Unity には画面のタップを感知するための専用メソッドがいくつか用意されています。
そのうちの1つである Camera クラスの管理する ScreenToWorldPoint メソッドは画面のスクリーン座標をワールド座標に変換することによって、
画面のタップした位置情報をゲーム内の位置情報へと変換してくれますので、こちらを利用します。

 今回利用するタイルマップはワールド座標ではなく、タイルマップ内のセル座標という座標体系によって管理されています。
そのため従来の画面のタップを感知するメソッドのみでは、タイルマップ内のタイルを特定することが出来ません

 そこで画面のタップを感知してワールド座標を取得したあとに、ワールド座標をタイルマップのセル座標に変換するメソッドを組み合わせて利用することによって、
画面のタップした位置をタイルマップ内の1つのタイルの地点と紐づけをして座標を指定できるように処理を考えます。

 セルの座標がわかれば、その地点に対して NavMeshAgent の目標地点として設定することで、
タイルマップ上にいるキャラを NavMeshAgent の機能を使い、移動不可のタイルを回避しながら、目的地まで移動させることが可能になります。
 
 Unity に用意されている複数のメソッドとそのメソッドの戻り値を上手に活用することによって、今回のタップ処理が実装できることになります。
今後もこのように複合式なロジックや考え方が必要になりますので、しっかりと学習して活かしていきましょう。


2.MoveToClickTileMapPoint スクリプトを作成する


 画面のタップ(マウスクリック)を感知し、その地点にキャラを移動させるためのスクリプトを作成します。
スクリプトを作成する前にはスクリプト専用のフォルダを用意し、その中で作成するようにしてファイル管理をしやすくします。

 Project フォルダの空いている場所で右クリック、あるいは + ボタンを押してメニューを開き、Create => Folder を選択します。
名前を Scripts に変更します。このフォルダ内で同じように再度メニューを開き、Create => C# Script を選択します。

 新しくスクリプト・ファイルが作成されますので、名前を MoveToClickTileMapPoint に変更します。
ダブルクリックすることで Visual Studio エディターが起動しますので、そちらにスクリプトを記述していきます。
 

MoveToClickTileMapPoint.cs

<= クリックすると開きます。


 スクリプトを作成したらセーブを行います。セーブは Ctrl + Shift + S キーです。


3.<Vector3Int 構造体>


 Vector3 構造体を int 型で表現するためのものです。通常の Vector3 型との違いは各項目が float 型ではなく、すべて int 型で管理されている部分です。

 今回のタイルマップでは座標情報がワールド座標ではなく、セル座標という体系で管理されています。
これは (3, 4) や(110, 44) のように、すべて整数の Vector3Int 構造体で座標が表現されます。
そのため、セル座標の情報をスクリプト内で扱う場合にも Vector3Int 構造体の情報を利用することで、セル座標を取得したり、利用したりすることが出来ます。

 通常のワールド座標は Vector3 構造体(float 型)、セル座標は Vector3Int 構造体(int 型)という風に、座標の管理している型を覚えてしまうといいでしょう。

Unity 公式スクリプトリファレンス
Vector3Int
https://docs.unity3d.com/ja/current/ScriptReferenc...


4.<Camera.ScreenToWorldPoint メソッド>


 引数で指定したスクリーン座標の情報をワールド座標に変換するメソッドです。

  // タップ(マウスクリック)の位置を取得してワールド座標に変換
  Camera.main.ScreenToWorldPoint(Input.mousePosition);

 タップした位置、あるいはマウスのポインタがあるスクリーン座標の情報(Input.mousePosition 変数)を引数に指定し、
その値を戻り値として Vector3 構造体のワールド座標に変換しています。

 今回はこの値を WorldToCell メソッドを利用して、さらにタイルマップのセル座標に変換しています。

 このメソッドは2Dでは問題なく動作しますが、3Dの場合には Z 軸の情報を考慮しないと正確な計算を行ってくれません。
スクリーン座標は2次元(Vector2 構造体)であるため、Vector3 構造体の値に変換した際、Z 軸には常に 0 になるためです。
こういったケースの場合には、 Z 軸に適切な値を代入して利用します。
またカメラの Projection の設定が2Dの場合には自動的に Orthographic になるのに対し、3Dの場合には Perspective になりますので、その辺りにも注意が必要になります。

Unity 公式スクリプトリファレンス
Camera.ScreenToWorldPoint
https://docs.unity3d.com/ja/current/ScriptReferenc...


5.<GridLayout.WorldToCell メソッド>


 引数に指定したワールド座標(Vector3 型)を、タイルマップ内の座標単位であるセル座標(Vector3Int 型) に変換するメソッドです。

  // タップ(マウスクリック)の位置を取得してワールド座標に変換し、それをさらにタイルのセル座標に変換
  gridPos = grid.WorldToCell(Camera.main.ScreenToWorldPoint(Input.mousePosition));

 今回の実装では、ScreenToWorldPoint メソッドの戻り値として Vector3 構造体の情報を取得して、それをワールド座標の値として引数に指定しています。
WorldToCell メソッドhではその引数の位置の結果をタイルマップ内にあるセル座標として Vector3Int 構造体の情報として変換して取得しています。

 このように座標の情報を変換していくことによって、画面のタップの位置情報をタイルマップ内のタイルの位置情報と紐づけて利用する処理になっています。
Unity 公式スクリプトリファレンス
Grid
https://docs.unity3d.com/ja/current/ScriptReferenc...
Unity 公式スクリプトリファレンス
GridLayout.WorldToCell
https://docs.unity3d.com/ja/current/ScriptReferenc...


6.Chara ゲームオブジェクトに MoveToClickTileMapPoint スクリプトをアタッチして設定をする


 ヒエラルキーにある Chara ゲームオブジェクトのプレファブを選択します。

 最初に、インスペクターの一番下にある Add Component ボタンを押して、NavMeshAgent コンポーネントを追加してください。
コンポーネントのスイッチはオフにしておきます。そのほかの設定は初期値で問題ありません。
 
 次に MoveToClickTileMapPoint をドラッグアンドドロップしてアタッチしてください。
アタッチしたら必ず、ゲームオブジェクトを選択してインスペクターを確認して、正常にアタッチされているかを目視でチェックします。

 MoveToClickTileMapPoint スクリプトにはインスペクターよりアサインする情報が2つ表示されていますので、こちらを設定します。

 grid 変数には、ヒエラルキーにある Grid_DontMove_Base ゲームオブジェクトをドラッグアンドドロップしてアサインします。

 tilemap 変数には Grid_DontMove_Base ゲームオブジェクトの子オブジェクトである Tilemap ゲームオブジェクトををドラッグアンドドロップしてアサインします。


インスペクター画像



 以上で設定は完了です。ヒエラルキーにあるプレファブのゲームオブジェクトを更新した場合、
Prefabs フォルダにある Chara ゲームオブジェクトの方には、それらの情報は更新されません。

 そのため、インスペクターの上部にある Overrides ボタンを押して、プレファブの情報を更新しておきます。


7.ゲームを実行して動作を確認する


 セーブを行い、ゲームを実行して処理を確認しましょう。
どのような処理を記述し、どのような制御処理が動くのか、イメージを作ってから実行してください。

 まず最初に、Chara ゲームオブジェクトのインスペクターの確認を行います。
MoveToClickTileMapPoint スクリプトの Start メソッドの処理により、オフになっている NavMeshAgent のスイッチがオンに切り替わるはずですので、そちらをチェックします。

 問題なく NavMeshAgent が有効になっていれば制御成功です。
ゲーム開始後に NavMeshAgent をオンにする理由については、別の機会に詳しく説明します。

 続いて、画面をマウスでクリックしてください。その地点に向かって、Chara ゲームオブジェクトが移動を開始し、
クリックした地点で移動を停止すれば、NavMeshAgent による自動経路移動の制御は成功です。


<実行動画>
動画ファイルへのリンク


 最後に NavMeshAgent の機能について学習します。


8.<NavMeshAgent に用意されている変数とメソッド>


 NavMeshAgent クラスには多くの変数とメソッドが用意されています。
この機能を上手く活用することにより、AI による NavMeshAgent のルートの自動移動を実装することができます。

 NavMeshAgent 型をスクリプト内で宣言し、各変数やメソッドを利用するためには using UnityEngine.AI; の宣言が必要です。

 今回は実装に利用した機能のみを抜粋して掲載しています。
すべての機能は紹介できませんので、自分で調べてみましょう。


1.NavMeshAgent.destination 変数(SetDestination メソッド)

 NavMeshAgent 目標地点を設定することができる Vector3 型の変数です。
この情報をセットすることで、NavMeshAgent に目的地へ移動することを伝え、移動を開始させることができます。

 目標地点の設定方法にはもう1つ、SetDestination メソッドも用意されており、そちらでも同じように目的地の設定が可能です。

 SetDestination メソッドには bool 型の戻り値があり、正常に目的地の設定が要求されているかどうかを評価することが出来ます。
目的地が正常に要求されていれば true、要求に失敗した場合には false が戻ります。

 用途に応じて使いわけるようにしましょう。


 // 目的地の更新
  //agent.SetDestination(nextPos);  // どちらの処理で設定をしても問題なし
  
 agent.destination = nextPos;

<参考サイト>
Unity 公式スクリプトリファレンス
NavMeshAgent.destination
https://docs.unity3d.com/jp/current/ScriptReferenc...
Unity 公式スクリプトリファレンス
NavMeshAgent.SetDestination
https://docs.unity3d.com/jp/current/ScriptReferenc...
Unity 公式マニュアル
NavMeshAgent に目的地へ移動することを伝える
https://docs.unity3d.com/ja/current/Manual/nav-Mov...


2.NavMeshAgent.speed 変数

 NavMeshAgent によるルート移動時の最大速度の設定値です。0 にすることで移動は停止します。
NavMeshAgent コンポーネントをアタッチした際の初期値は 3.5 になっていますので、ゲームの内容に応じて設定を行います。
今回の実装例のように、スクリプトから制御も可能です。

 // 移動速度の設定
  agent.speed = 3.0f;

<参考サイト>
Unity 公式スクリプトリファレンス
NavMeshAgent.speed
https://docs.unity3d.com/jp/current/ScriptReferenc...


3.NavMeshAgent.updateRotation 変数

 NavMeshAgent によるゲームオブジェクトへの回転情報を更新するかどうか bool 型にて設定します。
通常は true になっているため、ゲームオブジェクトは NavMeshAgent の移動時に回転を許可されており、ゲームオブジェクトの向きが移動に伴い更新されます。
false にすると回転は更新されなくなります。

 今回は 2D ゲームであるため、回転の更新は不要であり、true になっていると誤作動するため、false にして機能をオフにしています。


  // 3D 用の設定をオフにする。2D なので、この処理がないと、変な位置に勝手に移動する
  agent.updateRotation = false;
  agent.updateUpAxis = false;

<参考サイト>
Unity 公式スクリプトリファレンス
NavMeshAgent.updateRotation
https://docs.unity3d.com/jp/current/ScriptReferenc...


4.NavMeshAgent.updateUpAxis 変数

 エージェント(NavMeshAgent で移動するゲームオブジェクト)が NavMeshAgent による向きの影響を受けるかどうかについて、bool 型にて設定します。
通常は true になっているため、ゲームオブジェクトは NavMeshAgent による向き(ローカル軸の上方向)の影響を受けます。
そのため、3D 空間であれば、Bake されたエリアに対して、NavMeshAgent がエージェントを垂直になるように立たせようとします。
false にするとこの向きの影響は受けなくなります。

 こちらの設定も、今回は 2D ゲームであるため、不要な概念になります。
true になっていると、3D 空間ではないためゲームオブジェクトを垂直に立てることが出来ずに誤作動するため、false にして機能をオフにしています。


  // 3D 用の設定をオフにする。2D なので、この処理がないと、変な位置に勝手に移動する
  agent.updateRotation = false;
  agent.updateUpAxis = false;


<参考サイト>
Unity 公式スクリプトリファレンス
NavMeshAgent .updateUpAxis
https://docs.unity3d.com/jp/current/ScriptReferenc...



 以上でこの手順は終了です。

 次は 手順8 −カメラの追従制御の実装− です。