i-school - 2Dリアルタイムストラテジー 手順11
 キャラの移動の際、前回の手順で作成したアニメーションの機能を利用し、移動している方向とキャラのアニメの移動方向を同期させる機能を追加します。


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

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



手順11 ー味方キャラの移動移動と移動アニメの同期処理の実装−
16.CharaController スクリプトを作成し、味方キャラの移動アニメ制御処理を実装する
17.MoveToClickTileMapPoint スクリプトを修正し、味方キャラの移動の方向と移動アニメを同期させる



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

 ・TryGetComponent()メソッドとoutキーワード宣言
 ・Vector2.Distance メソッド
 ・Animator.SetFloat メソッド
 ・Vector2.normalized/Vector3.normalized 変数を利用した正規化処理
 ・ローカル関数
 ・コルーチンを変数に代入して実行する



16.CharaController スクリプトを作成し、味方キャラの移動アニメ制御処理を実装する

1.設計


 プレファブになっている Chara ゲームオブジェクトの制御を行うためのスクリプトの設計を行います。

 まずは最初に、アニメーションの制御を行えるようにしたいので、どういった情報があればいいのかを検討していきます。

 必要なコンポーネント、メソッド内の処理など、アニメーションを制御するためにはどういった内容が必要になってくるのか、
今まで学習した内容を元に考えてみてください。

 例えば、アニメーションの制御は Animator コンポーネントによって制御されています。
よって、この情報を制御したいのであれば、Animator コンポーネントの型で宣言した変数が必要になります。


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


 どういった情報が必要であるのかを自分でしっかりと考えて、まずは処理の実装に挑戦してみましょう。
また、この教材の内容を記述する場合には、意識しながら処理を記述してください。

 コルーチンメソッドと while 文を組み合せたロジックで処理が構成されていますので、
どのように処理が動いているのかを考えてみてください。


CharaController.cs

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



3.<TryGetComponent()メソッドとoutキーワード宣言>


 Unity2019.2以降に追加されたメソッドです。処理結果として bool 型で戻り値を返してくれます
このときの処理結果というのは、指定したコンポーネントの型の取得を行い、それが取得できれば true、取得できなければ false が戻ります

 またoutキーワードによる宣言がありますので、trueの場合には必ず、このoutの後に宣言した変数内に型が代入されます。
 outキーワード宣言を行うと、outを付けた引数で指定した変数はメソッド内で必ず結果が入ることが保証されるものです

 if (!TryGetComponent(out tilemapMove)) {
    Debug.Log("MoveToClickTileMapPoint を取得出来ませんでした。");
  }

 今回はこのような処理として利用しています。
スクリプトがアタッチされているゲームオブジェクト(つまり Chara です)へとアクセスし、その gameObject に対して TryGetComponent メソッドを実行しています

 out キーワードの後には tilemapMove という変数を用意しておきます。

 もしもこの TryGetComponent の結果が true であるならば、out として用意した tilemapMove 変数に MoveToClickTileMapPoint コンポーネントの情報が代入されます。

 TryGetComponent メソッドの結果が false の場合には MoveToClickTileMapPoint コンポーネントの取得ができなかったため false が結果として戻り、変数内には値が代入されないまま終了します
また、false の場合には if 文の条件が成立するため、Console ビューに取得に失敗しているメッセージを表示しています。

 なおTryGetComponentメソッドには複数の書式があります。こちらは下記のリファレンスを参照してください。


参考サイト
Unity公式スクリプトリファレンス
Component.TryGetComponent
https://docs.unity3d.com/ScriptReference/Component...


4.<Vector2.Distance()メソッド>


 第1引数と第2引数に指定したVector2型の2点間の距離を、float型に変換して距離として戻してくれるメソッドです。
戻り値に合わせて辺にはfloat型の変数を用意して代入させます。

 if (Vector2.Distance(transform.position, corners[currentCornerIndex]) <= 0.3f) {
 
Unity公式スクリプトリファレンス
Vector2.Distance
https://docs.unity3d.com/ja/current/ScriptReferenc...


5.<Vector2.normalized/Vector3.normalized 変数を利用した正規化処理>

 
 正規化処理です。magnitude(マグニチュード。長さ)を1としたベクトル(単位ベクトル)を返します。戻り値は Vector2/Vector3 型になります。

 Vector2 direction = (corners[currentCornerIndex] - transform.position).normalized;

 現在のベクトルの方向を維持したまま、magnitudeが1、あるいは0の単位ベクトルを作成することが出来ます。(ベクトルの値が小さいと0になります。)

 この正規化を行うことによって位置の遠近に関わらず、magnitude がすべて 1、あるいは 0 に統一された単位ベクトルの値となるため、
左と下の方向であれば -1右と上の方向であれば 1の値がベクトルとして作成されます。値が 0 に近い場合には 0 がベクトルとして作成されます。

 キー入力が斜め方向になると、縦横直線よりも長い距離が生まれます。その値をそのまま利用してしまうと、斜め移動の方が移動速度が速くなってしまいます。
そこで正規化処理を行い単位ベクトルを作ることで、方向の情報はそのままで、値だけを 0 か 1 か -1 に作り直しています。
この値を移動の入力値として利用することにより、移動方向の情報はそのままで、移動する速度を補正することが出来ています。

Unity公式スクリプト・リファレンス
https://docs.unity3d.com/ja/current/ScriptReferenc...
TechProjin様
UnityのVector3でよく使うものまとめ
https://tech.pjin.jp/blog/2016/02/16/unity_vector3...


6.<Animator.SetFloat メソッド>


 Unityのアニメーションは、Animatorクラスによって様々なアニメーションの制御が行えます。
Unity公式スクリプトリファレンス
Animator
https://docs.unity3d.com/ja/current/ScriptReferenc...

 今回はアニメーションの遷移のために、SetFloat メソッドを利用し、遷移の条件をこのメソッドの引数に指定してアニメーションの遷移を行っています。

 各メソッドの引数にはそれぞれ型の指定が異なりますが、いずれも第1引数は string 型です。この部分には、パラメータで設定した文字列を指定します。
文字列ですので大文字小文字は区別されます。パラメータに登録した文字列をこの第1引数に指定することでパラメータのもつ情報を変更することが出来ます。
そして、パラメータの値を変更する内容を第2引数に指定します。

 例えば、SetFloatであれば、第1引数に float 型のパラメータである "Look X" の文字列を指定し、第2引数に float 型の値を指定します。

 // BlendTree にアニメーションの命令を行う
 anim.SetFloat("X", direction.x);
  anim.SetFloat("Y", direction.y);

 こうすることで、このパラメータの値をスクリプトから書き換えることができます。その結果として、条件が合致したアニメーションに遷移することが出来ます。


パラメータとSet〜メソッドの関連性


参考サイト
Unity公式スクリプトリファレンス
SetFloat
https://docs.unity3d.com/ja/current/ScriptReferenc...


17.MoveToClickTileMapPoint スクリプトを修正し、味方キャラの移動の方向と移動アニメを同期させる

1.設計


 先ほどの手順で Chara ゲームオブジェクトのプレファブの制御を行うためのスクリプトを作成しました。
その中には SetAnime メソッドが用意されており、このメソッドを実行することで、引数に指定した位置を順番に移動する際に
キャラの移動方向に合わせてアニメーションの制御を行う機能が実装されました。

 この手順では、キャラの移動先を設定する MoveToClickTileMapPoint スクリプトを修正し、
タップした際の処理を追加して、タップに合わせて CharaControlller スクリプトの SetAnime メソッドに移動地点の情報を引数で渡すことで
移動経路の情報をその都度、CharaControlller スクリプト側に提供する機能を追加します。

 メソッドの引数を活用することにより、同じ SetAnime メソッドを実行しても、渡される情報(移動地点)が異なるため、
アニメの同期もその情報を元に制御を行うようにしているので、自動的に移動方向とアニメするキャラの向きが常に同期するように設計しています。


2.MoveToClickTileMapPoint スクリプトを修正する


 新しく作成した CharaController スクリプトを利用できる状態にするため、変数の宣言を追加しています。
また、Start メソッド内と SetPathAndMove メソッド内に処理を追加しています。

 どのような処理が追加されているのか、ただ記述するだけではなくて、処理の理解を行いながら実装を行ってください。


MoveToClickTileMapPoint.cs

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


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


3.<ローカル関数>


 C# 7.0 以降ではメソッドの内部に入れ子としてメソッドを作成出来るようになっています。
メソッド内部に宣言するメソッドをローカル関数と呼びます。

 メソッド内の入れ子である関数であるため、修飾子の宣言は出来ません(private のみ)が引数も戻り値も持たせることが出来ます。
C# 8.0 以降では、属性を付与したりすることも出来ます。


<抜粋>
    // 経路表示の生成
        StartCoroutine(GenerateCornerLineFromPath(transform.position, nextPos));


    /// <summary>
        /// 経路表示の生成
        /// </summary>
        /// <param name="startPos"></param>
        /// <param name="endPos"></param>
        IEnumerator GenerateCornerLineFromPath(Vector3 startPos, Vector3 endPos) {

            // パスを取得するまで待機
            yield return new WaitUntil(() => agent.hasPath);

            Debug.Log(agent.hasPath);
            Debug.Log(agent.isOnNavMesh);

            NavMeshPath path = new NavMeshPath();


            // 経路の情報を取得できているか確認
            if (NavMesh.CalculatePath(startPos, endPos, NavMesh.AllAreas, path)) {

                // 経路の座標情報取得
                Vector3[] corners = path.corners;

                // すでに移動中のアニメを再生している場合
                if (coroutine != null) {

                    // アニメ再生を停止
                    StopCoroutine(coroutine);
                    coroutine = null;
                }

                // アニメ再生用のメソッド登録
                coroutine = charaController.SetAnime(corners);

                // 登録したアニメ再生用のメソッドを実行
                StartCoroutine(coroutine);


                // TODO 経路情報がある場合、経路表示用のゲームオブジェクト作成


                // TODO 経路表示の作成


            }
        }

 ローカル関数は通常の関数と同じように、実行命令を行うことで実行されます。
ただし、利用する範囲(スコープ)が、ローカル関数が定義されているメソッド内部のみに限られます

 今回のケースであれば、GenerateCornerLineFromPath メソッドを SetPathAndMove メソッド内で宣言していますので、
GenerateCornerLineFromPath メソッドを呼び出す命令は、SetPathAndMove メソッド内でしか実行できません。

 利用方法としては、メソッドの呼び出し命令が特定のメソッド内にのみにあり、かつ、他のメソッドからも呼ばれることのない処理をローカル関数として準備することが一般的です。
利用範囲を制限している分、処理が読みやすくなりますし、エラーも見つけやすくなります。(使い道が限られている処理をメソッド化し、メソッド内部に閉じ込めておくことで可読性が上がります。)

MicroSoft
ローカル関数
https://docs.microsoft.com/ja-jp/dotnet/csharp/pro...
.NET column 様
C#のローカル関数とは?使い方やメリット・ラムダ式との違いも紹介
https://www.fenet.jp/dotnet/column/language/3933/


4.<コルーチンを変数に代入して実行する>


 以下のページに詳細な説明があります。

 => コルーチンを変数に代入して実行する

 今回のケースでは、敵がプレイヤーを見失ったり、敵の攻撃処理の途中で敵が倒された場合に、攻撃の準備の処理を停止できるようにするためにこの機能を実装しています。

 コルーチンメソッドを実行しているスクリプトがアタッチされているゲームオブジェクトが破棄された場合、
コルーチンメソッドの処理も強制的に停止されます。そのため、主な目的としては、敵がプレイヤーを見失った場合に、攻撃準備処理を停止させることです。

 コルーチンメソッドは非同期処理であるため、それ以外の処理が終わるのを待たずに、コルーチンメソッド内の処理を進めていきます。
便利である反面、コルーチンメソッドの処理が不要になった場合にはしっかりと停止する必要があります。


<変数にコルーチンメソッドを登録して実行>

    private IEnumerator coroutine;   // コルーチンメソッドを登録するための変数


    // アニメ再生用のメソッド登録
    coroutine = charaController.SetAnime(corners);

    // 登録したアニメ再生用のメソッドを実行
    StartCoroutine(coroutine);



<変数に登録されているコルーチンメソッドを停止>

    // すでに移動中のアニメを再生している場合
    if (coroutine != null) {

        // アニメ再生を停止
        StopCoroutine(coroutine);
        coroutine = null;
    }

参考サイト
Qiita @riekure 様
【Unity】非同期処理を理解する〜コルーチン編〜
https://qiita.com/riekure/items/9f59fec68c3e31f38f...


5.Chara ゲームオブジェクトに CharaController スクリプトをアタッチする


 Chara ゲームオブジェクトに CharaControlller スクリプトをアタッチします。

インスペクター画像


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


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


 処理の内容が複雑になっていますので、しっかりと読み解いてからすすめるようにしてください。

 ゲームを実行し、いままでと同じように移動したい地点をクリックしてください。
キャラのアニメが移動方向と同期するようになれば制御成功です。


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




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

 次は 手順12 −味方キャラの移動経路の表示機能の実装− です。