i-school - 3Dレールガンシューティング 手順18
 武器のデータベースを作成します。この機能を作成しておくことにより、使用している武器の特定、武器の切り替えといった機能が実装可能になります。

 以下の内容で順番に実装を進めていきます。
今回は内容が新しいものばかりで、非常に深い知識・技術を得られる内容になっていますが、当然、難しい内容にもなっています。

手順18 −武器用のデータベース作成−



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

 ・スクリプタブル・オブジェクトの作成とデータの登録

以前学習した内容の復習もありますので、ここでしっかりと理解を深めておきましょう。

 ・enum だけのスクリプト・ファイルの作成
 ・クラス内に別のクラスを作成する(入れ子クラス)
 ・[System.Serializable(シリアライザブル)] 属性
 ・List の初期化
 ・入れ子クラスを外部のクラスで宣言する方法



スクリプタブル・オブジェクト作成のための準備を行う

1.設計


 エネミーやアイテムなどのデータなどを扱う場合に、複数のデータを1つのまとまりとして管理できるデータベースのようなものがあると扱いが楽になります。

 Unityにはスクリプタブル・オブジェクトという機能(こちらはアセットとしてデータベースを扱う方法)がありますので、今回はこの機能を利用していきます。

 スクリプタブル・オブジェクトを作成するためには、専用のスクリプトを作成する必要があります。その作成方法を学習します。

 今回作成するスクリプタブル・オブジェクトは武器のデータを管理する目的で作成を行います。
そのため、スクリプト内には、武器のデータをまとめるための WeaponData クラスを用意します

 WeaponData クラスは、武器1つ分のデータを1つにまとめている情報群です。
現在の武器は固定値として、PlayerController クラス内に弾数や攻撃力といった値を個別に用意していますが、
これを1つのデータ群としてまとめて管理するためのクラスが WeaponData クラスになります。

 この WeaponData クラスは、ゲームに登場する武器の数だけ用意することになりますので、それを管理するために List 機能を利用します。

 この2つの情報を管理して完成するのがスクリプタブル・オブジェクトになります。
どのような構成になっているかはスクリプト作成後に説明をしていますので、そちらをしっかりと学習してください。

 なお、今回は利用していませんが、武器の種類(タイプ)を登録するための WeaponType を enum にて作成してもよいでしょう。


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


 参考用に、武器の種類を登録しておくための enum の作成方法を提示します。
手順11でも学習していますので、復習として作成しておくといいでしょう。

 enum (イーナム)を利用して、武器の種類を事前に登録し、この情報を武器の持つ情報として WeaponData クラス内に設定できるようにします。
enum のみでスクリプトを作成する場合、using の宣言や、MonoBehaviour(モノビヘイビア) クラスの継承は不要です
そしてどのスクリプトからでも 変数の代入なしで利用可能 になります。

 enum ではゲーム内に登場させたい種類の情報を、列挙子(れっきょし)という形で種類を作成できます。
今回は武器の種類という情報を WeaponType という名前で作成し、その中に 武器の種類 を登録しておきます。
これは追加可能な情報ですので、先々に 武器の種類が増えても対応できます

WeaponType.cs

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


 スクリプトを作成したらセーブします。



 3つ以上の情報を管理する場合には、enum でその種類を登録しておくことをおすすめします
enum を利用する場合、その登録してある列挙子からしか情報を指定できませんので、
例えば、文字列と異なり、指定に際して打ち間違えが発生しませんので、不備の値が入ることも防ぐことが出来ます。

 ゲームの内容に応じた enum を考えて作成して運用します
プレイヤーの状態用(毒、混乱、痺れとか)、アイテムの種類(消耗品、武器、防具、など)、
ゲームの状態管理(ゲーム開始前、ゲーム中、ゲーム終了)など、非常に応用が利く機能です。



 なお enum では各列挙子に自動的に整数の番号が与えられます 一番上から 0 で連番になっています
今回の場合であれば、Normal には 0、Fire には 2 の数字が与えられています。

 この番号は見えない情報ですが、列挙子を int 型にキャストを行うことで取得して利用出来ます
下記の例の場合、enumValue には 1 が代入されます。

<enum の列挙子のキャスト>
int  enumValue = (int)WeaponType.Freeze;

また、列挙子の宣言時に数字を指定して代入することも可能です。その場合には連番ではなく、指定した数値を取得出来ます。

<数字の代入の例(今回この方式は利用しません)>
WeaponType.cs
public enum WeaponType {
    Normal = 10,
    Freeze = 5,
    Fire = 100
}

 上記のように代入されている場合には、列挙子を int 型にキャストすると、代入してある値が取得出来ます。
今回は数字の代入は行っていませんので一番上の列挙子には 0 から順番に採番されています。


3.WeaponData スクリプトを作成する


 武器のデータを1つにまとめて管理するための情報群として、WeaponData クラスを作成します。
ここに含まれる変数が、武器1つあたりの保有するデータ群になります。


WeaponData.cs

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


 スクリプトを作成したらセーブを行います。


4.WeaponDataSO スクリプトを作成する


 スクリプタブル・オブジェクトを作成するために必要な WeaponDataSO スクリプトを作成します。
スクリプタブル・オブジェクト専用の ScriptableObject クラスを継承し、[CreateAssetMenu] 属性を記述する ことで作成可能になります。

 スクリプタブル・オブジェクトでは、指定したデータを複数のデータとしてまとめて管理することが出来ます。
そのため、データベースとしての役割を果たすことが可能になっています。

 今回指定して管理したいデータは武器のデータです。
そのため、スクリプタブル・オブジェクト内に必要な情報は以下の2つです。

 1.武器1つ単位でのデータ(弾数、攻撃力、画像などのデータ群)を扱うクラス = WeaponData クラス
 2.武器のデータをまとめる List(リスト) = WeaponDataSO クラス

 【1】の WeaponData クラスについては作成が終了していますので、【2】のクラスを作成します。

 まずは最初にスクリプトを記述してから、内容を確認しましょう。



WeaponDataSO.cs

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


 スクリプトを作成したらセーブします。


5.スクリプタブル・オブジェクトの構造について


処理の内容について、順番に確認していきます。

 1.武器1つ単位でのデータ(弾数、攻撃力、画像などのデータ群)を扱うクラス = WeaponData クラス
 2.武器のデータをまとめる List(リスト) = WeaponDataSO クラス


1.武器1つ単位でのデータ(弾数、攻撃力、画像などのデータ群)を扱うクラス = WeaponData クラス

 PlayerController スクリプトにて管理していた武器の弾数や攻撃力を個別の変数ではなく、
1つのデータ単位として管理できるように、 WeaponData クラスとして作成して、こちらにて管理を行うようにします。
利点は、1つの WeaponData クラス内には1つ分の武器の全データが登録できることです。
そのため、弾数用、攻撃力用というように変数を個別に作る必要はなく、WeaponData の bulletPower、WeaponData の reloadTime という形で WeaponData を参照して利用できます
例えば、WeaponData.bulletPower と記述すれば、それはその WeaponData クラスに登録されている bulletPower の値を参照することになります。

 ここからはピリオドによる参照処理が増えていきますので、しっかりと処理を読み解いていきましょう

[System.Serializable]
public class WeaponData {
    public string weaponName;
    public int weaponNo;
    public int maxBullet;
    public float reloadTime;
    public int bulletPower;
    public float shootInterval;
    public float shootRange;
    public Sprite weaponIcon;
}

このように武器1つ分に必要になるデータをクラスとしてまとめておくことで管理と利用が容易になります
また、武器の情報を増やしたい(効果範囲や、武器の種類・属性など)場合には、この WeaponData クラス内に 型と変数を追記すれば、好きなだけ増やすことも出来ます。


武器のデータをまとめる List(リスト) = WeaponDataSO クラス

 WeaponData クラスにはぶきの情報をまとめて登録できるようにしました。
このデータは武器1つ分ですので、もしも複数の武器のデータを用意して登録したい場合、
この WeaponData クラスを複数用意して、それを1つに束ねて管理するための変数が必要になります。

 こういった1つの同じデータ群をまとまったものをコレクションといいます。
C# にはコレクションを管理する方法として、Dictinary(ディクショナリー)List(リスト) があります。



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

 List を利用する場合には配列と同様に初期化が可能ですが、Listでは初期化時に要素数の宣言が不要です

<配列の初期化>
  WeaponData [] weaponDatas = new WeaponData[3];       // <=  要素数の宣言が必要

<List の初期化>
  List<WeaponData> weaponDatasList = new List<WeaponData>();   // <=  要素数の宣言が不要

 そのため基本的には、予め要素数の確定しているデータを扱う場合には配列を、要素数が未確定であったり可変長であるデータについてはListを利用するように考えてください。


参考サイト
.net column様
【初期化の方法】C#で配列やリストを初期化するには?
https://www.fenet.jp/dotnet/column/language/713/



 public 修飾子にて List を宣言することで、インスペクター上でサイズの変更が可能です
例えば武器のデータを3つ分作って登録したい場合には、インスペクターで List のサイズを 3 に設定すれば
WeaponData クラスが 3 つ、Element 0 〜 Element 2 として作成されますので、ここに武器のデータを1つずつ、合計3つ分登録することが出来ます。

<WeaponData クラスを扱う List>
    public List<WeaponData> weaponDatasList = new List<WeaponData>();


6.<クラス内に別のクラスを作成する(入れ子クラス)>


 今回は WeaponData クラスと WeaponDataSO クラスとを別々のスクリプトに記述していますが、
以前にも学習している入れ子構造を活用することで、1つのスクリプト内に2つのクラスを作成することも出来ます。
現在であれば、RailPathData クラスで利用している方法になります。

<入れ子構造時の作成例>
WeaponDataSO.cs

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


 WeaponDataSO クラスの宣言フィールドにおいて、別のクラスを作成しています。今回は WeaponData クラスです。
このようにC#では、1つの独立したクラス(ファイル)としてではなく、あるクラスの中に別のクラスを作成しても使用することができます。
このような構造を入れ子(ネスト)クラスと言います。

 特定のクラスでのみ使用することが確定しているような、使用範囲の狭いクラスであれば、
このように入れ子クラスにした方がスクリプト・ファイルが増えずに済みます。
 また設計上、ファイルにはしたくない(隠しておきたい)クラスを作成する場合にも用いられます。

 使用方法は他のクラスと同じです。参照する場合は、WeaponDataSO.WeaponData という書式で、入れ子クラスのあるクラスの後に、入れ子クラスを順番に記述します


7.[System.Serializable(シリアライザブル)]属性


 入れ子になっているクラスの1行上には上記の宣言があります。[ ]で宣言された設定値は「属性」と呼ばれる情報になり、特別な意味を持ちます。

 今回利用している[System.Serializable]属性は、Systemに含まれている設定値であり、こちらを宣言することで入れ子クラスの情報をインスペクターに表示することが出来ます。
これを書き忘れてしまうと、インスペクターに WeaponData が表示されず、データをインスペクターから登録することが出来ません
using System; を宣言している場合には [Serializable] とだけ記述すれば適用されます。
宣言していない場合には [System.Serializable] と記述する必要があります。


WeaponDataSO スクリプタブル・オブジェクトを作成する

1.設計


 WeaponDataSO スクリプトを元に WeaponDataSO スクリプタブル・オブジェクトを作成します。
WeaponDataSO スクリプトに用意してある WeaponData 型の List である weaponDatasList 変数がデータベースの役割を持っています


2.<クラスのリスト化によるデータベース作成>


 WeaponData クラスは1つのデータ情報を扱うことが出来ます。今回であれば武器1つ分の情報です。
そのため複数の武器の情報をまとめて扱う必要がある今回のような場合には、その分だけ WeaponData クラスを追加して作成しなければなりません

 それらを管理するために WeaponData 型の List を作り、まとめて管理を出来るようにしています。
ここで大切なことは、1つ1つの別の変数に個別に WeaponData が存在していたのではまとめて管理していることにはなりません
WeaponData のリストとはすなわち、WeaponData をまとめて扱っているデータの集合体になりますので、ここにデータベースとして役割を成立させることが出来ます


3.WeaponDataSO スクリプタブル・オブジェクトを作成する


 最初に、スクリプタブル・オブジェクトを管理するためのフォルダを作成しておきます。
Project 内で右クリックをしてメニューを開き、Datas フォルダを作成してください。
この中に作成されたスクリプタブル・オブジェクトを入れて管理します。



 Unity の左上のメニューより、Assets => Create => Create WeaponDataSO を選択します。
新しく WeaponDataSO というファイルが作成されます。名前はそのままで構いません。

 このアイコンの形が違うファイルがスクリプタブル・オブジェクトになります。
これはアセットとして取り扱われるようになる情報です。

 WeaponDataSO スクリプタブル・オブジェクトを Datas フォルダへ移動してください。
今後もスクリプタブル・オブジェクトを作成したら、Datas フォルダ内で管理するようにします。


<フォルダ管理> 



 早速スクリプタブル・オブジェクトを活用して、武器のデータを登録していきましょう。


4.WeaponDataSO スクリプタブル・オブジェクトの設定を行う


 作成された WeaponDataSO スクリプタブル・オブジェクトを選択してインスペクターを確認します。
WeaponDataSO スクリプトにて宣言した weaponDatasList 変数がインスペクターに表示されて、 Sizeが 0 になっています。
これがスクリプタブル・オブジェクトの中身です。

 Size に任意の数を入力すると、同数の Element が作成されます。これが List で管理する WeaponData クラスの情報群になります。


インスペクター画像



 まずは武器のデータを3つ分登録しておきたいと思います。
weaponDatasList 変数の Size を 3 に変更してください。Element 0 〜 2 が下に作成されます。

 Element とは List の要素(中身)のことです。
そのため、Element 1つが WeaponData 1つになります。Element の番号は 0 から始まります。

 以上のことから、1つの Element には1つの WeaponData クラスの内容を設定できるようになっています。
このとき、WeaponData クラスの上に [Serializable] 属性を宣言しているので、WeaponData クラスの内容がインスペクターに表示されています。
[Serializable] 属性を活用することによって、インスペクターから武器の情報を1つずつ、WeaponData 単位で登録出来るようになっています。



 下記の画像のように設定を行ってみてください。
画像は Textures フォルダにある武器用の画像ファイルをドラッグアンドドロップしてアサインしてください。
もしも3つの画像がない場合には、以前にダウンロードしてある武器用の画像をいくつか選択して、新しくインポートして利用してください。


インスペクター画像



 各数値や画像は任意ですが、weaponNo の値だけは異なる番号で設定してください。できれば 0 から連番が理想です。
この番号は武器用の個体番号として利用する可能性がありますので、同じ番号を重複して設定してしまうと、同じ番号の武器が複数存在することになり、
番号によって武器を特定することが出来なくなります。


5.<入れ子クラスを外部のクラスで宣言する方法>


 WeaponDataSO スクリプト内に入れ子クラスとして作成している WeaponData クラスを外部のクラスで用いる場合には、入れ子の親クラスを記述した上で宣言するようになります。

 下記のように書きます。

  WeaponDataSO.WeaponData weaponData;

  WeaponDataSO.weaponDatasList[index].bulletPower;



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

 => 次は 手順19 ー使用している武器の情報の管理機能ー です。