Unityに関連する記事です

 引き続き、ゲームの進行管理の機能を実装していきます。
今回の手順では敵の生成と移動に関して、ゲームの進行管理のステートを運用して制御を行う機能を追加します。

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

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


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

手順25 ーゲームの進行管理の制御機能の実装◆
43.GameState を利用し、GameManager と EnemyGenerator と CharaGenerator を修正して、敵の制御(生成と移動)機能を追加する



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

 ・List の使い方◆ Remove メソッドー
 ・戻り値と引数を利用した処理の挙動を把握する
 ・WaitUntil クラス



43.GameState を利用し、GameManager と EnemyGenerator と CharaGenerator を修正して、敵の制御(生成と移動)機能を追加する

1.設計


 前回の手順において、GameState を活用したロジックの組み込みをおこないました。
制御の結果、ゲームが実行されている間だけ、画面をタップしてキャラ配置ポップアップが表示されるようになりました。

 今回もこの GameState を活用して、敵の移動について制御を追加していきます。

 現在はキャラ配置ポップアップが開いている間も敵が移動を行うようになっています。この状態のままですと、プレイヤーがキャラを選択する時間がありません。
常にリアルタイムなゲーム環境を提供するのであれば問題のない処理ですが、今回のゲームでは、プレイヤーにはポップアップが表示されている間には、どのキャラを配置するのかを考える時間を用意したいと思います。

 そのため、実装したい挙動としては、次のようなものになります。

 ・キャラを配置するポップアップが開いている間は敵の移動を一時停止させたい
  あわせて、敵の生成も行わないようにしたい

 ・このポップアップが閉じたら敵の移動と敵の生成を再開させたい

 上記の機能のうち、敵の移動の制御に関しては、EnemyController 内にそれぞれメソッドが準備してあります。PauseMove メソッドと ResumeMove メソッドです。
そのため、この部分に関しては新しく処理を作るのではなくて、適切なタイミングで呼び出し命令を実行すれば処理を実装できると考えます。

 よって、この PauseMove メソッドや ResumeMove メソッドの処理を処理のゴール地点とするならば、この処理に辿り着くためにはどんな処理の流れを作るべきか
ロジックを組んでいくためには、すでにある部分の把握と利用するタイミング、そして、どの部分が足りないのか、という観点で考えてみてください。

 そうなると当然ながら、どのから処理がスタートするのかも考えていく必要が生まれてきます。



 続けて、敵の生成についても考えてみます。この機能は EnemyGenerator が管理しています。
そのため、生成を行っている部分に、前回のポップアップの表示制御と同じように GameState の確認を行うようにすればどうでしょうか。
ゲームの進行状態は GameState が管理していますので、GameState が Play の間だけ、敵の生成を行うようにするイメージです。

 この制御を行うためには、どの部分に制御を行えばいいのかを処理を見直してみましょう。


2.GameManager スクリプトを修正し、GameState を用いたゲームの進行管理を利用して、敵の移動の制御を行う


 敵の制御を行うことを考えた場合、ヒエラルキーにあるすべての敵役のゲームオブジェクトを探して利用することもできます。
ですが、この処理が頻繁におこなわれることを考えた場合、この手法は非効率的であり、有効ではありません

 制御を行う対象を List などのコレクションに代入することにより、1つの変数で、ヒエラルキーにある敵のゲームオブジェクトの管理を行うことが出来ます。

 とはいえ、では、どのタイミングでこれらの敵の情報を List に代入するか、が大切になります。

 最適解として考えられるのは、敵を生成したタイミングです。Instantiate メソッドはクラスやゲームオブジェクトを元にしてクローンの生成を行うメソッドですが、
合わせて、戻り値として生成したクローンの情報を提供する機能があります。戻り値には、生成した際のクラス、あるいはゲームオブジェクトの型の情報が戻されます。

 よって、敵の生成のタイミングに合わせて、List へこの生成された敵の情報を代入することが出来れば、敵が生成されるたびに自動的に List へと追加していくロジックを組むことが出来ます。



 処理の違いを精査してみましょう。合わせて、この List の情報を利用していく部分まで深堀りして、どういった処理が運用を考えた処理になっているかを確認します。

 \言後に敵を見つける場合

 Instantiate メソッドで生成された敵のゲームオブジェクトはヒエラルキーに配置されます。
このとき、Instantiate メソッドはクローンを生成するだけですので、その後は、GameManager クラス内で生成を行っていたとしても、参照を持たないゲームオブジェクトになります。
つまり、敵のゲームオブジェクトも、GameManager も、お互いに知らない状態として存在することになるので、GameManager が敵の制御を行うためには、
まず、敵を探して、その後、敵の情報(EnemyController)を取得する必要があります。

  // ヒエラルキーにある、タグが Enemy であるすべてのゲームオブジェクトを探して配列に代入する
  GameObject[] enemyObjs = GameObject.FindOfGameObjectsTag("Enemy");

  // 敵1体ずつに停止命令を出す
  for(int i = 0; i < enemyObjs.Lenght; i++){

   // GameObject 型なので、まず、敵の情報である EnemyController クラスを取得する
      EnemyController enemy = enemyObjs[i].GetComponent<EnemyController>();

   // EnemyController クラス内にある PauseMove メソッドを実行して、敵の移動を一時停止する
      enemy.PauseMove():
  }

 処理を見るとわかるように、敵を一時停止するたびに、毎回、ヒエラルキーにあるゲームオブジェクトの検索と取得、そして、EnemyController の取得が必要になっていることが分かります。

 プログラムの良い部分は、同じ処理はなるべくメソッド単位でまとめておいて、それを実行することにありますが、この処理は敵を探したり、敵の情報を取得する処理を毎回行っているため、
「敵を管理する」という観点からみると、「敵を探す」「敵の情報を取得する」という部分がネックになっています。

 できればこれらの処理自体も1回で終了して、管理している敵に対して命令を出すだけにしたいと考えてみてください。
どうすればそのロジックが出来るようになるのか、無駄になっている部分はないだろうか、という視点でスクリプトを見直すとよい学習になります。


◆\言時に敵を見つける場合

 では、次は、今回実装予定の、Instantiate メソッドの戻り値を利用して敵の情報を管理する場合と命令です。
実際のスクリプトの処理は、敵の生成と管理を行うクラスが異なっているので、このままの処理ではありませんが、こういう処理の手順で進んでいる、ということを考えてください。

  // 敵の生成
 EnemyController enemy = Instantiate(enemyPrefab);

 // 敵の情報を List に追加(GameObject 型ではない部分に注目)
  enemiesList.Add(enemy)

 まず、この部分だけで敵を管理するための処理が整います。前回と大きく違うのは、生成のタイミングで List へ追加しているため、敵の情報が常にある状態になっています。
よって、敵の制御を行うたびに必要になっていた、敵を見つける、という手間がなくなっていることです。

 つまり、この処理があることによって、前回では毎回おこなっていた敵を探す部分が、この処理1回だけで済むようになります。

 次に命令です。

  for(int i = 0; i < enemiesList.Count; i++) {
      enemiesList[i].PauseMove();
  }

 こちらも大きな違いとして、GameObject 型の敵のまとまりではなく、最初から EnemyController 型として敵を1つのまとまりにして List 管理しています。
そのため、利用したい EnemyController 型の情報がすぐに利用できる状態になっているため、GetComponent メソッドの処理が不要となり、すぐに、PauseMove メソッドの呼び出しが実行出来ています。

 GameObject.Find 系のメソッドも、GetComponent メソッドも、どちらも負荷の軽い処理ではありません。
,亮衙,両豺隋△海譴蕕僚萢を敵の制御を行うたびに、毎回行うようになっています。敵の数が増えるほどに負荷も上がりやすくなります。
そのため、これらを利用せずに、かつ、処理の実行がすぐにできるという部分で考えても、△亮衙,諒が理にかなっており、効率化された、よりよいロジックでの実装であると言えます。


 是非こういう目線でロジックを見ながら、処理を考えて実装を行うようにしてみてください。


GameManager.cs


 スクリプトを修正したらセーブをします。


3.<List の使い方◆ Remove メソッドー>


 List.Remove(T型) メソッドの機能の説明になります。

 Listの中にある、引数で指定した要素(データ)を削除します。
引数が T 型となっていますが、これは Add メソッドと同じで List を宣言した際に使った型が自動的に入ります。
今回作成している enemiesList 変数は EnemyController 型の List を宣言していますので、引数には EnemyController 型のみ指定できます。

 Listで宣言している型と同じ型であれば Remove メソッドで List から指定した要素を削除することが出来ます

  // List から EnemyController 型の removeEnemy 変数の要素(つまり、敵の情報)を削除
 enemiesList.Remove(removeEnemy);

 Remove メソッドでは引数で指定したデータのみをList内から削除しますので、他の要素は削除されません。

 今回の実装ケースでは、敵が破壊されたタイミングでこの処理の記述されているメソッドを実行し、enemiesList 変数内の指定された EnemyController の情報を削除します。


参考サイト
SamuraiBlog 様
【C#入門】Listの使い方総まとめ(ArrayList/Add/Remove/ソート/検索)
https://www.sejuku.net/blog/47378


4.EnemyGenerator スクリプトを修正して、生成した敵の情報を GameManager 内にあるリストに追加する機能を実装する


 いままでは敵を生成するだけで終了していましたが、今回からは、生成した敵の情報を GameManager クラスにある List に対して提供をする必要があります。

 こういったときに便利なのがメソッドの引数です。生成を行った敵の情報を EnemyController 型として引数を通じて GameManager クラス側へ届けることが出来ます。
GameManager 側には AddEnemyList メソッドがあり、この引数として EnemyController 型を追加してありますので、この引数部分が情報の受け取り口であると考えてください。

 よって、EnemyGenerator 側の処理としては、AddEnemyList メソッドを実行する際に、EnemyController 型の情報を用意する必要が生まれています。

 今回はこの処理を、戻り値を活用したメソッドも合わせて利用して実装を行っています。
ロジックが難しくなってきていますので、しっかりと処理の流れを把握しながら進めるようにしてください。

 最初は戻り値を使わずに、自分なりの実装をしてみてもいいでしょう。生成された敵の情報が、EnemyGenerator から GameManager へと届けることが出来るか、
そのイメージが沸くかどうかが、とても重要な内容になります。


EnemyGenerator.cs


 スクリプトを修正したらセーブします。


5.<戻り値と引数を利用した処理の挙動を把握する>


 今回、GameManager を修正した際に、AddEnemyList メソッドには EnemyController 型の引数の指定が追加されています。
いままでは AddEnemyList メソッドには引数の指定がありませんでしたので、メソッドを書くだけで実行できましたが、
今回から、この AddEnemyList メソッドの呼び出しを行う際には、引数に情報を用意していないと処理が行えない状態になりました。


GameManager.cs
    public void AddEnemyList(EnemyController enemy) {
        enemiesList.Add(enemy);
        generateEnemyCount++;
    }

 引数に指定されている情報は EnemyController 型です。この情報を利用して、敵の情報をリストに追加したいためです。
よって、AddEnemyList メソッドを実行するためには、EnemyController 型の情報を用意する必要があります。



 これに合わせて、EnemyGenerator 内の PreparateGenerateEnemy メソッドの中での記述が変更になっています。
敵を生成する処理自体は同じですが、生成された敵の情報を GameManager 側に用意したリストへ提供する処理が追加されています。
そのため、GenerateEnemy メソッドには戻り値の情報が追加されています。

EnemyGenerator.cs
    // 敵の生成し、敵の生成数のカウントアップと List への追加
    gameManager.AddEnemyList(GenerateEnemy());

EnemyGenerator.cs
   public EnemyController GenerateEnemy(int generateNo) {

       return enemyController;
    }

 AddEnemyList メソッドの実行の部分を、戻り値が反映された場合の処理に書き替えてみるとわかりやすくなります。

EnemyGenerator.cs
    // 敵の生成し、敵の生成数のカウントアップと List への追加
    gameManager.AddEnemyList(GenerateEnemy() の実行結果による EnemyController 型の情報);

   ↓
 
GameManager.cs
    public void AddEnemyList(EnemyController enemy <= GenerateEnemy() の実行結果による EnemyController 型の情報が enemy 変数に代入されている) {
        enemiesList.Add(enemy);  // <=その情報をリストに追加している
        generateEnemyCount++;
    }
 
 生成された敵の情報が AddEnemyList メソッドに引数を通じて渡されて、それがリストへ追加される、というロジックになっています。
この処理によって、EnemyGenerator の情報がクラスを超えて GameManager に届くようになっています。

 引数と戻り値による処理を活用することで処理に柔軟性と汎用性をもたらします。
処理の流れを読み解きながら、どのようにしてロジックが構成されているかをしっかりと把握しておいてください。


6.<WaitUntil クラス>


 WaitUntil クラスは、コルーチン内で命令できる待機処理です。yield return をつけて new (インスタンス)します

 WaitUntil クラスにはコンストラクタ・メソッドがあり、インスタンスされると、引数で指定した条件を満たすまで、処理を一時中断して待機させることが出来ます。

  // GameState の現在値(currentGameState)が Play になるまで処理を一時中断する
 yield return new WaitUntil(() => gameManager.currentGameState == GameManager.GameState.Play);

 今回の場合の条件は、【GameManager クラス内にある currentGameState 変数が Play になったら】という条件を満たすことが必要になります。
この条件を満たすまでは、ここで処理が一時中断しますので、次の行にある敵の移動を再開する処理が実行されない状態を作り出しています。


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


7.CharaGenerator スクリプトを修正して GameState の切り替え機能を実装する


 最後に CharaGenerator スクリプトを修正します。
GameState の状態に合わせて敵の生成を停止/再開する機能は実装できたのですが、この GameState 自体を切り替える機能はまだありません
よって、どこかのタイミングで、GameState を適切に変更を行っていくことにより、ゲームの全体の進行管理の状態が変化することになります。

 キャラを配置するポップアップが開いたとき、そして閉じるときに GameState の切り替えを適切に行うことができれば想定している挙動になりそうであることが分かります。

 この切り替えのタイミングが重要になります。どの部分に追加すればうまく機能するのか、スクリプトを確認する前に自分でロジックを考えてみましょう。
もちろん、誤ったタイミングにしてしまうと、他のプログラムにも影響が及びます。何故ならば、GameState によってすでにキャラを配置する処理が制御されているためです。

 TODO の部分を参考にしながら、どういったプログラムを書けばいいのか挑戦してみてください。


CharaGenerator.cs

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


 スクリプトを修正したらセーブします。


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


 ゲームを実行して、キャラ配置のポップアップを開いてみてください。
敵の生成が停止し、移動している敵が停止すれば制御成功です。

 ポップアップを閉じたら、再度、敵の生成が開始されて、敵の移動が再開されれば制御成功です。


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

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


 リストを活用することによって同じ型の情報を管理して運用することが可能になります。
多くのゲームで利用できる機能になりますので、処理の流れを確認しておいてください。



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

 次は 手順26 −ゲームのクリア条件と判定機能の実装− です。

コメントをかく


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

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

Menu



プログラムの基礎学習

コード練習

技術/知識(実装例)

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

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

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

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

レースゲーム(抜粋)

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

3D脱出ゲーム(抜粋)

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

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

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

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

3Dトップビューアクション(白猫風)

VideoPlayer イベント連動の実装例

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

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

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

private



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

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