テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity,ECS】他のEntityが持つComponentDataを追跡する - ComponentDataFromEntity

今回もECSの話です。

ゲームでは特定のオブジェクトを追跡するというのはよくある話です。例えばカーソルやユニット、アイテムやパスなど。

ECSでも同様にそういった機構が必要になる事は多々あります。今回はEntityから特定のキャラクターを追跡させる方法についてです。

キャラクターを追跡する

キャラクターを追跡する…となると、MonoBehaviourを使用するならば非常に簡単に実現が可能です。下のようなコードを書けば良いです。

gist.github.com

ただしECSでは、こんな簡単に実現出来ません。ECSが使用するオブジェクトEntityは基本的に参照型ではなく、またポインター等でアドレスを取得してもComponentData等の増減やSharedComponentDataの値変更等の理由により、アドレスが変化する可能性があります。

なのでComponentDataFromEntityを使用して、Entityから対象の座標やパラメーターを追跡します。

ComponentDataFromEntityでEntityの持つComponentDataを参照する

ComponentDataFromEntityは、全てのComponentDataの内から、特定のEntityが持つComponentDataを取得するAPIです。

特定のEntityの方を向くシステムを作ってみます。

f:id:tsubaki_t1:20181201224146g:plain

最初に特定のEntityからPositionComponentDataを取得します。

[ReadOnly] public ComponentDataFromEntity<Position> positionFromEntity;

値を取得するには、下のように記述します。 なお対象のEntityがPositionを持っていない可能性もあるので、一応でEntityが無ければ処理をスキップします。

if (!positionFromEntity.Exists(target.Value)) return; 
var targetPos = positionFromEntity[target.Value];

gist.github.com

ComponentDataFromEntity[ReadOnly]なら並列でもアクセス出来るので、楽で良いです。

なお、ComponentDataFromEntityやポインタをキャッシュして云々は、基本的にランダムアクセスになります。ECSは連続したメモリアクセスによる高速化(事前にソートしたデータへ連続してアクセスし、同じ処理を繰り返し実行することによる高速化)なので、ランダムアクセスは出来れば避けたい所です。

ComponentDataFromEntityと同じ種類のComponentDataにアクセスする場合

厄介なのが、ジョブ上でComponentDataFromEntityと同種のComponentDataにアクセスする場合です。
例えば、ComponentDataFromEntityが取得するPositionの情報をを元に、Positionを更新する…といった場合です。

ということで、キャラクターがシリンダーを追跡するコードを作ってみます。

f:id:tsubaki_t1:20181202000338g:plain

まず、下のコードはエラーになります。 InvalidOperationException: The writable NativeArray LookTargetJob.Iterator is the same NativeArray as LookTargetJob.Data.positionFromEntity, two NativeArrays may not be the same (aliasing).

gist.github.com

一旦EntityFromPositionの情報をキャッシュしてから使用するように変更してみました。
ポインタを使用して直接アクセスするのも悪くなさそうですが、このアプローチならリニアなメモリアクセスが維持できるので良いんじゃないかなとコッソリ思っています。
(キャッシュミスしまくるComponentDataFromEntityを事前に叩いておき、参照するデータを連続したメモリに格納、あとは参照先の値を使用した処理はシーケンシャルに処理する)

gist.github.com

コピーのコストがあるので最適解かは微妙なところですが、まぁ一応こんな感じで回避出来るという事で。

また、一応EntityCommandBuffer.Concurrentで一旦バッファに格納することでも回避が可能といえば可能です。

tsubakit1.hateblo.jp

追記

一つしか存在しないようなデータ(例えばプレイヤー)の場合、Systemにフィールド持たせてキャッシュさせるのはアリです。
Systemは世界で一つであることが保証されているので、シングルトン的な感じでデータをキャッシュしたり出来ます。 ただ、ComonentGroupがなければSystem停止はしてくれないので、動作を安全にするためにSystemが勝手に動かないようにOnUpdateを即リターンするなり、Systemの生成を運用するなり工夫する必要はあります。