高度なトピック

Tutorial

·

intermediate

·

+0XP

·

60 mins

·

(15)

Unity Technologies

高度なトピック

ここから先のセクションは、Game Kit のシステムを一歩掘り下げた内容となっているので、Unity についてのより高度な知識が必要になります。一部のトピックでは C# に関するある程度の理解が必要です。

Languages available:

1. 「高度なトピック」セクションについて

ここから先のセクションは、Game Kit のシステムを一歩掘り下げた内容となっているので、Unity についてのより高度な知識が必要になります。一部のトピックでは C# に関するある程度の理解が必要です。


2. RandomAudioPlayer(ランダム音声再生)

このスクリプトは、ユーザーによって割り当てられたオーディオクリップのリストから、サウンドをランダムに取得して再生できるようにするものです。Ellen プレハブでも各種サウンドの制御に使用されています(Ellen の子ゲームオブジェクト「SoundSources」の中を確認してみてください)。例えば、FootstepSource を見ると、設定された 4 種類の足音のリストからランダムに取得した音に、ピッチ(音の高さ)のランダム化処理を付加して再生していることが分かります。また特定のタイル用のオーバーライドも含まれています。Ellen が石のタイルの上を歩いた時の足音と草の上を歩いた時の足音は異なっています。 タイルマップ以外のゲームオブジェクトにオーバーライドを追加したい場合は、以下の「AudioSurface*」の項をご覧ください。


スクリプト


スクリプト側では、RandomAudioPlayer は PlayRandomSound 関数を持っており、サウンドの再生が必要な時に、この関数が別のスクリプトによって呼び出されます([例]足音は PlayerController 内の PlayFootstep 関数によってトリガーされますが、この関数自体は、足が地面と接触するフレームのアニメーションクリップによって呼び出されます。)


この関数は、オーバーライドサウンドを選択するために引数として TileBase を取ることができます([例]足音に現在プレイヤーの立っている面を使用したり、弾丸の衝突には衝突した場所のタイルを使用するなど)


AudioSurface


AudioSurface は、タイルマップ以外のすべてのゲームオブジェクトに追加できるスクリプトです。特定のゲームオブジェクトに、Audio Player でオーバーライドサウンドを再生するかを設定することができます。(例えば石のボックスなら、「Tile」オプションを「Alien」タイル⦅石の建造物のタイル⦆に設定した AudioSurface を使用すれば、上を歩いた時に通常の「地面」のサウンドではなく「石の足音」のサウンドがトリガーされるようになります。)


3. VFXController(エフェクト管理)

VFX プレハブをプールから生成し、生存期間が終了したら無効化してプールに戻すために使用されるコンポーネントです。



VFXController は Project フォルダー(Resource フォルダー内)にあるアセットで、ゲームが使用する全ての視覚エフェクトのリスト化を可能にします。使用する視覚エフェクトのインスタンスのプールを作成し、VFX プレハブの初期化コストが使用の度に発生しないように、それをゲームの開始時に移動させます。


その上で、PlayerControllerStateMachineBehaviourTriggerVFX などのスクリプトが、そういった視覚エフェクトを名前によってトリガーします。


Audio Player 同様、VFXController もタイルごとにオーバライドを設定できます。例えば、地面(床)の種類に応じて、足の着地に使用する視覚エフェクトのオーバライドを設定することができます。


本プロジェクト内での VFXController の使用例としては、DustPuff VFX が、プレイヤーが石の上を歩く時にオーバーライドを使用しています。


オーバーライドするタイルは、視覚エフェクトをトリガーするスクリプトによって提供されます(例:足の着地(Footstep)は現在触れている面を渡す、面に当たる弾丸はその面に設定されているタイルを渡す)。


オーバーライドタイルは、タイルマップ以外のゲームオブジェクトにも設定できます。ゲームオブジェクトがどのオーバーライドに対応しているか、またその設定方法に関しては、サウンドに関するドキュメント「Sounds.txt」をご覧ください。


4. データ・パーシステンス

データ・パーシステンス・システム(データの持続性に関するシステム)は、ゲームプレイ中のデータの保存(セーブ)を可能にし、プレイヤーが何を既に完了したかの情報を維持できるようにする仕組みです。


各 Zone が新しいシーンになるため、このシステムがなければ、特定の Zone に入った時にその Zone が「初期設定の」状態(エディター内で設定されたそのままの状態)で読み込まれ、プレイヤーがそれまでに獲得した鍵や武器などが取り消されてしまいます。


エディター内での使用


このデータシステムは、IDataPersister と呼ばれるスクリプトインターフェースを介して機能します。


このインターフェースを実装しているオブジェクト(ビルトインの InventoryItem、HubDoor、PlayerInput など)は、PersistentDataManager のデータの書き込みと読み出しを行えます。


このインターフェースを実装した MonoBehaviour には、インスペクター下部に Data Settings の折り畳みメニューが表示されます。この設定には以下が含まれています。


  • Data Tag ― ゲームオブジェクトの固有識別子です。そのゲームオブジェクトへデータをリンクするためにマネージャーによって使用されます。一部のビルトインコンポーネントは自動生成される固有識別子を使用しますが、手動で名前を入力することもできます("Zone_3_key" や "Quest_Item_Card" など)。

  • Persistence Type ― データの持続性(パーシステンス)のタイプには以下の 4 種類があります。

  • Don't Persist ― パーシステンスを無効化します。シーンの切り替わり時にリセットされなければならないゲームオブジェクトに役立ちます(例えばステージの再開時には扉は閉まった状態に戻すなど)。

  • Read Only ― このタイプに設定されたゲームオブジェクトは、データの読み出しは行えますが書き込みは行えません。使い方として、同じ Data Tag を持つゲームオブジェクトの Write Only(下記参照)版を作るのもひとつの方法です。この Read Only のゲームオブジェクトは、もう一方の Write Only のゲームオブジェクトがその Data Tag 用に書くデータを使用しますが、それを上書きすることはできません。

  • Write Only ― このタイプに設定されたゲームオブジェクトは、データの書き込みは行えますが読み出しは行えません。ひとつ上の「Read Only」の説明をご覧ください。

  • Read Write ― 最も一般的に使用されるタイプです。このタイプに設定されたゲームオブジェクトは、設定された Data Tag でデータの読み出しと書き込みを行えます。

データ保存と読み込みのサイクル


SaveData は、シーンの遷移前に、そのシーン内の IDataPersister の全てのインスタンスに呼び出されます。


LoadData は、新しいシーンの読み込み後に、そのシーン内の IDataPersister の全てのインスタンスに呼び出されます。


どの IDataPersister でも、PersistentDataManager.SetDirty(this) を呼び出すことで、いつでもデータを手動で保存できます。


コード内での使用例


データマネージャーをコード内で使用するには、それが有効化された(あるいは開始された)時に関連のデータを読み出して、それに続く処理を実行させます。


例えば、インベントリアイテムは、その状態(アクティブあるいは非アクティブ)を持続データに書き込みます。したがって、シーンが読み込まれるとそのインベントリアイテムは、そのタグに関連付けられたデータを取得します。false 値が保存された場合、そのゲームオブジェクトは既に取得されており、したがって無効化できるということになります。


もうひとつの例は、状態を保存する扉です。シーンが読み込まれてデータが読み出された際に、扉が指定の状態(開いた状態・閉まった状態など)に自動的にリセットすることができます。


5. SceneLinkedSMB

SceneLinkedSMB は、StateMachineBehaviour の機能を拡張するクラスです。これにより、アニメーターステートマシン内のステートの Behaviour が MonoBehaviour への参照を維持できるようになります。SceneLinkedSMB は、特定の MonoBehaviour を参照する必要がある場合にどこでも使用可能ですが、ロジックと機能を区別する目的をもって設計されたものです。


アニメーターコントローラーにはステートマシンが含まれています。これは実行フローの制御に非常に適しています。SceneLinkedSMB を使用すると、MonoBehaviour にパブリック関数への呼び出しを行うことで MonoBehaviour に関数を制御させることが可能となり、より簡単にシーンの参照が取得できるようになります。


このような仕組みで、SceneLinkedSMB がステートマシンの一部としてロジックを制御し、それらの関連付けられた MonoBehaviour が機能を制御するということが可能になっています。


SceneLinkedSMB の使用方法


SceneLinkedSMB を使って新しい Behaviour を記述する手順は以下の通りです。


  • 新しいクラスに SceneLinkedSMB を継承させてください。このクラスはジェネリックであるため、どのタイプの MonoBehaviour を持たせるか指定する必要があります。

(例)public class EnemyAttackState: SceneLinkedSMB<Enemy>


  • そのステートを使用しているアニメーターを持つすべてのゲームオブジェクトで初期化を行ってください。 上記の例の場合、Enemy クラスの Start() 関数に SceneLinkedSMB<Enemy>.Initialise(animator, this); を追加することもできます。

この例で用いている「animator」は、SceneLinkedSMB<Enemy> のあるステートマシンを持つアニメーターへの参照です。複数のステートに 10 種類の異なるスクリプトがあったとしても、必要な呼び出し回数は 1 回のみとなります。それらがすべて SceneLinkedSMB<Enemy> から継承されてさえいれば、すべて初期化されます。


  • 必要なステート関数をすべてオーバーライドしてください。SceneLinkedSMB に更なる関数が追加され、機能が実行されるタイミングをより具体的に制御できるようになります。クラス内で、そのクラスのジェネリックタイプ・パラメーターと同じタイプを持つ「m_MonoBehaviour」というprotected メンバーにアクセスできます。上記の例の場合、m_MonoBehaviour のタイプは Enemy になります。


Enemy.cs


public class Enemy: MonoBehaviour {
Animator animator;


void Start() {
animator = GetComponent<Animator>();
SceneLinkedSMB<Enemy>.Initialise(animator, this);
}


public void TrickerAttack() {
// ...
}
}


EnemyAttackState.cs


public class EnemyAttackState: SceneLinkedSMB<Enemy> {
public override void OnSLStateEnter (Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
// m_MonoBehaviour のタイプは Enemy です。
m_MonoBehaviour.TrickerAttack ();
}
}


6. Game Kit におけるオブジェクトプーリング

Game Kit は一部のシステムに、拡張可能なオブジェクトプーリングシステムを使用しています。以下の説明は、Game Kit を使用する上で必ず必要となる知識ではありませんが、目的に応じて拡張したい場合に役立つ内容となっています。


オブジェクトプーリングシステムを拡張するには、2 つのクラスを作成する必要があります。1 つは ObjectPool から継承し、もう 1 つは PoolObject から継承します。ObjectPool から継承するクラスはプール自体であり、PoolObject から継承するクラスはプール内の各プレハブのラッパーです。この 2 つのクラスはジェネリック型によって結び付けられています。ObjectPool および PoolObject は必ず同じ 2 つのジェネリック型を(ObjectPool から継承するクラス、PoolObject から継承するクラスの順で)持っている必要があります。以下はその分かりやすい例です。


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

このような形になるのは、プールがそれに含まれるオブジェクトの型を把握し、オブジェクトがその所属するプールの型を把握している状態にするためです。これらのクラスは 3 つ目のジェネリック型を持つ場合もあります。これは、PoolObject の WakeUp 関数のパラメーターを含めたい場合にのみ必要となります。この WakeUp 関数は、PoolObject がプールから取得された時に呼び出されます。例えばスペースシップのアクティベート時に燃料の残量を検知しなければならない場合は、以下のように float 型を 1 つ追加することもできます。


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

デフォルトでは PoolObject には以下のフィールド含まれます。


  • inPool ― この bool 型は、PoolObject が現在プール内にあるかアクティブであるかを表します。

  • instance ― このゲームオブジェクトは、この PoolObject がラップする、インスタンス化されたプレハブです。

  • objectPool ― この PoolObject が属するオブジェクトプールです。このクラスの ObjectPool のタイプと同じタイプを持っています。

PoolObject には以下の仮想関数も含まれます。


  • SetReferences ― PoolObject の初回作成時に呼び出されます。この関数の目的は、参照をキャッシュすることにより、PoolObject がアクティベートされる度にそれらを集める必要性をなくすことですが、それ以外の一時的な使用は可能になります。

  • WakeUp ― PoolObject がアクティベートされてプールから集められる度に呼び出されます。この関数の目的は PoolObject が使用される度に必要なすべての設定を行うことです。クラスに第 3 のジェネリックパラメーターが与えられた場合は、そのタイプのパラメーターで WakeUp が呼び出せるようになります。

  • Sleep ― PoolObject がプールに戻される度に呼び出されます。この関数の目的は、PoolObject が使用された後に必要なすべてのクリーニング実行することです。

  • ReturnToPool ― デフォルトでは単純に PoolObject をプールに戻しますが、追加的な機能が必要な場合はオーバーライド可能です。

ObjectPool は MonoBehaviour であり、したがってゲームオブジェクトに追加可能です。デフォルトでは以下のフィールドが含まれます。


  • Prefab ― プール作成のために複数回インスタンス化されるプレハブへの参照です。

  • InitialPoolCount ― Start メソッド内で作成される PoolObject の数です。

  • Pool ― PoolObject のリストです。

ObjectPool には以下の関数も含まれます。


  • Start ― 初回のプール作成が行われる場所です。ObjectPool 内に Start 関数があると、基底クラスの Start 関数が隠蔽されます。

  • CreateNewPoolObject ― PoolObject が作成されてその SetReferences を呼び出し、その後 Sleep 関数を呼び出す時に呼び出されます。仮想関数ではないのでオーバーライドは不可ですが、protected なので必要に応じて継承するクラス内で呼び出すことができます。

  • Pop ― プールから PoolObject を取得するために呼び出されます。デフォルトでは、inPool フラグが true に設定された PoolObject を検索して最初に検知されたものを戻します。true に設定されたものがなければ、新しく作成してそれを戻します。戻す PoolObject に WakeUp を呼び出します。これは仮想関数であり、オーバーライド可能です。

  • Push ― PoolObject をプール内に戻すために呼び出されます。デフォルトでは単純に inPool フラグを設定して PoolObject に Sleep を呼び出しますが、仮想関数なのでオーバーライド可能です。

オブジェクトプールシステムの使用例の全体は、BulletPool に関するドキュメンテーションおよびスクリプトをご覧ください。


BulletPool


BulletPool MonoBehaviour は BulletObject のプールであり、各 BulletObject が弾丸のプレハブをラップします。BulletPool は Ellen と敵の両方に使用されていますが、使われ方が若干異なっています。Ellen のほうは、親ゲームオブジェクトに BulletPool MonoBehaviour がアタッチされ、Prefab フィールドに Bullet プレハブが設定されています。一方敵のほうは BulletPool が EnemyBehaviour クラス内にあります。GetObjectPool 静的関数を使って、BulletPools を、頻繁に作成することなく使用できるようにしています。


BulletPool クラスには以下のフィールドが含まれます。


  • Prefab ― 使用したい弾丸プレハブを設定します。

  • Initial Pool Count ― 最初にプール内に作成される弾丸の数です。一度に使用する最大数をここに設定してください。より多く必要になった場合はランタイムで作成されます。

  • Pool ― プール内の BulletObject です。これはインスペクターでは表示されません。

BulletPool クラスには以下の関数が含まれます。


  • Pop ― プールから BulletObject 1 つを取得するために使用します。

  • Push ― BulletObject をプールに戻すために使用します。

  • GetObjectPool ― 特定のプレハブに対して適切な BulletPool を探す静的関数です。

弾丸は、1 つの BulletObject としてプールから取得されます。BulletObject クラスには以下のフィールドが含まれます。


  • InPool ― その特定の弾丸がプール内にあるか、または使用中であるかを表します。

  • Instance ― インスタンス化されたプレハブです。

  • ObjectPool ― その BulletObject が属する BulletPool への参照です。

  • Transform ― そのインスタンスの Transform コンポーネントです。

  • Rigidbody2D ― そのインスタンスの Rigidbody2D コンポーネントです。

  • SpriteRenderer ― そのインスタンスの SpriteRenderer コンポーネントへの参照です。

  • Bullet ― そのインスタンスの Bullet スクリプトへの参照です。

BulletObject には以下の関数が含まれます。


  • WakeUp ― BulletPool によって、その Pop 関数が呼び出された時に呼び出されます。

  • Sleep ― BulletPool によって、その Push 関数が呼び出された時に呼び出されます。

  • ReturnToPool ― 特定の弾丸についての処理が必要なくなったときに呼び出されます。BulletPool の Push 関数を呼び出し、その結果として Sleep 関数を呼び出します。

7. Behaviour Tree(ビヘイビアーツリー)

(注)これはスクリプト限定のシステムです。この機能および使用方法を理解するには、スクリプトの基礎知識と、Unity で C# が機能する仕組みの理解が必要です。


Behaviour Trees は、その名の示す通り、挙動(behaviour)をアクションのツリーを使って記述する方法です。Game Kit では敵の AI の挙動やボス戦闘のシーケンスの制御に使用されています。


以下は、Behaviour Tree を視覚的に示した例です。



理論


ゲームの毎回の更新中に、ツリーが「チェック」されます。つまりルートのすべての子ノードが一通りチェックされます。そのノードに子がある場合などは更に深くチェックされます。


各ノードにはそれに関連付けられたアクションがあり、それぞれが 3 つのステートのうち 1 つをその親に戻します。


  • Success ― ノードがそのタスクを正常に完了した場合です。

  • Failure ― ノードがタスクに失敗した場合です。

  • Continue ― ノードのタスクが未完了の場合です。

戻されたステートの使われ方は、ノードの親ごとに異なります。


(例)


  • *Selector は、現在の子が Failure または Success を返した場合は次の子をアクティブにし、Continue を返した場合は現在の子をアクティブに保ちます。*

  • *Test ノードは、テストが true だった場合はその子ノードを呼び出して子ステートを戻します。テストが false だった場合は子ノードを呼び出さずに Failure を戻します。*

Game Kit における実装


Game Kit 内では、Behaviour Tree はスクリプトを通して使用されています。以下はごく単純な Behaviour Tree の例です。


まずファイルのトップに using BTAI; を追加する必要があります。


[@portabletext/react] Unknown block type "code", specify a component for it in the `components.types` prop

aiRoot はクラス内にメンバーとして保存します。これは、Tree アクションを実行可能にするために Update 関数内で aiRoot.Tick() を呼び出す必要があるためです。


UpdateaiRoot.Tick() 内でどのように機能するか順を追って見てみましょう。


  • まず、関数「TestVisibleTarget」が true を戻すかどうかテストします。true を戻す場合は続けて子が実行されます。子は関数「Aim」と「Shoot」を呼び出しています。

  • テストが false を戻した場合、If ノードは Failure を戻し、続けてルートは次の子へと進みます。これは 1 つのシーケンスであり、その最初の子の実行によって開始されます。

  • これによって関数「Walk」が呼び出されます。この関数は Success を戻すことで、このシーケンスが次の子をアクティブに設定して実行します。

  • Wait ノードが実行されます。5 秒間の待機が必要ですが、1 回目の呼び出しが行われた所であるため、まだ待機時間が満たされておらず、したがって Continue が戻されます。

  • このシーケンスは、そのアクティブな子から Continue ステートを受け取るため、アクティブな子が変更されません。そのため、次の Update では同じ子から開始されます。

  • Wait ノードは、十分に更新されて待機時間が経つと Success を戻し、その結果シーケンスが次の子に進みます。

ノードリスト


Sequence


子ノードをひとつずつ実行します。子の戻すステートによって挙動が以下のように変わります。


  • Success ― シーケンスは次のフレームで次の子をチェックします。

  • Failure ― シーケンスは次のフレームで最初の子に戻ります。

  • Continue ― シーケンスは次のフレームで同じノードを再び呼び出します。

RandomSequence


呼び出される度に、そのリストからランダムな子を実行します。コンストラクター内で、それぞれの子に適用する重要度のリストを整数配列として指定し、特定の子を選択されやすくすることも可能です。


Selector


子のどれかが Success を戻すまですべての子を順番に実行し、子のどれかが Success を戻した時点で、残りの子ノードは実行せずに終了します。どの子も Success を戻さなかった場合は、このノードは Failure を戻します。


Call


該当の関数を呼び出します。この関数は常に Success を戻します。


If


該当の関数を呼び出します。


  • true が戻された場合は、現在アクティブな子を呼び出してそのステートを戻します。

  • それ以外の場合は、子を呼び出すことなく Failure を戻します。

While


その関数が true を戻す限り Continue を戻します(したがって、次のフレームでツリーがチェックする時は、以前のノードのすべてをチェックするのではなく、同じノードから再開します)。


子が 1 つずつ実行されます。


関数が false を戻した場合に Failure を戻します。


Condition


このノードは、関数が true を戻すと Success を戻し、false の場合は Failure を戻します。


自身の子の戻り値に依存する他のノード([例]Sequence や Selector)と連結されている場合に役立ちます。


Repeat


すべての子ノードを、設定回数、繰り返し実行します。


設定回数に達するまで Continue を戻し続け、回数に達すると Success を戻します。


Wait


(初回呼び出しから数えて)設定回数に達するまで Continue を戻し、回数に達すると Success を戻します。


Trigger


トリガーの設定(または、最後の引数が false に設定されている場合は、トリガーの設定解除)を、該当アニメーター内で行えるようにします。常に Success を戻します。


SetBool


該当アニメーター内にブールパラメーターを設定できます。常に Success を戻します。


SetActive 該当ゲームオブジェクトの有効・無効を設定します。常に Success を戻します。


Complete this tutorial