はじめに
発表されてからだいぶ経つがまだ正式版のリリースされていないECS。
ただ、最近v0.50までアップデートされた模様。
v1.0まであと少しとなっていそう?なこのあたりでそろそろ触っておこうと考えその作業ログを残す。
この記事では何をするか
- Installation & setup guideに従いECS環境を構築
- サンプルコードをざっくり読解
- 簡単なコードを記述してみる
環境
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個並べ、表示できることを確認。
ざっくり読解
なお、少し Runtime Entity Creation のコードそのものからは変更しています。
おおよそは同じです。
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波に沿って動かす。
問題なく動いていそう。
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に放り込んで見る。
良い感じ。(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
-
2022.06.25現在のECSインストールガイドに準拠します。Intelチップでないため動作は怪しいかも ↩