Unityに関連する記事です

 エネミー用のプレファブと EnemyDataSO スクリプタブル・オブジェクトを利用し、クローンされたエネミーのゲームオブジェクトにエネミーのデータを設定し、
同じプレファブから異なるエネミーを自動的に生成する処理を実装します。

 以下の内容で順番に実装を進めていきます。
今回は内容が新しいものばかりで、非常に深いものになっています。


<実装動画 .痢璽泪襪離┘優漾爾ランダムで自動で生成される>
動画ファイルへのリンク


<実装動画◆.椒垢離┘優漾爾自動で生成される>
動画ファイルへのリンク


発展3 −エネミー用のデータベースの利用−
 5.EnemyController スクリプトを修正し、EnemyDataSO スクリプタブル・オブジェクトのデータを参照して反映する処理を追加する
 6.EnemyGenerator スクリプトを修正し、エネミー用のプレファブに EnemyDataSO スクリプタブル・オブジェクトのデータを参照して、1つのプレファブから異なるエネミーを自動生成する処理を追加する


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

 ・スクリプタブル・オブジェクトを利用する方法
 ・引数と戻り値を使った処理の実装例
 ・enum を条件に利用した switch 文の実装例
 ・Listの使い方  Add メソッドー



5.EnemyController スクリプトを修正し、EnemyDataSO スクリプタブル・オブジェクトのデータを参照して反映する処理を追加する

1.設計


 まずは最初に実装動画を確認してください。

 生成されるエネミーが、スクリプタブル・オブジェクトに登録したエネミーの画像を持ったゲームオブジェクトとして生成されています。
これは複数のエネミー用のプレファブを用意しているのでなく、今までと同じエネミー用のプレファブを利用していますが、
エネミー用のプレファブにエネミーの情報(EnemyData)を設定することによって、1つのエネミー用のプレファブから異なるエネミーを作成しています。
ボスも同様です。

 この手順では、エネミーの自動生成に新しい処理を追加して、スクリプタブル・オブジェクトを利用して1つのプレファブから異なるエネミーを作り出す方法を学習していきます。



 EnemyDataSO スクリプタブル・オブジェクトにエネミーのデータを登録しました。
スクリプタブル・オブジェクトは、スクリプト内に同名の型名で変数を作成することで利用することが可能になります。

<スクリプタブル・オブジェクトを利用できる状態にする>
  public EnemyDataSO enemyDataSO;

 上記のように変数として宣言することにより、変数を利用してスクリプタブル・オブジェクトの情報をゲーム内に利用することが出来ます。

 宣言の方法は他の変数と変わりません。また、public 修飾子にして宣言しておくことにより、
インスペクターより登録が出来る他、外部のスクリプトからも参照して利用することが可能です。これも他の変数と同じです。



 現在、エネミーのデータとしてゲーム内で利用されている情報は、EnemyController スクリプトにある、以下の2つの情報です。

  [Header("エネミーのHP")]       
  public int hp;            

  [Header("エネミーの攻撃力")]      
  public int attackPower; 

 いままではこちらの変数に対してインスペクターより設定を行って数値を決めていました。
ですが今回の設計ではエネミー用のプレファブのゲームオブジェクトは1つしかありません。
エネミーのゲームオブジェクトをプレファブのクローンとして生成すると、常に同じ hp と攻撃力を持っているエネミーが生成されていました。
そのため、今まではボスの生成に際しては、hp を 3 倍にするという処理を追加することで、Hp の値だけを変更していました。
 


 この設定の部分に、今回作成した EnemyDataSO スクリプタブル・オブジェクトのエネミーのデータを利用するように処理の変更を行います。
生成されるクローンのゲームオブジェクトは同じですが、その内容・情報に対して、エネミーのデータを反映することによって、生成されたエネミーの振る舞いを変更します。

 Normal のエネミーのデータを受け取ったプレファブのクローンのゲームオブジェクトは、Normal のエネミーとして振る舞うようになります。
Boss のエネミーのデータを受け取ったプレファブのクローンのゲームオブジェクトは、Boss のエネミーとして振る舞うようになります。

 この処理を自動化して、生成されたときにエネミーのデータの情報に合わせて、エネミーとしての振る舞いも自動的に変わるようにします。

 このような設計にすると、1つのプレファブから、エネミーのデータベースに登録されているすべてのエネミーを作り出すことが可能になります。
この手法は他のデータベースでも同じように利用できます。例えば、キャラ用のプレファブが1つあり、キャラの情報が登録されているスクリプタブル・オブジェクトがあれば
このキャラのプレファブから、キャラ用のデータベースに登録されているすべてのキャラを作り出し、そのように振る舞わせることが出来ます。


2.EnemyController スクリプトを修正し、EnemyDataSO スクリプタブル・オブジェクトのデータを参照して反映する処理を追加する


 設計に基づいて、インスペクターで設定していた情報を EnemyDataSO スクリプタブル・オブジェクトの EnemyData クラスの情報を参照するように変更します。

 まずは EnemyData クラスの情報を扱えるように、EnemyData 型の変数を用意します。この変数に、エネミーとして必要なすべてのデータが入ることになります。
hp、攻撃力、そしてエネミーの画像の情報などが入っていますので、このデータを利用することで「どのようなエネミーとして振る舞うか」決まります。
この変数を利用することによって、EnemyData クラスの情報を参照して利用出来るようになります。

 この EnemyData クラスの情報は、外部のスクリプトから引数で受け取るように設計します。
いままで SetUpEnemy メソッドで bool 型のボスの有無の情報を受け取っていましたが、これを削除して、代わりに EnemyData 型を宣言して、ここでエネミーのデータを受け取れるようにします。

 ここで大切なポイントがあります。
 EnemyData クラスの情報を決定するのは、EnemyController スクリプト側ではありません
EnemyController スクリプトでは、引数として届いた EnemyData クラスの情報を利用して、設定を行うことで、エネミーのデータを反映してエネミーに役割を与えることが仕事です。

 生成されたエネミーのクローンに対して、エネミーのデータを決定し、「この情報を使ってエネミーに役割を与えて振る舞いを変えてください」と命令を出すのは EnemyGenerator スクリプト側になります。

 この役割分担の考え方についてもしっかりと学習して理解を深めてください。



 よって、この EnemyData クラスに含まれている情報については、このスクリプト内で個別に設定を行う必要がなくなります。
対象となる変数は hp 変数と attackPower 変数です。このうち、attackPower 変数については後程変更を行います。
何故ならば、この変数は DefenseBase スクリプトにおいて参照して利用されている値であるため、この値を修正したり削除してしまうと、DefenseBase スクリプトにエラーが出てしまうからです。

 このように変数を修正したり、削除する場合には、変数を管理してるスクリプト内だけではなく、外部のスクリプトにおいても利用されていないかを確認してから修正してください。

 EnemyData クラスには、エネミーの種類の情報も含まれている。EnemyType という enum がそれに当たります。
この情報を参照すれば、生成されたエネミーがボスなのか、あるいはノーマルなのか、判断をすることが可能です。
そうなると、いままでボス判別用に利用していた isBoss 変数も不要になります。合わせて、この変数を利用していた条件式などを修正する必要があります。

 EnemyData クラス内の変数はすべて public 修飾子によって宣言されています。そのため、EnemyData クラスの情報を取得している変数からは
これらの public 修飾子の変数の情報をすべて利用できます。
例えば、EnemyType を参照したい場合には以下のようになります。

  // EnemyData クラスに登録されている EnemyType の情報が、Boss の列挙子である場合
  if(enemyData.enemyType == EnemyType.Boss) {  }


 以前に学習したように、変数に代入されている情報からは、public 修飾子の情報をさらに取得して利用できます。
ピリオドを使った処理について、この機会にしっかりと覚えていきましょう。


EnemyController.cs


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



 Prefabs フォルダにある EnemySet ゲームオブジェクトのインスペクターを確認します。
新しく public 修飾子と SerializeField 属性で宣言した変数が追加され、不要な変数が削除されています。


EnemySet ゲームオブジェクト インスペクター画像




3.EnemySet ゲームオブジェクトの設定を行う


 Prefabs フォルダにある EnemySet ゲームオブジェクトを選択して、インスペクターの一番上にある Open Prefab ボタンを押して、プレファブの編集モードにしてください。

 新しく追加された変数のうち、enemyData 変数は何も設定をしないでください
この部分に、EnemyGenerator スクリプト側から届いた EnemyData の情報が自動的に入ります
そうすることによって、このデータを利用して、エネミーの役割、振る舞いが自動的に設定されます


EnemyData




 imgEnemy 変数には、同名のゲームオブジェクトをドラッグアンドドロップしてアサインしてください。
Image コンポーネントの情報が登録されます。この変数を利用して、EnemyData クラスに登録されている画像の情報を利用し、
エネミーの画像を生成されるたびに、自動的に差し替えるようにします。


EnemySet ゲームオブジェクト インスペクター画像



 以上で設定は完了です。


6.EnemyGenerator スクリプトを修正し、エネミー用のプレファブに EnemyDataSO スクリプタブル・オブジェクトのデータを参照して、1つのプレファブから異なるエネミーを自動生成する処理を追加する

1.設計


 EnemyController スクリプトの修正が終わり、EnemyDataSO スクリプタブル・オブジェクトのデータを反映して利用できる準備が完了しました。

 つづいて、EnemyGenerator スクリプト内に EnemyDataSO スクリプタブル・オブジェクトのデータを扱えるようにし、
そのデータを利用して、生成したエネミーのクローンにエネミーのデータを送り渡す処理を修正・追加を行います。

 前の設計で説明したように、スクリプタブル・オブジェクトも作成しただけでは利用できません
利用するためには EnemyDataSO 型の変数を宣言し、その変数にスクリプタブル・オブジェクトを代入して、参照して利用していく方法になります。

 EnemyGenerator スクリプトにスクリプタブル・オブジェクトの情報を扱えるということは、
このスクリプトにおいて生成したエネミーのクローンに対して、エネミーとしての振る舞いを決定する部分までが実装される設計になります。
そしてそのエネミーの情報を EnemyController スクリプト側に渡すことで、EnemyController スクリプトがエネミーの情報を設定してくれる処理の流れになっています。

 そのためには事前準備として多くの処理の実装が必要になります。

<ロジックの流れ>
◇1.エネミー用のデータを、ノーマルのエネミーのデータだけが抽出されている List とボスのデータだけが抽出されている List の2つに分けて、新しく作成するための変数を用意する
◇2.EnemyDataSO スクリプタブル・オブジェクトを利用して、【1】の List をぞれぞれ作成し、変数に代入する
◇3.エネミーの生成にあたり、ノーマルのエネミーを生成する場合には、ノーマルのエネミーのデータの List、ボスのエネミーを生成する場合には、ボスのエネミーのデータの List を使う分岐を作成する
◇4.【3】の List の中からランダムなエネミーのデータを1つ選択して、それを生成したエネミーのクローンの「エネミーの情報(EnemyData)」として決定する
〇5.エネミーのプレファブからクローンの生成を行う
△6.EnemyController スクリプトの SetUpEnemy メソッドに【4】で決定したエネミーの情報を引数として渡す
〇7.EnemyController スクリプトの SetUpEnemy メソッドにおいて、引数で取得したエネミーの情報を利用して、エネミーとしての振る舞いを設定する

 ◇の部分は新しく実装する変数や処理、〇は実装済の処理、△は修正を行う処理になります。

 まず最初に◇1のために、EnemyData 型の List の変数を2つ用意しておきます。
EnemyDataSO スクリプタブル・オブジェクトにはすべてのエネミーのデータが登録されていますが、
実際に利用する際には、利用実態に合わせて必要な情報を作成して運用していくことが必要になります。
つまり、スクリプタブル・オブジェクト自体はゲームのマスターデータとして利用し、ゲーム内ではそれを上手く活用して運用していくという形式になります。

 今回の場合、エネミーを生成するたびに毎回処理をする、すべてのエネミーのデータの中から特定のエネミーのデータを探す、という処理を
よりコンパクトに、必要な情報の中からだけ検索して利用できるようにするために、それぞれのエネミーの種類を特定した List を作成しています。

 現在はボスか、それ以外のエネミーのデータしかありませんので、この2つに分類して List を用意しておきます。
そうすることよって、ボスを生成する際には、スクリプタブル・オブジェクトをすべて検索してボスを見つけるのではなく、
ボスのデータのみが入っている List から検索を行うようにする設計になっています。



 続いては、この2つの List に対して、スクリプタブル・オブジェクトのデータを抽出して
ノーマルの List にはノーマルタイプののエネミーのデータのみが代入される処理を考えます。
ボスも同様です。

 抽出方法は様々ありますが、今回は抽出処理専用のメソッドを用意し、引数にEnemyType を指定することによって、
指定した EnemyType の情報のみを抽出して List を作成するメソッドを用意しておきます。

 このメソッドには戻り値を設定し、指定した EnemyType の情報のみが List の形で取得できるようにして、各 List の変数に代入出来るようにします。
引数で指定した EnemyType に合わせて List が取得できるため、汎用性が高い処理になります。

 この手順はゲームスタート後にエネミーを生成する前に終了しておくべき処理になりますので、
Start メソッド内にて実行して、エネミーの生成前に事前に List を2つ用意しておきます。



 エネミーの生成にあたっては、ノーマルのエネミーであるのか、あるいはボスのエネミーであるのかによって生成までの過程を分岐します。
これはいままで GenerateEnemy メソッドの引数において isBoss 変数という値を利用して分岐していましたが、
EnemyData には EnemyType 情報があり、この中には Boss という情報がありますので、この EnemyType の指定を引数で受け取るように処理を変更し、
メソッド内の処理を分岐するように設計します。

 このとき、EnemyType がノーマルのタイプである場合には、ノーマルのエネミーのデータのみが抽出されている List から
ランダムなエネミーのデータを1つ選択して、今回生成するエネミーの情報(EnemyData)として決定します。

 ボスも同様の処理を行います。



 その後、エネミーの生成の手順になります。この部分は変わりません。

 最後に、生成されたエネミーのクローンのゲームオブジェクトより、EnemyController スクリプトの情報を取得し、SetUpEnemy メソッドを実行する部分の
引数の指定を bool 型から EnemyData へと変更します。このメソッドはすでに修正済ですので、この修正によって、EnemyGenerator スクリプトにおいて決定した
エネミーの情報が EnemyController スクリプト側へと引数を通じて送信されることになります。



 以上がロジックの流れになります。
 スクリプタブル・オブジェクトのデータを抽出して List を作成したり、作成にあたってメソッドの引数と戻り値を活用するなど、
今まで以上にプログラムの処理の理解を深めていくことが重要になります。


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


 設計に基づいて処理のロジックを組んでいきます。
読んでいただいてお分かりに用に、非常に多くの処理の修正と追加があります。

 スクリプタブル・オブジェクトのための変数の宣言、スクリプタブル・オブジェクトの情報を利用して、エネミーのデータをノーマルタイプの情報とボスタイプの情報に分ける処理、
生成する際にエネミーのタイプに応じてエネミーのタイプごとに分けて作成したリストからランダムにエネミーのデータを抽出して利用する処理、など
今までに利用してきた処理ではなく、もっと複雑な処理がつながって1つの処理になっています。

 設計で提示したロジックの流れと実装していく内容を1つずつ確認しながら、処理の内容を理解していきましょう。


EnemyGenerator.cs

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


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

 EnemyGenerator ゲームオブジェクトのインスペクターを確認します。
新しく public 修飾子で宣言した変数が追加されています。


EnemyGenerator ゲームオブジェクト インスペクター画像



3.処理を1つずつ読み解いていく


 今回追加された処理は、switch 文と enum の EnemyType を組み合わせた処理、
戻り値を持つメソッドを利用して List を作成したり、色々な新しい処理がたくさんあります。

 順番に1つずつ処理を見直して、どのような内容になっているかを読み解いてみてください。

 例えば、戻り値の処理はどのように動いているのか、メソッドを追いかけて読んでください。

  // EnemyType が Normal のEnemyData だけを List に代入する
    normalEnemyList = GetEnemyTypeList(EnemyType.Normal);
   ↓ こちらのメソッド処理結果が代入される。何故ならば、このメソッドは戻り値を持つメソッドであるため
    /// <summary>
    /// 引数で指定されたエネミーの種類のListを作成し、作成した値を戻す
    /// </summary>
    /// <param name="enemyType"></param>
    /// <returns></returns>
    private List<EnemyDataSO.EnemyData> GetEnemyTypeList(EnemyType enemyType) {

    // enemyType 変数には、EnemyType.Normal が引数として代入されている
    // まずは変数の値をしっかりと把握してから、以下の処理を読み解く

        // EnemyType が Normal の EnemyData だけが代入される List を用意する
        List<EnemyDataSO.EnemyData> enemyDatas = new List<EnemyDataSO.EnemyData>();

        // Normal タイプのエネミーのデータだけをスクリプタブル・オブジェクトより抽出してリストに追加する
        for (int i = 0; i < enemyDataSO.enemyDataList.Count; i++) {

      // 1つずつ EnemyData 内の情報にある EnemyType を確認し、それが Normal の情報を持つ EnemyData である場合
            if (enemyDataSO.enemyDataList[i].enemyType == enemyType) {
 
        // 用意しておいた List に EnemyData を追加する。これにより、 EnemyDatas の List には EnemyType が Normal のEnemyData のみが抽出される
                enemyDatas.Add(enemyDataSO.enemyDataList[i]);
            }
        }

    // 抽出結果が代入されている List を処理結果として戻り値として戻す
        return enemyDatas;
    }
    ↓ 以上のことから、この1行の処理は、次のような処理になっている
  // 左辺と右辺の型が同じ List<EnemyDataSO.EnemyData> 型なので、代入処理が成立する
  normalEnemyList = GetEnemyTypeList(EnemyType.Normal);  => このメソッドの処理結果の List<EnemyDataSO.EnemyData> enemyDatas が戻ってくる

  // ボスの場合も同じ流れの処理

 このように、1つずつの処理を順番にしっかりと読み解いていくことが大切です。
 

4.<Listの使い方  Add メソッドー>


 List クラスは <T> にジェネリック型(任意の型)を指定して、同じデータ型をまとめて管理するコレクション機能を持つクラスです。
配列と異なり、要素を自由に追加・削除できます。(要素数が可変する)

List.Add(T型) メソッド

 Listの末尾に引数で指定した要素(データ)を追加します。
引数が T 型となっていますが、これは List を宣言した際に使った型が自動的に入ります。
今回は EnemyDataSO.EnemyData 型の List を宣言していますので、Add メソッドの引数には EnemyDataSO.EnemyData 型のみ指定できます。

 そのため、Listで宣言している型と同じ型であれば Add メソッドで List に要素を追加することが出来ます

 // EnemyType が Normal の EnemyData だけが代入される List を用意する
  List<EnemyDataSO.EnemyData> enemyDatas = new List<EnemyDataSO.EnemyData>();

 // 用意しておいた List に EnemyData を追加する。これにより、 EnemyDatas の List には EnemyType が Normal のEnemyData のみが抽出される
  enemyDatas.Add(enemyDataSO.enemyDataList[i]);

 追加された要素は、自動的に List の最後に順番に追加されていきます。
例えば、ballonList がまだ1つも要素がなければ、enemyDatas[0] として1つ目に要素が追加されます。
そのあとに Add メソッドが実行された場合には、enemyDatas[1] として2つ目に要素が追加されます。


5.EnemyGenerator ゲームオブジェクトの設定を行う


 EnemyGenerator ゲームオブジェクトのインスペクターより、EnemyDataSO 型の変数に
Datas フォルダにある EnemyDataSO スクリプタブル・オブジェクトをドラッグアンドドロップしてアサインしてください。
これで EnemyDataSO スクリプタブル・オブジェクトが登録されて、スクリプト内で利用できる状態になります。

 他の2つの List についてはそのまま(Size 0)で問題ありません。
ゲームを実行すると、この List にはそれぞれ、ノーマルタイプのエネミーのデータ、ボスのエネミーのデータだけが代入されます。
それを Debug するために public 修飾子にしています。正常に代入されることが確認できたら、後程 private 修飾子に戻します。


EnemyGenerator ゲームオブジェクト インスペクター画像



 以上で設定は完了です。


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


 すべての実装が完了しましたので、ゲームを実行して順番に処理の内容を確認していきます。

 まずはゲーム実行したら一時停止してください。
EnemyGenerator ゲームオブジェクトのインスペクターを確認し、Size が 0 になっていた
ノーマルタイプ用のエネミーの List と、ボスタイプ用のエネミーの List の内容を確認します。

 EnemyDataSO スクリプタブル・オブジェクトは現在、ノーマルタイプが2体、ボスが1体のデータが登録されていますので
この EnemyType に基づいて、それぞれの Size が 2 と 1 になって、値が代入されていれば制御成功です。
EnemyData の中身も確認し、ちゃんと EnemyType によって分けられているか確認しておきます。

 スクリプタブル・オブジェクトを元に、それぞれの EnemyType 用のエネミーのデータ List が完成しています。
もしもゲームを実行しても、これらの Size が 0 のままの場合、EnemyType による分離が出来ていないため、EnemyGenerator の処理を見直してください。

 スクリプタブル・オブジェクトを元にして作成した各エネミーの List からエネミーの情報を取り出す処理になっているため、
List の Size が 0 のままですと、生成されたエネミーのクローンのゲームオブジェクトに対して EnemyData を渡すことが出来ません。


<EnemyGenerator の各 List>




 List が問題なく作成されていたら一時停止を解除してゲームを進めてください。
時間の経過で自動生成されるエネミーは同じ EnemySet ゲームオブジェクトですが、それぞれの EnemyData に基づいたエネミーとして振る舞っていれば制御成功です。
画像だけではなく、Hp や 攻撃力などの値も確認してみてください。


<ヒエラルキー画像(すべて EnemySet プレファブのクローン)>



<EnemyController スクリプトの EnemyData の値がそれぞれ異なり、この情報を設定して異なるエネミーとして振る舞っている>






<実装動画 .痢璽泪襪離┘優漾爾ランダムで自動で生成される>
動画ファイルへのリンク


<実装動画◆.椒垢離┘優漾爾自動で生成される>
動画ファイルへのリンク


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

 次は 発展4 −エネミーのデータを外部のスクリプトで参照する処理の実装− です。

コメントをかく


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

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

Menu



技術/知識(実装例)

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

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

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

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

レースゲーム(抜粋)

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

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

3D脱出ゲーム(抜粋)

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

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

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

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

VideoPlayer イベント連動の実装例

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

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

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

private



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

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