テラシュールブログ

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

【Unity】ECSでComponentGroup内のComponentDataを取得する新API、ToComponentDataArrayとCopyFromComponentDataArray

ComponentDataArray(型)が非推奨になり、代わりにToComponentDataArray(...)CopyFromComponentDataArray()が追加されました。

これは今までのComponentDataArrayと異なり、NativeArrayを取得します。

ToComponentDataArrayとCopyFromComponentDataArrayはChunkIterationの面倒な記述をスッキリ書ける

このAPIでは、今までComponentDataArrayで取得していたような、ComponentGroup全体が要求するComponentDataの配列を取得できます。

機能は2つで、ComponentGroup.ToComponentDataArray()でComponentGroupが持つComponentData一覧をNativeArrayにコピーして使用できるようようします。また、ComponentGroup.CopyFromComponentDataArray()で変更を反映します。

public NativeArray<T> ToComponentDataArray<T>(Allocator allocator, out JobHandle jobhandle) where T : struct,IComponentData {...}

public void CopyFromComponentDataArray<T>(NativeArray<T> componentDataArray, out JobHandle jobhandle) where T : struct,IComponentData {...}

内部的にはGatherComponentDataJob<T> : IJobChunkで全Chunkが入るバッファを1回で作り、中身を一気に埋めていく感じです。チャンクがそれ程多くないなら、殆ど一回のMemcopyで終わるので効率的に見えます。ChunkIterationでやるような面倒くさい記述を割とスッキリとしてくれるので、割とありがたいです。

ForEachで面倒くさい(例えば複数のGroupを参照するような)ケースでは、こちらが便利そうです。

ということで、使い方を見ていきます。

まずは普通にComponentDataのNativeArrayを取得→反映までをメインスレッドのみで

普通にComponentGroupの要求を定義します。

        // コンポーネントの準備
        playerGroup = GetComponentGroup(
            ComponentType.ReadOnly<Position>(),
            ComponentType.ReadOnly<Player>(),
            ComponentType.Create<HitPoint>());

次にComponentDataを取得します。

ComponentGroup.ToComponentDataArray<T>(Allocator.TempJob);を使用します。

この時Allocatorを設定するのですが、内部的にコピー作業にジョブを使用しているので、実質TempJobもしくはPersistentの二択です。(もしくはTempJob 1択?)

なお内部的にジョブ(&Burst)を使用していますが、即座にCompleteしているので普通にメインスレッド(ComponentSystem)で使って問題ありません。

    protected override void OnUpdate()
    {
        // チャンクからNativeArrayを取得
        var playerPositions = playerGroup.ToComponentDataArray<Position>(Allocator.TempJob);
        var playerHitpoint = playerGroup.ToComponentDataArray<HitPoint>(Allocator.TempJob);
        var enemyPositions = enemyGroup.ToComponentDataArray<Position>(Allocator.TempJob);

これで取得したComponentDataのArrayを使用して、色々と演算出来ます。NativeArrayなのでジョブに投げる事も出来ます。

ただし演算に使用するデータはあくまでコピーされたデータなので、もし変更していた場合は設定を反映させる必要があります。

演算結果をComponentGroupに書き戻します。

下記戻す時にはComponentGroup.CopyFromComponentDataArray(...)を使用します。

        // ダメージを反映
        playerGroup.CopyFromComponentDataArray(playerHitpoint);

ジョブ対応する

ToComponentDataArrayとCopyFromComponentDataArrayは内部的にはIJobChunkのジョブを使用しています。この時JobHandleを渡してやれば、CompleteのタイミングをoutしてきたJobHandleにまかせてくれます。

少し厄介だったのが、JobHandleを受け渡すのが今までのIJob系と若干異なっていた点です。JobではSchedule時に渡しますが、今回はComponentGroup.AddDependency経由で渡します。

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        // チャンクからNativeArrayを取得
        playerGroup.AddDependency(inputDeps);
        var playerPositions = playerGroup.ToComponentDataArray<Position>(Allocator.TempJob, out JobHandle handle1);
        var playerHitpoint = playerGroup.ToComponentDataArray<HitPoint>(Allocator.TempJob, out JobHandle handle2);
        var enemyPositions = enemyGroup.ToComponentDataArray<Position>(Allocator.TempJob, out JobHandle handle3);
        jobhandle = JobHandle.CombineDependencies(handle1, handle2, handle3);

値の反映もジョブ経由です。こちらもAddDependency経由で渡します。正直なんでやねん感はありますが、何故かそうなっています。

        // ダメージを反映
        playerGroup.AddDependency(jobhandle);
        playerGroup.CopyFromComponentDataArray(playerHitpoint, out JobHandle handle4);

ジョブを使わない方の実装例

gist.github.com

ジョブを使う方の実装例

gist.github.com

f:id:tsubaki_t1:20190222005710j:plain
システム内にWaitForJobGroupIDは無くなった

ハイブリットで使いたい場合

ToComponentDataArrayGetComponentDataArray(旧)と異なりコンポーネント(e.g. Transform)を取得出来ません。

そういうのはGetComponentArrayなりGetGameObjectArrayなり、ForEachでやります。(どうせメインスレッドでしか動きませんし)

感想

またAPIが紛らわしいんだよオラァ!!

低レイヤーに直接アクセスを要求するようなAPIが減ってくれて嬉しい限り

関連

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp