LoginSignup
1
1

More than 1 year has passed since last update.

ECS事始め

Posted at

はじめに

発表されてからだいぶ経つがまだ正式版のリリースされていないECS。
ただ、最近v0.50までアップデートされた模様。
v1.0まであと少しとなっていそう?なこのあたりでそろそろ触っておこうと考えその作業ログを残す。

この記事では何をするか

  1. Installation & setup guideに従いECS環境を構築
  2. サンプルコードをざっくり読解
  3. 簡単なコードを記述してみる

環境

MacBook Pro (13-inch, M1, 2020)
macOS Monterey
Unity 2020.3.30f11

インストール

 Unity Editor version
 You must use Unity Editor version 2020.3.30 with Entities 0.50.

とのことなのでそれに従う。

マニフェストの編集

manifest.jsonに下記を追記する。

"com.unity.entities": "0.50.1-preview.2"

なお、PackageManagerの"Add package from git url"から"com.unity.entities"を入力でも追加し、ダウングレードするでもOK。

コンパイルが走り、特にエラーも表示されず完了。

Rendererの追加

描画にはHybrid Rendererを使用するとのこと。

"com.unity.rendering.hybrid"

サンプルを動かす

ひとまずHybrid Renderer のサンプルを動かしてみる。
Runtime Entity Creation に記載の通り、作成時に必要なComponentはRendererUtilityを使用して追加する。

10ヶ並べた結果

10個並べ、表示できることを確認。

ざっくり読解

なお、少し Runtime Entity Creation のコードそのものからは変更しています。
おおよそは同じです。

code
public class EntityHandler : MonoBehaviour
{
    public Mesh mesh;
    public Material material;
    public int entityCount;

    // Example Burst job that creates many entities
    [BurstCompatible]
    public struct SpawnJob : IJobParallelFor
    {
        public Entity Prototype;
        public EntityCommandBuffer.ParallelWriter Ecb;

        public void Execute(int index)
        {
            // プロトタイプとなるEntityからクローンし、新たにEntityを生成.
            // Entityの生成や変更等々はメインスレッドで実行する必要があるため、
            var e = Ecb.Instantiate(index, Prototype);
            // 必要なComponentは前もって設定されているため、それぞれのEntityに対し個別の処理を実行することが可能。
            // ここではindexに応じてTransformを変更している。
            Ecb.SetComponent(index, e, new LocalToWorld { Value = ComputeTransform(index) });
        }

        public float4x4 ComputeTransform(int index)
        {
            // indexに応じて適当に座標をずらす
            const int xMax = 100;
            var x = index % xMax;
            var z = index / xMax;
            return float4x4.Translate(new float3(x, 0, z));
        }
    }

    void Start()
    {
        CreateEntity();
    }

    void CreateEntity()
    {
        // デフォルトのWorldを取得
        var world = World.DefaultGameObjectInjectionWorld;
        // 対象のWorldに存在するEntityの管理はすべて、EntityManagerを通して行う
        var manager = world.EntityManager;

        // スレッドセーフなコマンドバッファ。後ほど、Job内でEntityを生成するコマンドを積むために用意
        // Jobで使用するためTempJobで生成
        EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob);

        // 変更されうるHybridRenderのコンポーネント一式が正しくEntityに追加されるよう、
        // RenderMeshDescriptionを生成
        var desc = new RenderMeshDescription(
            this.mesh,
            material,
            shadowCastingMode: ShadowCastingMode.Off,
            receiveShadows: false);

        // 空のEntityから、必要なComponentを追加して作りたいEntityを作成する。
        // RenderMeshDescriptionを使う関係上、ArcheTypeを先に作成することは難しいと思われる。
        // なおEntityManagerのAPIを使用してEntityを生成するのは、一般的には最も効率が悪いとのこと
        var prototype = manager.CreateEntity();
        
        // 対象のEntityに、HybridRendererが要求するComponentをAddする
        RenderMeshUtility.AddComponents(
            prototype,
            manager,
            desc);
        // と、LocalToWorldを追加
        manager.AddComponentData(prototype, new LocalToWorld());

        // 事前に用意しておいたプロトタイプとなるEntityを、Jobを通してランタイムで大量に生成する。
        var spawnJob = new SpawnJob
        {
            Prototype = prototype,
            Ecb = ecb.AsParallelWriter(),
        };
        // Jobをスケジュール.
        // JobQueueあたりのバッチ数はサンプル通り128。公式のScriptingReferenceによれば、単純なJobなら32-128推奨とのこと
        var spawnHandle = spawnJob.Schedule(entityCount, 128);
        
        // サンプルなので即時待ちでOK
        spawnHandle.Complete();

        // EntityCommandBufferに溜めていた処理を一気に実行
        ecb.Playback(manager);
        ecb.Dispose();
        manager.DestroyEntity(prototype);
    }
}

もう少しいじってみる

Systemを追加し、y軸を時間でSin波に沿って動かす。
問題なく動いていそう。
output.gif

code
using System;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

public partial class SinHeightSystem : SystemBase
{
    protected override void OnUpdate()
    {
        var time = Convert.ToSingle(Time.ElapsedTime);
        Entities.ForEach((ref Translation translation) =>
        {
            var pos = translation.Value;
            translation.Value.y = 5 * math.sin(math.PI * (time + (pos.x + pos.z) * 0.025f));
        }).ScheduleParallel();
    }
}

v0.51に上げてみる

v0.51がリリースされたため、上述のプロジェクトをそのままUnity2021.3.4f1に放り込んで見る。
output2.gif
良い感じ。(2020でのStatsはSceneViewも同時に描画していたためfpsが出ていませんでした)

packageの主要な差分は以下の通り:

-    "com.unity.entities": "0.50.1-preview.2",
+    "com.unity.entities": "0.51.0-preview.32",
-    "com.unity.rendering.hybrid": "0.50.0-preview.44",
+    "com.unity.rendering.hybrid": "0.51.0-preview.32",

終わりに

  • DOTSの重要パッケージの一つ、Entitiesについて触れた
  • ざっと動かすにあたり、どういった手順を踏めば動くのかの感触は得られた

次はもう少し実践的に組み込んで、使用感をチェックしたいところです。

参考資料

こちらの記事を大いに参考にさせていただきました。
https://qiita.com/simplestar/items/17b0886be0170f79aa2e

  1. 2022.06.25現在のECSインストールガイドに準拠します。Intelチップでないため動作は怪しいかも

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1