Toybox ObjectPool
Toybox に含まれる ObjectPool プレハブの使い方、内部の仕組みなどを解説する
(文章校正がまだ十分にされていません。不可解な点があればお知らせください。)
この文書に従って作成できるものを デモワールド wrld_c8f68baa-6d46-483d-9c39-9b822cacd4e1 として置いてあります。
- Object pool とは
- Toybox について
- 用語 spawn, despawn
- 基本的な使い方
- Pickup でき、かつ gravity enable なオブジェクトと共に使う例
- 仕様・注意事項
- 内部の仕組みについて考察
- 付録
Object pool とは
ソフトウェア設計における一般的な手法の一つ。
プログラムが実行されている期間(VRChat の場合はワールドインスタンスの維持期間)に対して、 あるオブジェクトの使用期間が限られていて、かつそれが頻繁に多数使われる場合に、 それらオブジェクトの生成と破棄の処理を軽減するための手法。
そのオブジェクトの存続の必要がなくなった時に、 実際の破棄はせずに溜めておく場所「プール」に移しておき、 次に新たなオブジェクトが必要になった時にこのプールから取り出して再初期化して使用する、 というのが基本的な仕組みである。
Toybox について
Toybox は作者 Hardlight680 さんによるアセット集であり、 VRChat のワールドを開発する際に有用なプレハブおよびシーンを配布しているものである。
- Readme.txt より: "A collection of useful prefabs and scenes for use in developing VRChat worlds."
- 入手元は作者によるツイートを参照: https://twitter.com/JasonL663/status/1012799892425588736
- (ObjectPool プレハブでは必要ないが、 Toybox は Unity の Standard Assets および UnityChan パッケージを利用している。 他の機能を使う場合はそれも合わせて導入すること。)
用語 spawn, despawn
Toybox ObjectPool はオブジェクトを取り出す操作を「spawn(スポーン)」、 用が済んだオブジェクトを ObjectPool のプールに戻す操作を「despawn(デスポーン)」と呼んでいる。
VRChat SDK には SpawnObject
というアクションがあるが、
この文章での「spawn」はそれではなく ObjectPool の用語を指している。
(
両方とも「シーン上にオブジェクトが出現する」という点では同じだが、内部的には扱いが異なる。
SpawnObject
アクションでは GameObject が新規に生成されているのに対し、
ObjectPool の spawn では GameObject は再利用されており新規には生成されていない。
この点、用語の意味を取り違えて混乱しないで欲しい
)
基本的な使い方
ここでは基本的な使い方を示すために、例として、 キューブをインタラクトするとオブジェクトがスポーンし、 スポーンしたオブジェクトをインタラクトするとデスポーンする、 という構成を作る。
作成手順
- ObjectPool を配置する
- Toybox の
ObjectPool
プレハブをシーンに追加する。( プレハブの位置はAssets/Toybox/ObjectPool
)
- Toybox の
- Pooled object を一つだけ残して削除する
- (ObjectPool プレハブは初期状態で例示として3つのオブジェクトを扱う状態で構成されている。 以下に述べる手順でオブジェクトを改造するので、一つだけ残してあとは削除する。改造の後に再び複製して増やす。)
ObjectPool/Instances/PooledObject (2)
およびObjectPool/Instances/PooledObject (3)
を削除する- 削除操作の際 "This action will break the prefab instance." という警告が出るが続行する
- Pooled object に対する物理設定を調整する
- (
ObjectPool
プレハブの初期状態で都合が悪い点があるので調整する。 いくつかの方法がありうるのだがここでは簡単にするため、ワールドの設定の方を変えてしまう。) VRC_SceneDescriptor
のObjectBehaviourAtRespawnHeight
をRespawn
にする- (
VRC_SceneDescriptor
はObjectPool
の中ではなくシーンに一つ置いてあるはずのもの)
- (
- 生成したいオブジェクトの作成する
ObjectPool/Instances/PooledObject (1)/Object/Sphere
が ObjectPool で管理されるオブジェクト (のゲーム上で現れる部分)である。ここを出現させたいものに作り替える。 (ObjectPool
の動作を試してみたいだけならばプレハブに入っている状態のまま先に進んでよい)- 名前は
Sphere
のままにしておくこと - 作成し終わったらオブジェクトを元のようにインアクティブにしておくこと
- オブジェクトがスポーンする場所を設定する
ObjectPool/SpawnPoint
をオブジェクトをスポーンさせたい場所に移動する
- スポーン操作を実装する
- Cube などを作り
VRC_Trigger
を追加しOnInteract
トリガーを追加する。 VRC_Trigger
のAdvancedMode
にチェックを入れ、broadcast type をLocal
にする- Action に
AnimationTrigger
を追加する。Reciever にObjectPool/Spawner
を指定する。Trigger 名にSpawn
を指定する。
- Cube などを作り
- デスポーン操作を実装する
- 前ステップの
Sphere
にVRC_Trigger
を追加、OnInteract
トリガーを追加する。 VRC_Trigger
のAdvancedMode
にチェックを入れ、broadcast type をLocal
にする- Action に
ActivateCustomTrigger
を追加、receiver にSphere
から見て二つ上のPooledObject (1)
を指定。 Custom trigger 名にDespawn
を選択する。
- 前ステップの
- 必要なだけオブジェクトを増やす
ObjectPool/Instances/PooledObject (1)
を duplicate して、Instances
の下に必要な数のオブジェクトを持たせておく。
補足
- 物理設定の調整について
- ここではシンプルな実装になるようにワールドの設定
ObjectBehaviourAtRespawnHeight
を変更する方法を紹介した。 - これは
ObjectPool
の初期状態ではオブジェクトが使われていない時に、 落下していって失われてしまうために必要な措置となっている。 別解として「ObjectPool/Instances/PooledObject (1)/Object
が持つRigidbody
のIsKinematic
を有効にする」 あるいは同様に「UseGravity
を無効にする」といった方法で Respawn Height に容易に達しないようにするという方法も取れる。 だがそれらの場合、デモンストレーションとして見た目が分かりにくいので、ここでは前述の方法を採用した。
- ここではシンプルな実装になるようにワールドの設定
Pickup でき、かつ gravity enable なオブジェクトと共に使う例
ObjectPool で「ピックアップでき重力によって落下する」つまりは VR の中で自然な振る舞いをする手に持てるオブジェクトを扱えるようにする手順を述べる。
例として
「ワールドにひとつ配置した Cube のインタラクトにより spawn し、オブジェクトが特定 Layer に触れると despawn する」
ものを作る。
Layer には Water
を使用する。(デフォルトで存在するレイヤーの一つというだけで、選定に深い意味はない。)
なお、ここで作成する構成は、ObjectPool の元の状態を生かして作業量が少なくて済むようなものにしている。 用途によっては必ずしも最適なものではないかもしれない。
作成手順
- ObjectPool の配置
- Toybox の ObjectPool prefab をシーンに追加する。
ObjectPool/Instances
に入っている pooled object を一つだけ残してあとは一旦削除する。(以下PooledObject
と表記) "This action will break the prefab instance." という警告が出るが続行する- (ここでデバッグのために目印として
PooledObject/Object/Sphere
の兄弟にオブジェクトを追加しておくと良い。 このオブジェクトのコライダーは邪魔になるので削除しておくこと。 ObjectPool prefab の機能によりPooledObject/Object
が空間を移動するのだが、Sphere
が初期状態では inactive なので何処に位置しているのか判断できない。 デバッグ用に見えるオブジェクトを入れておくことで作業がしやすくなる。) ObjectPool/SpawnPoint
オブジェクトを、シーン上でオブジェクトが出現する位置に移動する
(必要に応じてこれにもデバッグのために目印として子オブジェクトを作成しておくと具合が良い。 追加する場合はコライダーは邪魔になるので削除しておく。)
- Spawn 動作をさせるスイッチを作る
Cube
を作りVRC_Trigger
を追加しOnInteract
トリガーを追加Advanced Mode
にチェックを入れて broadcast type をLocal
にする- action に
AnimationTrigger
を追加 reciever にObjectPool/Spawner
を指定する。 - trigger 名に
Spawn
を指定する
- コライダーの設定
- (この段階でスポーン動作は既にするようになっている。ただし
PooledObject/Object
は初期状態で有効なコライダーを持たないので落下してしまっている。その対策をする) PooledObject/Object
にコライダーを追加する。PooledObject/Object/Sphere
についているコライダーは不要になるので削除する。
- (この段階でスポーン動作は既にするようになっている。ただし
- VRC_Pickup と Rigidbody を調整
PooledObject/Object
にVRC_Pickup
はあらかじめ備えられているがPickupable
が無効になっているので有効にする。必要に応じて他の設定も変更する。- 同じく
Rigidbody
が備わっているので、パラメタを必要に応じて変更する。
- Despawn のきっかけとなる衝突対象オブジェクトを作る
- (ここまでで spawn して使えるようになるための処置は完了している。以降では despawn の処理を作っていく)
- シーンに適当なオブジェクトを生成する
- コライダーの
IsTrigger
を有効にする。Layer
をWater
にする。
- Despawn 動作をさせるトリガーを設定する
PooledObject/Object
にVRC_Trigger
を追加しOnEnterTrigger
トリガーを追加。Advanced Mode
にチェックを入れて broadcast type をMasterUnbufferd
にするLayers
でWater
を選択する- Action に
ActivateCustomTrigger
を追加する - Recievers にこのオブジェクトの親である
PooledObject
を追加する。Name にDespawn
を選択する。(Deactivate
を選ばないように)
- Pickup 中に despawn した時の処置を追加
- (持っている時に despawn が発動するとアバターの手が持ったままになるので、対策する)
PooledObject
のVRC_Trigger
にDeactivate
という custom trigger をインスペクタで開く。- もとからある
SetGameObjectActive
アクションの前にSendRPC
アクションを加える。 - Recievers に
PooledObject/Object
を選択する。 - Method の選択肢の中に
VRC_Pickup.Drop
があるので選ぶ - Targets を
Local
にする。(その後ろの "Use Player ID as last" はそのままチェックが入った状態にしておく)
- Despawn されて再利用待ちになっているオブジェクトを格納しておく場所を作る
- (ここまでで despawn 処理自体は行えるようになっている。ただし
PooledObject/Object/Sphere
は inactive に戻り、 見えなくなっているがPooledObject/Object
はその場にとどまっている。PooledObject/Object
の pickup 機能はそのままなのでつかめてしまう。以降ではこの処置をする。) PooledObject/Object
が衝突して出られない囲われた空間を作る
- (ここまでで despawn 処理自体は行えるようになっている。ただし
- この空間に位置するように格納場所を指すオブジェクトを作る
- (移動には
VRC_ObjectSync.TeleportTo
を使うのだが不具合があるので親子構造を余分に作る) - Create Empty で空のオブジェクトを作る。以降
PoolPointHolder
と呼ぶ。 PoolPointHolder
にさらに空のオブジェクトを作る。PoolPoint
と呼ぶ。- (さらに
ObjectPool/SpawnPoint
の時と同じように、コライダーを無効にした目印オブジェクトを入れておくと分かりやすい) PoolPoint
の Trasform はゼロにしておき、PoolPointHolder
の位置を前手順で作った空間の空中に位置するように調整する
- (移動には
- despawn 時に前述の空間へ移動させる
PooledObject
のVRC_Trigger
にDeactivate
という custom trigger をインスペクタで開く。(Drop
を追加したのと同じ所)- もとからある
SetGameObjectActive
アクションの後にSendRPC
アクションを加える。 - Recievers に
PooledObject/Object
を選択する。 - Method の選択肢の中に
VRC_ObjectSync.TeleportTo
があるので選ぶ - Targets を
Owner
にする targetLocation
に、前の手順で作成したPoolPoint
を指定する
PooledObject
の初期位置を前述の空間内にする- despawn 時と同じになるように
PooledObject
の初期配置位置を前述の空間内にする
- despawn 時と同じになるように
- オブジェクトの個数を調整する
- (仕組みはこれで出来上がっているので、動作確認をまずする。問題ないならば最後の調整を行う)
- デバッグ目的でいれた目印オブジェクトを削除するか inactive にする。
PooledObject
を必要な個数だけ duplicate する。- (コライダを持っているので同じ座標に置くと互いに相手をはじく。できれば重ならないように配置したほうが良い。数個程度なら大丈夫ではある)
注意事項
- 表示に用いているオブジェクト
Sphere
は内部の構成は変えて良いが、この名前を不用意に書き換えてはいけない。 (変えたい場合はPooledObject
が使用するアニメーションクリップAssets/Toybox/ObjectPool/Animation/PooledObjectActive
の中でこの名前で参照しているので、そこも合わせて変更すること。) VRC_Trigger
でのVRC_ObjectSync.TeleportTo
のtargetLocation
の指定は奇妙なふるまいをするので注意。- target に指定するオブジェクトの調整は先に済ませ、
targetLocation
に設定するのは最後にした方がいい。 - (例えば名前を後から変更すると VRC_Trigger が見失ってしまう)
- target に指定するオブジェクトの調整は先に済ませ、
- spawn/despawn の指示(すなわち animation trigger
Spawn
の有効化、Despawn
custom trigger の呼び出し)は ひとりのプレイヤー(一つのクライアント)でのみ行うようにしなければならない。 (他への伝達は ObjectPool が実装しているので、それへの指示自体を伝達すると処理が過剰に同時に実行されてしまう。)- despawn では
PooledObject/Object
のオーナーによって(broadcast type を Owner にする事で)一人に絞れる - spawn はこのガイドでは OnInteract を使用したので broadcast type を Local にした。
- ワールドに居る各プレイヤーが等しく観察するトリガーではこの部分を作成するのが少々難しいかもしれない。
- spawn の場合は複数から同時に呼ぶと生成する数が
- despawn では
残っている問題
- 複数のプレイヤーが同時にスポーン動作をさせると一つしか現れないことがある。
- (ObjectPool にもとからある問題 Toybox/ObjectPool/Readme.txt 参照 )
- 全てのオブジェクトが出払っている場合にスポーンが失敗した時の動作。
- 現状では何も起きない。オブジェクトが現れなかったことでしか把握できない
- オブジェクトがワールドから落下した場合の振る舞い
VRC_SceneDescriptor
でObjectBehaviourAtRespawnHeight
がDestroy
になっている場合、 ObjectPool で管理しているオブジェクトが一つ失われてしまう。Respawn
にするとどうやら初期位置に戻るようだ spawn 状態のまま格納庫に入ってしまうので具合が悪い。(状態は異なるので選別処理は可能ではあろう)
解説
- Toybox ObjectPool は spawn 時にオブジェクトを所定位置に移動させるまでの面倒をみようとする設計になっている。
このため
PooledObject/Object
はVRC_ObjectSync
を備える。 また、VRC_ObjectSync
を確実に動作させるためにPickable
false なVRC_Pickup
を備え、 このVRC_Pickup
はRigidbody
に依存している。 ピックアップ可能なオブジェクトを Toybox ObjectPool で管理したい場合、 この既に備わっている
VRC_Pickup
およびRigidbody
を利用する必要がある。- 独自のものを下位構造として作ってもおそらく動作はするが、その内容は ObjectPool が行っている位置管理と同程度になり、 ObjectPool を使う事で楽をしようとしているはずなのに本末転倒なことになり、無意味である。
ObjectPool は管理しているオブジェクトが「活動している」ことを表すのに、
PooledObject/Object/Sphere
の isActive を使用している。- 言い換えると「spawn/despawn に際してオブジェクトの選定、位置移動、状態変更までの面倒は見るので、
他のことは利用者が
Sphere
以下に実装する」という設計になっている。
- 言い換えると「spawn/despawn に際してオブジェクトの選定、位置移動、状態変更までの面倒は見るので、
他のことは利用者が
以上の Toybox ObjectPool の構成のもとで、pickup 可能なものを作ろうとするとやや困った事になる。
- ObjectPool 利用者のオブジェクトは
PooledObject/Object/Sphere
以下に作らせる設計なのに、 肝心のVRC_Pickup
は一つ上の階層PooledObject/Object
に既に存在しているからである。 - このガイドでは、既に備わっているコンポーネントを(
Pickable
を true に変更するなどしつつ)そのまま使う方針を取った。
- ObjectPool 利用者のオブジェクトは
Rigidbody
のUseGravity
を有効にした場合、コライダをどう設定するかが問題になる。- 本来は目に見えるオブジェクトである
PooledObject/Object/Sphere
の形状に合わせて、このオブジェクト以下でコライダを付けたい。 - だが despawn 時 には
Sphere
は inactive になるためここに設定したコライダは働かない。UseGravity
が有効なオブジェクトを扱う場合、このままではPooledObject/Object
は落下していってしまう。 - また(
UseGravity
に関係なく)VRC_Pickup
の動作のためにPooledObject/Object
はコライダを備えておく必要がある。 - 理想的な構成は以下だろう
PooledObject
に pickup のためのコライダ(IsTrigger は true)PooledObject/Object
に物理的形状のためのコライダ- 初期 と despawn 時は
Rigidbody.IsKinematic
有効にして物理演算の影響を受けないようにする
- だがこれは現状(ver 2018.2.2)で安定動作しない。
- (
Rigidbody.isKinematic
の書き換えが安定動作しない。VRC_ObjectSync
がこの部分に関与している模様。 uGUI からVRC_ObjectSync.isKinematic
も見えるが、この変更もうまくいかない。)
- (
- 代替策としてこのガイドでは
PooledObject/Object
に備えたコライダを使い続けるようにした。- これに伴い despawn 時にオブジェクトを保存しておく場所を作りそこに移動する処置を追加した。
VRC_ObjectSync.TeleportTo
には rotation に関して不具合があるが、今回の場合は問題ない。
- 本来は目に見えるオブジェクトである
- 結果的に
Sphere
は見た目だけを担い rigidbody や コライダの機能はPooledObject/Object
の方が担うことになった。- 常に active で良いのであれば、
PooledObject/Object
から作りたいオブジェクトを構成してもよいだろう。(Sphere
の存在は無視する) Sphere
ではOnEnable
のみをしかけて初期化処理の目的で使う、といった構成の仕方もありうるだろう。
- 常に active で良いのであれば、
仕様・注意事項
これまで述べたものと重複するが、リファレンスとして仕様を書き出してみる。
概要
- Toybox
ObjectPool
は VRChat SDK 環境下において object pool を実現するプレハブである。ObjectPool
は一定個数のオブジェクトを管理し、それらを再利用する spawn 、despawn 操作を提供する
セットアップ
- spawn されるオブジェクトは
ObjectPool/Instances
の下にあらかじめ格納しておく- ここに格納されている
PooledObject
が個々のオブジェクトを管理する単位である。 - プレハブでは例示として元々3個格納されているが、 必要に応じて削除・複製をして数を調整する。
- ここに格納されている
- spawn/despawn によってシーンの3D空間を移動する GameObject は
PooledObject/Object
である。 - spawn/despawn されるオブジェクトの外見は、
PooledObject/Object/Sphere
以下に作る- 初期状態ではこの GameObject は inactive にして表示されないようにしておくこと。
- この "Sphere" という名前は変えてはいけない。(この名前で参照している AnimationClip があるため。変えたい場合はそちらも合わせて変更する)
ObjectPool/SpawnPoint
はオブジェクトがスポーンする位置- SpawnPoint の Transform の Scale は 1.0 のままにしておくこと。 (変えても動作に支障はないが 1.0 が作成作業において面倒がない)
操作
- spawn
ObjectPool/Spawner
のアニメーション・トリガーSpawn
を引くと、オブジェクトが SpawnPoint の位置にスポーンする。- このアニメーション・トリガーの操作はローカルな操作で行う事。さもないとインスタンスに居るプレイヤーの数だけスポーンしてしまう。
- despawn
- PooledObject の Custom Trigger
Despawn
を実行する。
- PooledObject の Custom Trigger
コールバック
- spawn, despawn 時に行いたい処理を追加するには、
PooledObject/Object/Sphere
にVRC_Trigger
を追加しOnEnable
,OnDisable
トリガーとして書く。 broadcast type はLocal
にすること。
既知の不具合
- ほぼ同時に複数のプレイヤーが spawn 操作を行った場合、spawn されるオブジェクトの数が足らないことがありうる。
内部の仕組みについて考察
プール構造の実現
- VRChat SDK では通常のプログラミング言語で使用する変数に相当するものが自由にならないため、 そもそもプールするオブジェクトをどのように保持管理するのか、というのが問題(見どころ)となる。
- Toybox ObjectPool では、この集合構造にオブジェクトの親子関係を利用している。
親子関係は
Transform.SetParent(parent: Transform)
によって変更でき、これは uGUI の機構から呼び出し可能である。 - 「集合のうち一つのオブジェクトを対象に操作する」というのが問題になるが、
これには「Unity の AnimationClip での対象オブジェクトの指定は名前に頼っている。
同名の物が有った時に一つだけが処理される。」という仕様が活用されている。
- (「一つだけ」の部分の仕様書からの裏付けは取れていないが、見るかぎりそうなっている)
- この構成を取った場合に実装上の鍵となるもう一つの関数は
Animator.Rebind()
SetParent で書き換わった構造を Animator に理解させることができる。
アーキテクチャ
Spawner
は自身では pooled object の(論理的な)保持と選択だけを行っている。 spawn/despawn の実処理は個々のオブジェクト側に書いてある。- 「処理対象が一つ選ばれる」はこの前半の処理である
Spawner
で行うのだが、 実際の構造変更は後半で個々のクライアントで実行される。(一般的なプログラミングに成れていると奇妙に感じるところかもしれない)
- 「処理対象が一つ選ばれる」はこの前半の処理である
- 前半処理は要求を出したマシンローカルで実行され、
pooled object の外部インタフェースになっている custom trigger(Spawn, Despawn)によって処理が全クライアントに配られる。
そこから先はまた各マシンローカルになっている。
- 処理の分岐としては基本的にはここだけになっていて、同期(というか分散処理)の考え方が明確になっている。
- (つまり若干意外なことに)プール構造の変更は「個々のクライアントが同時に行うので結果的に同じ状態になる」方式の同期になっている。
- object pool において各オブジェクトは最終的に active か inactive なので、バッファは
AlwaysBufferOne
を使用している。 - late joiner に対してはこの記録が再生されることで、各 pooled object が自主的にプールに留まったり出てきたりして、 同じ状態が再現される。(繰り返しになるが、集中的な管理を行わずに個々に自主的にやらせるという方針は、なかなか器用だ。)
- 外部インタフェースと内部処理の間にはアニメーション機構が挟まっていて、ここで pooled object の状態遷移管理をしている。
- (繰り返しになるが)これは個々のクライアント内での状態遷移の表現であり、
この Animator を保持する
PooledObject
自体は同期処理されていない。 Custom トリガー(のアクション)を配ったことでマルチクライアントの処理は済んでいる。 - (スポーンしたオブジェクトの空間移動については
PooledObject/Object
が持つVRC_ObjectSync
のSynchronizePhysics
がtrue
になっていることで実現される。 そのことについて基本的にはPooledObject
自身は関与していない。
- (繰り返しになるが)これは個々のクライアント内での状態遷移の表現であり、
この Animator を保持する
- 種々の処理は、それぞれの処理ごとにオブジェクトを用意して整理している。
- 処理はオブジェクトの有効化をきっかけに開始される。
-
VRC_Trigger
で処理を書く場合はOnEnable
トリガーに書く -
AnimationClip
で書きたければAnimation
を PlayAutomatically にして自動的に再生開始 - uGUI で書く場合はそこからさらに animation event で
OnClick()
を駆動
-
- オブジェクトは自身で不要になった時に無効化して、次の実行に備えている。
- サブルーチン的なものは「別のオブジェクトを有効化する」ことで実現される。
- (この方式は同じことを別のオブジェクトで実行したい場合は、オブジェクトを丸々コピーする必要があり空間効率が悪い。 だが VRChat trigger システムの記述では操作対象をパラメタとして書けず 「VRC_Trigger が付随しているこのオブジェクト」としてしか表記できないので致し方ない。)
- 処理はオブジェクトの有効化をきっかけに開始される。
- 内部処理は uGUI を使い Unity のコンポーネントの世界で書かれる。
同時処理について考察
- 二人がほぼ同時にスポーン操作した場合、オブジェクトは一つしかスポーンしない。
- これは同じ
PooledObject
がそれぞれのマシンで処理対象として選ばれてしまうからである。- それぞれの世界での
Spawner/Handle/Spawn
先頭に居たものは同じものになっているため。 この時、個々のクライアントは自分が spawn 処理始めたものだとして振舞っているが、実は同じHandle/Spawn
を叩いている。
- それぞれの世界での
- すると Custom Spawn は同じオブジェクトに二回発せられる。
しかし構造が壊れてしまうような致命的な問題は起きない。
これは
PooledObjectAnimator
アニメーション・コントローラの遷移処理によって、 activate 処理は各クライアントで一回しかされないようになっているためである。
まとめ
- アーキテクチャを整理してみると以下のようになっている事が分かる
- 処理の受付部分(受け付けたマシンローカルで実行する。ローカルで行っておく下処理を含む)
- 外部インタフェース(Custom trigger を適切な broadcast type で用意し、ここの呼び出しにより要求を各マシンに配る)
- 状態遷移管理(アニメーション・パラメタによりオブジェクト状態を扱う、必要な内部処理の呼び出しを行う)
- 内部処理(可能な限り uGUI を使用して、Unity の世界で書く)
- この階層構造により堅牢かつ改造が容易なアーキテクチャになっている。
付録
構造図: