Unityに関連する記事です

 2回の手順に分けて、経路移動の機能を実装します。
今回は、経路の情報を元に移動の処理を行う RailMoveController クラスを作成し、自動的に経路に沿った移動を制御する処理を実装していきます。

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



手順8 ー経路の自動移動機能ー

 この処理が問題なく動作するようになったら、さらに別の手順が実装できます。
GameManager クラスを作成して、そちらから RailPathData クラスの情報を受け取って、同じように移動の処理を動作するように処理を追加します。
そうすることにより、その都度ゲーム内で利用する経路の情報を切り替えて、処理を実行出来るようにします。

 まだ先にはなりますが、経路移動については、ここが最終的な目標になります。


<学習内容>
 ・ラムダ式とデリゲート
 ・メソッドチェーン
 ・DOTweenの補間機能と実装例  DOPath メソッド、SetEase メソッド、OnWaypointChange メソッドー
 ・DOTweenの補間機能と実装例◆ Tween.Kill メソッド、Tween.Pause メソッド、Tween.Play メソッドー
 ・処理のリファクタリング
 ・Linq の機能の実装例  Select メソッド、ToArray メソッド、Sum メソッドー



1.RailMoveController を作成する


 作成した RailPathData を活用して、カメラを経路に沿って移動させていく処理を施すクラスを作成します。
利用している処理のメソッドについては、順番にまとめてあります。

 using には、DOTween の機能を利用するための DG.Tweening の宣言と、Linq の機能を利用するための System.Linq の宣言を、それぞれ追加しています。

 DOTween はカメラを経路に沿って移動させる機能である DOPath メソッドを実装するために利用しています。
また、この DOPath メソッドには、経路上の各地点に到着した際に利用できる機能である OnWaypPointChange メソッドを追加できますので、今回はそちらも合わせて利用しています。

 プログラムのロジックは、利用できる機能を活用し、上手に組み立てていくことが重要です。


RailMoveController.cs

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



2.RailMoveController ゲームオブジェクトを作成して、RailMoveController クラスをアタッチする


 ヒエラルキーの空いている場所で右クリックをしてメニューを開き、Create Empty で新しいゲームオブジェクトを作成します。
名前を RailMoveController に変更し、作成した RailMoveController クラスをアタッチしてください。

 インスペクター上から変数をアサインできますので、RailMoveTarget には Main Camera ゲームオブジェクト、
CurrentRailPathData には RailPathData ゲームオブジェクトをアサインしてください。


<インスペクター画像>


 これで完成です。


3.<ラムダ式とデリゲート>


 処理を省略して記述する記法の1つに ラムダ式 による記法があります。
ラムダ宣言演算子( => )を利用することによって、匿名メソッド(即席の使い捨てのメソッド)を作成して、処理を記述する ことが出来ます。 

tweenMove = railMoveTarget.transform.DOPath(targetPaths, duration, pathType).SetEase(Ease.Linear).OnWaypointChange((waypointIndex) => CheckArrivalDestination(waypointIndex));

<上記命令内のラムダ式による記述部分>
  〜.OnWaypointChange((waypointIndex) => CheckArrivalDestination(waypointIndex));

ラムダ式の処理にはデリゲート という機能が利用されています。
デリゲートとは平たくいうと、メソッドを代入できる変数のようなもの です。

 ラムダ式の処理は必ずデリゲートに代入して行われます。
これはつまり、メソッドの引数としてデリゲートを受けとれるようにおくことで、
ラムダ式を使ってその場で 使い捨てのメソッドを作って登録する(この部分がデリゲート)ということができるようになります。

 一部のメソッドの引数にはラムダ式でないと式が記述できないものもあります。
今回利用している OnWaypointChange メソッドや、同じ DOTween の OnComplete メソッド、あとは Linq(リンク) 機能などです。



ラムダ式を利用しない場合の、通常の処理をまずは確認しておきます。

  〜.OnWaypointChange(SetHoge(waypointIndex));
       ↓
delegate int Hoge(int x);                           // デリゲートの宣言

private void SetHoge(int waypointIndex) {

    Hoge h = new Hoge(CheckArrivalDestination)      // デリゲートの初期化 Hoge 型の h 変数に CheckArrivalDestination メソッドを代入している
      h(waypointIndex);                               // h 変数に代入されている CheckArrivalDestination メソッドを実行し、引数として waypointIndex 変数を渡している
}
       ↓
  private void CheckArrivalDestination(int waypointIndex) {

}

上記がラムダ式を用いない場合の処理です。2つ目の処理がデリゲートの処理になっており、その中で目標となるメソッドを実行しています。
次は、ラムダ式を用いた場合の処理です。

  〜.OnWaypointChange((waypointIndex) => CheckArrivalDestination(waypointIndex));
       ↓
  private void CheckArrivalDestination(int waypointIndex) {

}

SetHoge メソッドの分がそのまま省略されているので、このように、1つ分のメソッドを作る手間が省けることがわかります。

 ラムダ式の記法の読み方としては、ラムダ宣言演算子の 左側に引数の変数を記述 し、右側に処理(メソッドの内容)を記述 します。
また、ラムダ式には 型推論 の機能があるため、引数と戻り値には 型を省略して記述できます (ここでは int 型)。

  (waypointIndex) => CheckArrivalDestination(waypointIndex);    //  (int waypointIndex) => と記述しなくてよい

最初のうちは、省略されている匿名メソッド部分を実際にイメージして書いてみる と理解が深まります。


参考サイト
MicroSoft
ラムダ式 - C# リファレンス
https://docs.microsoft.com/ja-jp/dotnet/csharp/lan...
Qiita @toRisouP 様
【C#】わかった"つもり"になれる「ラムダ式」解説
https://qiita.com/toRisouP/items/98cc4966d392b7f21...


4.<メソッド・チェーン>


 DOTween の機能について説明する前に、メソッド・チェーンについて説明します。

 // 各地点に向けて移動
  transform.DOPath(paths, 1000 / moveSpeed).SetEase(Ease.Linear);

上記の処理は、複数のメソッドをつなげて1行で記述している処理 になります。
ピリオドの位置までが1つの処理になっており、合計で2つの DOTween のメソッドが実行されています

 このようにピリオドを利用して、前のメソッドの処理に続けて次のメソッドの処理を書くと、前のメソッドの処理を受けて次のメソッドの処理が実行されます。
この記述方法をメソッド・チェーンといいます。これは C# が持つ機能です。DOTween だけのものではありません。

 上記の例の場合、transform に対して DOPath メソッドが最初に実行されて、その処理結果を受けて SetEase メソッド実行されるようになっています。

 プログラムは上から下に実行されていきますが、1行に書かれた処理の場合は、手前(左側)より、ピリオド単位で区切って実行される ことになります。
これは他の処理と同様です。しっかりと処理の順番を追えるように、読み解いてみましょう。


5.<DOTweenの補間機能と実装例  DOPath メソッド、SetEase メソッド、OnWaypointChange メソッドー>


 新しく実装を行った DOTween の機能について、各メソッドを順番に説明します。
DOTween については公式のドキュメント(英語)がありますので、こちらも参考にしてください。
DOTween
Documentation
http://dotween.demigiant.com/documentation.php


1.DOPath (Vector3[] path, float duration)

 今回実装している処理は次のものです。ここでは3つの DOTween のメソッドを活用してロジックを組み立てています。

 tweenMove = railMoveTarget.transform.DOPath(targetPaths, duration, pathType).SetEase(Ease.Linear).OnWaypointChange((waypointIndex) => CheckArrivalDestination(waypointIndex));

 DOPath メソッドは、他の移動系の DOTween のメソッドと同じで、制御したいゲームオブジェクトの Transform コンポーネントに対して命令を出すメソッドです。
Transform コンポーネント、あるいは RectTransform コンポーネントの Transform の Position 値に対して補間処理を行って、アニメしているように演出してくれる機能です。
LocalPosition に対して利用できる DOLocalPath メソッドもあります。

 ゲームオブジェクトの位置を現在値から第1引数で指定した値を目的地として第2引数に指定した時間をかけて移動を行います。
第1引数は targetPaths 変数を指定しています。この部分は Vector3 型の配列を指定します。今回であれば、2つの座標情報が含まれています。
これが移動を行い際に経由する座標になります。つまり、この配列の中の最初の座標(開始地点)から次の座標(目標地点)に対して移動を行う処理になります。
この機能を使うことにより、経路用のゲームオブジェクトの Transform コンポーネントの Position を順番に目的地として移動していくようになります。

 第2引数は float 型の値を指定します。今回は duration 変数を指定していますので、この値の時間をかけて目標地点に着くようになります。

 DOPath メソッドには他にも便利な使い方があります。例えば、ベジェ曲線を作成して移動を行いたい場合や、
同じ範囲を周回移動させたい場合にも対応できますので学習しておくといいでしょう。


参考サイト
Qiita @BEATnonanka 様
DOTween完全に理解するその4 DOPath編
https://qiita.com/BEATnonanka/items/50cacac803f88f...


2.SetEase (EaseType easeType)

 SetEase メソッドは他の DOTween のメソッドに付随する処理です。このように DOTween では DOTween のメソッド同士を1つの処理の塊として続けて処理を行うことができます。
処理を続ける場合には、前のメソッドの後にピリオド(ドット)を書くことで、次のメソッドを書くことが可能です。(最初に説明した、メソッド・チェーンの記述方法です)

 SetEase メソッドでは Ease というアニメーションさせる際のパターンを変更することができます。Ease は enum で設定されており、Ease.タイプ の書式で記述します。
非常に多くの種類があります。今回は Ease.Linear を使用しています。

 今回は、DOPath メソッドが実行されてゲームオブジェクトの Position が変更されている処理を実行している部分に、この SetEase メソッドが実行されますので
位置情報を変更する際の補間処理に対して、どのようなタイプのアニメーションのパターンを設定して移動を行わせるかを指定しています

 Position を変更する際に Linear を指定すると、最初の位置から目標となる位置までの移動速度を等速に指定することが出来ます。
そのため、初速で早くなったり、終了間際に遅くなったり、といったような補間処理ではなくなり、最初からゴール地点に到着するまで等速で座標の変更、つまり移動を行います。
Ease を変更してみると処理の内容をつかみやすくなりますので、下記のサイトを参考して設定を変更してみてください。

参考サイト
ゲームUIネット 様
DOTweenのイージング一覧を世界一詳しく&分かりやすく説明する
https://game-ui.net/?p=835


3.OnWaypointChange (int index)

 DOPath メソッド、あるいは DOLocalPath メソッドを実行している処理にのみ追加できるメソッドです。これ単体では動作しません。

 〜.OnWaypointChange((waypointIndex) => CheckArrivalDestination(waypointIndex));

 この処理を記述しておくことにより、DOPath メソッドの第1引数で指定している配列の座標に移動するたびに、1回だけ処理を実行します。
OnWaypointChange メソッドの実行にあたり、現在の配列のインデックス番号を int 型として自動的に取得してくれます。
そのため、OnWaypointChange メソッドの中にある処理を実行する際には、この番号を利用して処理を記述することが出来ます。
なお、この情報は引数に記述しなくても自動的に入りますが、よりわかりやすく記述する場合には以下のようにも記述できます。(waypointIndex 部分は変数ですので、自由に宣言できます)

 tweenMove = railMoveTarget.transform.DOPath(targetPaths, duration, pathType).SetEase(Ease.Linear).OnWaypointChange((waypointIndex) => CheckArrivalDestination(waypointIndex));

 tweenMove = railMoveTarget.transform.DOPath(targetPaths, duration, pathType).SetEase(Ease.Linear).OnWaypointChange(CheckArrivalDestination);

 どちらも同じ処理になります。自分のわかりやすい方の書式を利用してください。


参考サイト
Qiita @AzuQiita 様
DOTweenのコールバックをいくつか紹介
https://qiita.com/AzuQiita/items/822e382473e6c0db8...


 以上が今回実装している、DOTweenの処理になります。しっかりと読み解いていって使い方を覚えていきましょう


6.<DOTweenの補間機能と実装例◆ Tween.Kill メソッド、Tween.Pause メソッド、Tween.Play メソッド>


 Tween 型は、DOTween の元となる処理を管理制御しているクラスです。
この Tween 型で宣言した変数には DOTweenの処理内容を代入することが出来ます。

 DOTweenの処理は変数に代入せずとも実行できます。本来はそれだけで問題はないのですが、
Tween 型の変数に DOTween の処理を代入していることで、DOTweenの処理を一時停止させたり、再開したり、強制的に終了させることが出来るようになります。

 どのような場面で使用するかというと、無限ループの処理を終了させたり、処理を一時停止したり、処理の途中から再開したりできます。


1.Tween.Kill()

 変数にDOTweenの処理を代入していない場合、動いている DOTween の処理を強制的に終了させる処理が実行できません

 DOTweenの処理を中断するには、Killメソッドを実行します。この処理を実行することで、今回であれば DOPath メソッドによる経路移動処理を終了させることが出来ます。
DOTween の処理は非同期処理です。例えば、ゲームオーバーになってゲームを再開するような処理がある場合、シーンが切り替わったとしても
一度、動いている DOPath メソッドを終了させておかないとメモリリークが発生します。(ループ処理がある場合には、エラーも発生します。)
特に破壊される可能性のあるゲームオブジェクトに DOTween を利用している場合には、必ず破壊のタイミングで Kill メソッドを利用するようにしてください。

  // 宣言フィールド 
 private Tween tweenMove;  // Tween 型の変数

  // Tween 型の変数には DOTween の処理を代入できる
     ↓
  // 各地点に向けて移動。今後この処理を制御するため、Tween 型の変数に DOPath メソッドの処理を代入しておく
  tweenMove = railMoveTarget.transform.DOPath(targetPaths, duration, pathType).SetEase(Ease.Linear).OnWaypointChange((waypointIndex) => CheckArrivalDestination(waypointIndex));

 // DOTween の処理を破棄(終了)する
  tweenMove.Kill();


2.Tween.Pause()

 DOTween の Tween 処理を変数に代入しておくことにより、Kill メソッドの他にも制御を行うことが出来ます。

 Pause メソッドは Tween の処理を一時停止する命令です。この処理を行うことにより、Tween 型の変数内に代入されている実行中の DOTween の処理を一時停止出来ます。


<RailMoveController.cs>
   // 処理を一時停止
    PauseMove();


    /// <summary>
    /// レール移動の一時停止
    /// </summary>
    public void PauseMove() {
        // 一時停止
        tweenMove.Pause();
    }

 なお、同じ処理に DOPause メソッドがあります。こちらは、一時停止させたい処理を行っている Transfrom 型に対して命令出来ます。

  railMoveTarget.DOPause();


3.Tween.Play()

 一時停止している DOTween の処理は2つの方法で再開可能です。つまり、上手く使い分ける必要があります。
1つは今回利用している Play メソッドです。この命令では、一時停止して中断している DOTween の処理の続きから処理を再開できます。
もう1つは Restart メソッドです。この命令では続きからではなく、最初から処理の再生をやり直します。

 ロジックとしては、ゲーム開始前や、ミッションが発生している間は Pause メソッドで DOPath メソッドによる移動の処理を一時停止しておいて、
ゲームが開始されたり、ミッションが終了して移動が再開できる状態になったタイミングでこの Play メソッドを利用して移動を再開しています。

<RailMoveController.cs>
    // 移動再開
    ResumeMove();


    /// <summary>
    /// レール移動の再開
    /// </summary>
    public void ResumeMove() {
        // 移動再開
        tweenMove.Play();
    }

 なお、Play メソッドと同じ処理に DOPlay メソッドがあります。こちらは、再開させたい処理を行っている Transfrom 型に対して命令出来ます。

  railMoveTarget.DOPlay();


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


 想定している挙動になるかどうか、ゲームを実行して確認していきます。

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


 続いては、処理のリファクタリングを行います。


<応用>8.リファクタリングを行い、Linq を使った処理に書き換える


 バグの修正とは異なり、プログラムとしての動作を変えないままに処理を改善することをリファクタリングといいます。

 今回は RailMoveController.csの StartRailMove メソッド内の処理を Linq を活用して書き換えてみます。

 現在の処理をしっかりと読み解き、理解できている場合のみこの手順を行うようにしてください。
難しいと感じる場合には、これらの手順はスキップしてください。


<現在の処理>
    /// <summary>
    /// レール移動の開始
    /// </summary>
    /// <returns></returns>
    public IEnumerator StartRailMove() {

        yield return null;


////*  ここから  *////


        // 移動する地点を取得するための配列の初期化
        Vector3[] paths = new Vector3[currentRailPathData.GetPathTrans().Length];
        float totalTime = 0;

    // 移動する位置情報と時間を順番に配列に取得
        for (int i = 0; i < currentRailPathData.GetPathTrans().Length; i++) {
            paths[i] = currentRailPathData.GetPathTrans()[i].position;
	    totalTime += currentRailPathData.GetRailMoveDurations()[i];
        }


////*  ここまで  *////


        Debug.Log(totalTime);

         [中略]
    }

   ↓

<Linq の処理(ラムダ式の記述を使う)>
    /// <summary>
    /// レール移動の開始
    /// </summary>
    /// <returns></returns>
    public IEnumerator StartRailMove() {

        yield return null;


////*  ここから  *////


        // 移動先のパスの情報から Position の情報だけを抽出して配列を作成
        Vector3[] paths = currentRailPathData.GetPathTrans().Select(x => x.position).ToArray();

        // 移動先のパスの移動時間を合計
        float totalTime = currentRailPathData.GetRailMoveDurations().Sum();


////*  ここまで  *////


        Debug.Log(totalTime);

         [中略]
    }


9.<Linq の機能の実装例  Select メソッド、ToArray メソッド、Sum メソッドー>


 Linq(リンク)とは、コレクション(Dictionary や List など)の要素を操作して、検索したり集計する処理を簡潔に記述することができるライブラリ(複数の機能をまとめたもの)です。

 Linqを使用するためにはusing の宣言が必要になります。今回作成したメソッド内に登場している処理ですので、復習して読み解けるようにしていきましょう。

using System.Linq;



 Linqを記述する際にはラムダ式の記述を用います。
SamuraiBlog様
【C#入門】LINQの使い方総まとめ(Select、Where、GroupByなど)
https://www.sejuku.net/blog/56519

 Linqには多くの機能がありますが今回利用している機能についてまとめておきます。
そのほかの機能については記事がたくさんありますが、こちらのサイトも参考になります。
地平線に行く様
LINQの拡張メソッド一覧と、ほぼ全部のサンプルを作ってみました。
https://yujisoftware.hatenablog.com/entry/20111031...


1.Select メソッド

 射影処理と呼ばれる機能です。射影とはデータベース用の専門用語で、テーブル(実データ)から特定の列のデータのみを取り出すことを言います。

 Select メソッドでは引数にした条件を元に、照合できたデータのみを取り出す操作を行います。
このメソッドの戻り値は IEnumerable<TResult> であり、その情報を取得して利用したい場合には、メソッドチェーンを利用して、ToList メソッドや ToArray メソッドを利用して扱える状態にします。
これは Where メソッドと同じ処理の手順になります。

  // 移動先のパスの情報から Position の情報だけを抽出して配列を作成
    Vector3[] paths = currentRailPathData.GetPathTrans().Select(x => x.position).ToArray();

 こちらの実装例では、RailPathData クラスに用意している GetPathTrans メソッドの戻り値で取得した Transfrom 型の配列変数(pathTrans 変数)に対して Select メソッドを利用しています。

 pathTrans 配列変数の各要素より、potision 変数の情報をそれぞれ取り出します。そのため、Vector3 型の情報を抽出した結果が出来上がります。
GetPathTrans().Select(x => x.position) ここまでが Select メソッドです。


参考サイト
MicroSoft
Enumerable.Select メソッド
https://docs.microsoft.com/ja-jp/dotnet/api/system...
.NET Column 様
【LINQのSelectメソッドの書き方3選|LINQについてなどを紹介
https://www.fenet.jp/dotnet/column/language/1454/
Qiita @t_takahari 様
LINQのそのForEach、実はSelectで書き換えられるかも
https://qiita.com/t_takahari/items/6dc72f48b1ebdfe...


2.ToArray<TSource> メソッド

 IEnumerable<T> から配列を作成するメソッドです。

 IEnumerable<T> とは、 Linq の処理を実行した際の戻り値の情報です。例えば、フィルタリングして算出された結果を 配列型にして利用したい場合に利用します。
このメソッドの戻り値は <TSource> とジェネリック型であるため、 Select メソッドなどで抽出されている型の配列が出来上がります。

  // 移動先のパスの情報から Position の情報だけを抽出して配列を作成
    Vector3[] paths = currentRailPathData.GetPathTrans().Select(x => x.position).ToArray();

 今回の実装では、Select メソッドによって抽出された情報を ToArray メソッドを使って配列にしています。
Select メソッドのみでは抽出した情報が出来上がるのみで、このメソッドを利用しないと配列になりません。


参考サイト
MicroSoft
Enumerable.ToArray<TSource>(IEnumerable<TSource>) メソッド
https://docs.microsoft.com/ja-jp/dotnet/api/system...
Qiita @Marimoiro 様
LINQチートシート的なもの
https://qiita.com/Marimoiro/items/0e119b47d65bf138...
Qiita @mounntainn 様
LINQについての備忘録
https://qiita.com/mounntainn/items/e8b8f94a15ec4fe...


3.Sum メソッド

 集計用のメソッドの1つです。指定したコレクションの要素の合計値を戻します。

    // 移動先のパスの移動時間を合計
    float totalTime = currentRailPathData.GetRailMoveDurations().Sum();

 こちらの実装では、指定された RailPathData クラスにある moveDurations 配列変数を GetRailMoveDurations() メソッドで取得し、その要素をすべて合計しています。
moveDurations 配列変数の要素が4つあるのであれば、4つの合計値が計算され、その処理結果が totalTime 変数に戻されて代入処理されます。


参考サイト
MicroSoft
Enumerable.Sum メソッド
https://docs.microsoft.com/ja-jp/dotnet/api/system...
Qiita @RyotaMurohoshi 様
~https://qiita.com/RyotaMurohoshi/items/d4a6750a798...
Qitta @logikuma 様
【C#】LINQ備忘録2 〜集計編〜
https://qiita.com/logikuma/items/d4f87ff4c308a7606...


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


 リファクタリングを行ったら、必ず処理を確認します。
いままでと同じ挙動になっていなければ、再度リファクタリングをやり直す必要があるためです。
 


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

 => 次は 手順9 ーミッション発生用機能の追加ー です。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3Dレールガンシューティング(応用編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます