見出し画像

Unity2022.2からのECS入門【Advent Calendar 12/6】

はじめに

この記事は Colorful Palette アドベントカレンダー 12/6 の記事です。
株式会社Colorful Paletteでクライアントエンジニアをしているふぁいたーです。ゲーム全体の設計やシステムの構築を業務としております。

今回は遂にUnity2022.2以降で正式バージョンになるECS(Entity Component System)について記載したいと思います。
※本検証ではBeta版のUnityを使用するのと、まだ実験段階中の機能であるため、機能が正式にリリースされたとき内容が異なる場合があります。

ECSとは

ECSについて、Wikipediaには以下のように書かれています。

主にゲーム開発で使用されているソフトウェアアーキテクチャ パターンである。ECSは継承よりコンポジション の原則に従うことで、より柔軟にエンティティを定義することを可能にする。エンティティとは、ゲームのシーンの中のすべての実体であるオブジェクトのことである(例えば、敵、銃弾、乗り物など)。すべてのエンティティは、付加的な振る舞いや機能を追加するものである1つ以上のコンポーネントから構成される。したがって、エンティティの振る舞いは、実行時にコンポーネントを追加あるいは削除することで変更可能である。これは、深く幅広い継承階層を除去し、その理解・保守・拡張が難しくなりあいまいになるという問題を取り除く。ECSの一般的なアプローチはデータ指向設計 の手法と高い互換性を持ち、よく組み合わせられる。

https://ja.wikipedia.org/wiki/エンティティ・コンポーネント・システム

ゲームクライアント側の比較的新しいアーキテクチャで1998年のゲームが最古みたいです。
それがUnityでも扱えるようになり、うまく使用できれば既存のコンポーネント志向よりずっと高いパフォーマンスを出すことができます。
より詳細な説明はUnityの公式ドキュメントを参照していただくと良いと思います。
https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/index.html
https://learning.unity3d.jp/3266/
今回はHybridECS(MonoBehaviourとECSの併用する使い方)を始める際の
Entity, Component, System それぞれの作り方から紹介していきます。
この記事ではPureECS(ECSのみでゲームを構築する)は含みません。

検証環境

  • Unity 2022.2.0b12

  • Windows 10 Pro 11th Gen IIntel(R) Core(TM) i9-11900K

必要機能のインポート

Unity2022から実験段階のパッケージはPackageManagerの一覧に表示されなくなっているのでAdd package by nameから以下のパッケージ名を入力します。

  • com.unity.entities 1.0.0-exp.12

  • com.unity.entities.graphics 1.0.0-exp.14

  • com.unity.physics 1.0.0-exp.12

  • com.unity.render-pipelines.universal 14.0.3

※UniversalRenderPipelineのセットアップが必要です
https://docs.unity3d.com/ja/2019.4/Manual/universal-render-pipeline.html

Entityの作成

Entityを作成するにはSubSceneを作成するのが楽です。
画像にメニューからEmptySceneを選択し、この記事ではSubScene.unityとして保存します。
先程作成したSubScene以下にCubeを作成します。

Inspectorウィンドウを確認すると、このEntityが持つComponentが確認できます。このままではGameウィンドウにキューブが表示されませんが一度プレイモードに移行することでSubScene以下に存在するオブジェクトをEntityへ変換する処理が行われ、Gameウィンドウにも表示されるようになります。ここまでで表示の確認が行えたかと思います。
ECSはよく大量のオブジェクトを高速に処理ができると言われるので、今回のサンプルでは動的に50万個のキューブを生成しランダムに回転を行おうと思います。
また、コンポーネント志向でやった場合での比較もできればと思います。

Componentの作成

早速System側で扱うために、Componentを用意します。
また、このコンポーネントをSystem側で使用できるようにEntityにコンポーネントとして登録する処理も記載します。
このサンプルではComponentと、登録を行うAuthoringコンポーネントを別のファイルで記述しますが1つのファイルにまとめて記述しても問題ありません。

using Unity.Entities;

[System.Serializable]
public struct SpawnComponent : IComponentData
{
	public Entity prefab;
}
using Unity.Entities;
using UnityEngine;

/// <summary>
/// このMonoBehaviourコンポーネントをSubSceneにあるEmptyオブジェクトにアタッチして下さい
/// </summary>
public class SpawnAuthoring : MonoBehaviour
{
	[SerializeField]
	private GameObject _prefab;
	
	/// <summary>
	/// Entityへの変換処理を記述します。
	/// これはゲーム再生時に実行されます。
	/// </summary>
	class Baker : Baker<SpawnAuthoring>
	{
		public override void Bake(SpawnAuthoring authoring)
		{
			// アタッチされたオブジェクトに事前に要していたコンポーネントを追加します。
			AddComponent(new SpawnComponent()
			{
				// GetEntityを使用することで、MonoBehaviourオブジェクトをEntityに変換し、ECSコンポーネントに設定します。
				prefab = GetEntity(authoring._prefab)
			});
		}
	}
}

コード上のコメントでも記載しましたが、SubScene以下にEmptyなオブジェクトを作成し、SpawnAuthoringをアタッチしてください。
すると画像のようにコンポーネントが登録されるはずです。

あとはこのコンポーネントを処理するためのSystemを作成します。

Systemの作成

次にキューブを生成するためのSystemを生成します。
Systemは通常自動で追加されますが、[DisableAutoCreation]を適用することで、自動登録を無効化できます。
今回のサンプルでは自動でSystemを登録したいため、使用していません。

using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

/// <summary>
/// SystemBaseを継承することで、システムとして登録することができます
/// partialにしているのはUnity側で自動生成される処理をこのクラスに適用するために必須です
/// </summary>
public partial class SpawnSystem : SystemBase
{
	/// <summary>
	/// 毎フレーム実行されます
	/// </summary>
	protected override void OnUpdate()
	{
		Entities
			// すべてのEntityからSpawnComponentを持っているEntityを探し、以降の処理に通します
			.WithAll<SpawnComponent>()
			// Entitiesを動的に増やす必要があるため、構造の変更を許可します
			.WithStructuralChanges()
			// 上記条件に当てはまったEntityすべてに対して処理を行います。
			.ForEach((Entity entity, ref SpawnComponent component) =>
			{
				// Entityを生成します
				for (int y = 0; y < 50; ++y)
				{
					for (int z = 0; z < 100; ++z)
					{
						for (int x = 0; x < 100; ++x)
						{
							Entity instantiateEntity = EntityManager.Instantiate(component.prefab);
							EntityManager.SetComponentData(instantiateEntity, new Translation()
							{
								Value = new float3(x,y,z) * 1.5f
							});
						}
					}
				}

				// 生成処理が終わったら、コンポーネントを削除します
				EntityManager.RemoveComponent<SpawnComponent>(entity);
			})
			.Run();
	}
}

ここまでできると、画像のようにキューブが50万個できたと思います。
ただ、このままだと動かせてないので、最後に生成したキューブに回転用のコンポーネントを適用し、システムでランダムに回転をさせます。

using Unity.Entities;

[System.Serializable]
public struct SpawnComponent : IComponentData
{
	public Entity prefab;
}

[System.Serializable]
public struct RotationComponent : IComponentData
{
}

流石に数が多いのか1FPSしか出ていませんが、ここまでの大量のオブジェクトを動かせること自体凄いことです。

MonoBehaivourとの比較

ここでは従来のMonoBehaviourでの実装だとどうなるかの比較もしておきたいと思います。

残念ながら従来の実装だと50万個の生成はしましたが、動作が固まってしまいました。。。
一応5万個まで減らすと、動作はして6FSP前後でした。
ECSの方を5万個まで減らした場合は12FPS前後でした(すごい)
もちろんゲーム内でここまでの数のオブジェクトを出すことはなかなか無いですし、実際に使うにしてももっと工夫(JobSystemと組み合わせるとか)が必要かなと思いますが、それらの解説はまた別の機会にしようと思います。

おわりに

今回は2022.2からのHybridECSの使い方をサラッと追ってみました。
最近はスマートフォンゲームもリッチになってきており、より多くのオブジェクトを高速に処理する上でECSは非常に強力な武器になりそうです。
事前にEntityとして作成しておいて、アセットとしてロードするなどの部分はまだ整備されていないみたいですが今後より使いやすくなっていくでしょうから、Unityを使用しているいち開発者としては非常に楽しみです。

明日はばーやんさんのUI演出周りに関してです!ゲームでは当たり前のように使用される技術ですがそれがどのように作られているのか、お楽しみにしてください!
Colorful Palette アドベントカレンダー は12/25までの平日+α更新となっております。ぜひ引き続きご覧ください!