i-school - 2DタイルマップRPG 手順12
 今回は内容が新しいものばかりで、非常に深いものになっています。

 現在は NPC 用のゲームオブジェクトに直接表示させる情報を登録していますが、この方法ですと、NPC が増えた場合に対応が煩雑かつ、管理が難しくなります。
そのため、会話イベントなどのイベント用のデータを一元管理できるデータベースを用意し、そちらに会話イベントのデータを NPC 単位で登録しておき、
必要なデータをその都度参照して表示する方式に変更する方法があります。この手順ではそのデータベースの作成方法とデータを参照し、画面に反映する方法について実装を行います。

 手順としては2回に分けて実装を行います。


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


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

手順12 ーイベント用のデータベース作成ー
21.イベント用のデータベースとして利用するスクリプタブル・オブジェクトを作成する準備を行う −EventData スクリプトと EventDataSO スクリプトを作成するー
22.EventDataSO スクリプトを利用して EventDataSO スクリプタブル・オブジェクトを作成し、会話イベントのデータを登録する



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

 ・スクリプタブル・オブジェクトの作成とデータの登録
 ・クラス内に enum を作成する(入れ子構造)
 ・[System.Serializable(シリアライザブル)]属性
 ・List の初期化
 ・[Multiline(マルチライン)]属性



21.イベント用のデータベースとして利用するスクリプタブル・オブジェクトを作成する準備を行う −EventData スクリプトと EventDataSO スクリプトを作成するー

1.設計


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

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

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



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

 EventData クラスは、イベント1つ分のデータを1つにまとめている情報群です。
現在は NonPlayerCharacter スクリプトにおいて、NPC の名前や表示する文文字列の値を個別に用意していますが、これを1つのデータ群としてまとめて管理するためのクラスになります。

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

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

 まずは最初に EventData クラス内に、ゲームにおいて必要なイベントのタイプを登録する EventType を enum にて作成してから、
スクリプタブル・オブジェクト用のスクリプトを作成します。

 この情報はイベントの種類を設定するための enum です。今回は会話イベントのデータのみを登録しますが、
この EventType を利用することにより、会話以外のイベントも一緒に登録してゲーム内に利用できるような、汎用的な設計にしています


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


 イベントのデータを登録するためのクラスを作成します。

 先ほども説明しましたように、イベントには色々な種類がありますので、その情報を EventType という情報として登録できるようにしておきます。
これは1つのファイルとして作成することもできますが、今回は EventData のファイル内に一緒に作成して利用する設計にします。



 EventType は enum (イーナム) を利用して、イベントの種類を事前に登録し、この情報をイベントの持つ情報として EventData クラス内に設定できるようにします。

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

EventData.cs

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


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

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

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



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

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

<enum の列挙子のキャスト>
int  eventValue = (int)EventType.Talk;

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

<数字の代入の例(今回この方式は利用しません)>
EnemyType.cs
public enum EventType {
    Talk = 10,
    Search = 5,
}

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


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


 スクリプタブル・オブジェクトを作成するために必要な EventDataSO スクリプトを作成します。
スクリプタブル・オブジェクト専用の ScriptableObject クラスを継承し、[CreateAssetMenu] 属性を記述することで作成可能になります。
 
 スクリプタブル・オブジェクトでは、List の機能を利用することで、指定したデータを複数のデータとしてまとめて管理することが出来ます。
そのため、データベースとしての役割を果たすことが可能になっています。

 今回指定して管理したいデータはイベントのデータです。
そのため、EventData 型の List を作成して、管理を行える状態として作成を行います。



EventDataSO.cs

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


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


4.EventData スクリプトと EventDataSO スクリプトの構造について


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

 1.イベント1つ単位でのデータ(イベントの種類、イベントの通し番号、タイトル(NPC の名前など)、表示する画像などのデータ群)
 2.イベントのデータをまとめる List(リスト)


1.イベント1つ単位でのデータ(イベントの種類、イベントの通し番号、タイトル(NPC の名前など)、表示する画像などのデータ群)

 NonPlayerCharacter スクリプトにて管理していた NPC の名前の情報やメッセージの情報を個別の変数ではなく、
1つのデータ単位として管理できるように、 EventData クラスとして作成して、こちらにて管理を行うようにします。
利点は、1つの EventData クラス内には1つ分のイベントの全データが登録できることです。
そのため、名前用、メッセージ用というように変数を個別に作る必要はなく、EventData の 名前の情報、EventData のメッセージの情報という形で EventData を参照して利用できる部分です。
例えば、EventData.title と記述すれば、それはその EventData クラスに登録されている Title の値を参照することになります。

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

[System.Serializable]
public class EventData

    public EventType eventType;    // イベントの種類
    public int no;                 // 通し番号
   public string title;           // タイトル。NPC の名前、探す対象物の名前、など

   [Multiline] 
   public string dialog;          // NPC のメッセージ、対象物のメッセージ、など
   public Sprite eventSprite;     // イベントの画像データ
}

 このようにイベント単位で1つ分に必要になるデータをクラスとしてまとめておくことで管理と利用が容易になります
また、イベントの情報を増やしたい場合には、この EventData クラス内に 型と変数を追記すれば、好きなだけ増やすことも出来ます。


2.イベントのデータをまとめる List(リスト)

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

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



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

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

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

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

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


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



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

<EventData クラスを扱う List>
    public List<EventData> eventDatasList = new List<EventData>();


5.<クラス内に enum を作成する(入れ子構造)>


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

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

 使用方法は他のクラスと同じです。参照する場合は、EventData.EventType という書式で、入れ子構造になっているクラス名の後にピリオドを打って、入れ子クラス/ enum を記述します


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


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

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


7.[Multiline(マルチライン)]属性


 SerializeField属性 や public 修飾子で宣言した string 型の変数は、インスペクターにおいて文字列を入力することが出来ます。
このとき入力できる文字列は1行だけに固定されていますが、Multiline 属性を付与した変数の場合には、この文字入力できる行数を複数行のフィールドに設定してくれる属性になります。

 行数に指定がない場合には自動的に3行分が入力可能になります。引数に数字を設定すると、その数字分の行数が入力できるフィールドが用意されます。

 この中で改行を行って文字列を入力することで、その改行の情報をそのまま利用してゲーム内に文字列を表示させることが出来ます。


インスペクター画像



 上記の画像をみていただくと一目瞭然です。
Tilte 変数も Dialog 変数も同じ string 型ですが、入力できるフィールドの幅が違うことが分かります。


参考サイト
Unity公式マニュアル
MultilineAttribute
https://docs.unity3d.com/ScriptReference/Multiline...
エクスプラボ 様
【Unity】MultilineAttributeを使ってInspectorで複数行テキストを扱う
https://ekulabo.com/multiline-attribute
Nanashi-soft 様
◇Unityでゲーム開発 -C#で文字列操作-
https://yun.cup.com/unity039.html


22.EventDataSO スクリプトを利用して EventDataSO スクリプタブル・オブジェクトを作成し、会話イベントのデータを登録する

1.設計


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


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


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

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


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


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



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

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

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


<フォルダ管理>



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


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


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

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


インスペクター画像



 まずは会話用のイベントのデータを3つ分登録しておきたいと思います。
EventDatasList 変数の Size を 3 に変更してください。Element 0 〜 2 が下に作成されます。

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

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

 試しにこの属性をコメントアウトしてみてください。EventData が一切インスペクターに表示されなくなり、編集できない状態になります。



 下記の画像のように設定を行ってみてください。
EventSprite 変数には画像を登録できますが、今はまだ活用していませんので None のままで構いません。


インスペクター画像



 各数値や画像は任意ですが、No の値だけは、各イベントの種類ごとに異なる番号で設定してください。できれば 0 から連番が理想です。
この番号はイベント用の個体番号として利用する可能性がありますので、同じ番号を重複して設定してしまうと、同じ番号の個体が複数存在することになり、
通し番号によってイベントを特定することが出来なくなります。例えば、Talk の種類の 0 と、Search の種類の 0 を混同させることは可能です。
ですが、 Talk の種類に 0 を2つ以上登録することはやめてください。


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


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

 下記のように書きます。

  EventData.EventType eventType;

  eventType = EventData.EventType.Talk;

 これは次の手順から利用します。



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

 次は 手順13 ーイベント用のデータベースの登録ー です。