Skip to content

Latest commit

 

History

History
291 lines (202 loc) · 12.3 KB

README_JA.md

File metadata and controls

291 lines (202 loc) · 12.3 KB

Arch.Unity

English README is here.

Arch.UnityはC#向けのECSフレームワークであるArchをUnityに統合するための機能を提供するライブラリです。

Why not Unity ECS?

Unityには公式に提供されるEntitiesパッケージを用いることで、Untiyエディタに統合された極めて高速なECSフレームワークを使用することができます。また、他にもEntitiesに対応した物理演算や描画機能を持つパッケージが提供されています。

しかしながらEntitiesはECSフレームワークとしてやや機能過多であり、これを導入する場合はプロジェクトの構成から大きく見直す必要があります。また現時点でUnity ECSは対応パッケージが明らかに不足しており、多くの場合においてGameObjectと相互に運用を行う"Hybrid ECS"的なアプローチが必須です。正直なところ、最適化があまり重要でない小規模〜中規模のプロジェクト(特にUnity ECSの機能が不足している2Dプロジェクト)ではUnity ECSを採用するメリットはあまりないと言って良いでしょう。

それでもなお、ECS自体を採用するメリットは存在します。UnityのUpdate関数は通常のメソッド呼び出しよりも低速であり、最適化の際に多くの開発者が独自の"UpdateManager"を作成しています。また、処理とデータが一つのComponent内に混在することによる可読性の低下も問題です。これらはデータをEntity上に載せ、メソッドをSystemとして分離することで解決できます。

ArchはC#向けのECSフレームワークであり、十分な速度を持つほか、(多少のパフォーマンス低下はありますが)参照型をComponentに使用することが可能です。また、Archのコア部分はミニマルで洗練されており、Unityへの組み込みも容易です。Arch.Unityはこれにいくつかの機能・レイヤーを追加することで、Arch ECSとUnityのスムーズな統合を実現します。

セットアップ

要件

  • Unity 2022.2 以上
  • Burst 1.6.0以上
  • Collections 2.0.0以上
  • Arch 1.0.0以上
  • Arch.System 1.0.0以上

インストール

  1. Nuget For Unityを使用してArchとArch.Systemをインストールする

  1. Window > Package ManagerからPackage Managerを開く
  2. 「+」ボタン > Add package from git URL
  3. 以下のURLを入力する
https://github.com/AnnulusGames/Arch.Unity.git?path=src/Arch.Unity/Assets/Arch.Unity

パッケージ構成

namespace 説明
Arch.Unity.Conversion GameObjectをEntityに変換する機能を提供
Arch.Unity.Editor Entityを表示するHierarchyやInspectorなどのエディタ拡張
Arch.Unity.Jobs ArchのクエリとUntiyのC# Job Systemの統合
Arch.Unity.Toolkit Arch.SystemをUnityに統合する独自の機能を提供

Conversionワークフロー

GameObjectからEntityを作成するための機能としてEntityConverterコンポーネントが提供されています。これを用いることでGameObjectをEntityに変換したり、GameObjectが持つComponentをEntityに追加して同期したりなど、GameObjectとEntityの相互運用を行うことが可能になります。

Conversion Mode

Convertionには二つのモードが用意されています。

Conversion Mode 説明
Convert And Destroy GameObjectはConvert時にDestroyされます。これは後述するIComponentConverterを使用するために用いられます。
Sync With Entity 単一のEntityを生成してGameObjectと紐付け、GameObjectがDestroyされた際にEntityを削除します。また、Convert Hybrid Componentsにチェックを入れることでGameObjectの持つComponentを直接Entityに追加できます。

EntityConversion.DefaultWorld

EntityConverterが生成するEntityはEntityConversion.DefaultWorldのWorldに追加されます。EntityConversion.DefaultWorldは起動時に自動で作成され、アプリケーションと同期して自動でDisposeされます。

Component Converter

IComponentConverterを実装したMonoBehaviourはConverterとみなされ、Convert時に独自の処理を行うComponentとして動作するようになります。(IComponentConverterが実装されていない場合はHybrid Componentとして扱われます。)

以下はIComponentConverterの実装を行うサンプルです。

using UnityEngine;
using Arch.Unity.Conversion;

public class ExampleConverter : MonoBehaviour, IComponentConverter
{
    [SerializeField] float value;

    public void Convert(IEntityConverter converter)
    {
        converter.AddComponent(new ExampleComponent()
        {
            Value = value
        });
    }
}

public struct ExampleComponent
{
    public float Value;
}

GameObjectReference

Conversion ModeがSync With Entityの場合、対象のEntityにはGameObjectReferenceコンポーネントが追加されます。これを介して同期されているGameObjectを取得することが可能です。

パフォーマンス上の注意

EntityConverterはUnityのSubsceneとは異なり、実行時にEntityへの変換を行います。そのため、パフォーマンスが重要な場面やGameObjectとの同期が必要ない場面では可能な限り利用を避けることが推奨されます。

C# Job Systemとの統合

Archには並列処理を行う独自のJobSchedulerが使用されていますが、Unityではより安全かつ高速なC# Job Systemが使用できます。Arch.Unityでは独自のJobインターフェースを実装することで、ArchのクエリをJob Systemで安全に並列処理することが可能になります。

また、これらのJobはBurst Compilerと互換性があります。Burstを適用することにより列挙を極めて高速に行うことが可能になります。

IJobArchChunk

IJobArchChunkはChunk単位の処理を行うJobを作成するためのインターフェースです。Jobを作成するには、まずこれを実装した構造体を定義します。

using Unity.Burst;
using Arch.Unity.Jobs;

[BurstCompile]
public struct ExampleJob : IJobArchChunk
{
    public int ComponentId;

    public void Execute(NativeChunk chunk)
    {
        var array = chunk.GetNativeArray<ExampleComponent>(ComponentId);
        for (int i = 0; i < array.Length; i++)
        {
            var test = array[i];
            test.Value++;
            array[i] = test;
        }
    }
}

public struct ExampleComponent
{
    public float Value;
}

NativeChunkGetNativeArray<T>(int id)を呼び出すことで、Componentのデータを指すNativeArrayを取得できます。ただし、取得にはComponent固有のIdを事前にJobに渡しておく必要があります。これはArch.Core.Utils.Component<T>.ComponentType.Idなどから取得が可能です。

var world = World.Create();
var query = new QueryDescription().WithAll<ExampleComponent>();

var job = new ExampleJob()
{
    ComponentId = Component<ExampleComponent>.ComponentType.Id
};
job.ScheduleParallel(world, query).Complete();

制限事項

HPC#の制約により参照型は使用できません。また、EntityやComponentの追加/削除など、構造的変化を伴う操作は一切行えないことに注意してください。(ArchのCommandBufferはHPC#と互換性がなく、現在Jobで使用可能な専用のCommandBufferはArch.Unityでは提供されていません。)

エディタ拡張

Arch Hierarchy

Arch.UnityではWorldごとのEntityを表示するEditorWindowを提供しています。これはWindow > Arch > Arch Hierarchyから開くことができます。

Entityの名前にある数字は(Index:Version)を表します。

Inspector

Arch HierarchyでEntityを選択すると、Inspector上にそのEntityが持つComponentの一覧を表示します。これらは読み取り専用ですが、値の変更がリアルタイムで反映されるためデバッグに便利です。

また、EntityがGameObjectReferenceコンポーネントを持つ場合には同期されているGameObjectが表示されます。

Toolkit

Arch自体はSystemを提供しておらず、Arch.ExtendedでSystem用のAPIおよびSource Generatorが提供されています。これらは非常に便利ですが、Unityで利用する上ではあまり扱いやすいとは言えません。Arch.UnityではArch.SystemをUnityで扱いやすくするための独自の機能を提供します。

UnitySystemBase

Arch.UnityではBaseSystem<W, T>の代わりにUnitySystemBaseを継承してSystemを実装します。UnitySystemBaseBaseSystem<World, SystemState>を継承しており、SystemStateからはTimeDeltaTimeなどの情報を取得できます。

public class FooSystem : UnitySystemBase
{
    public FooSystem(World world) : base(world) { }

    public override void Update(in SystemState state)
    {
        
    }
}

ArchApp

ArchAppはArch.Unityで提供されるクラスで、UnityのPlayerLoop上でWorldとSystemを統合的に扱う機能を提供します。(ArchAppの設計はBevy EngineのAppにインスパイアされています。)

ArchAppを作成するにはArchApp.Create()を使用します。外部から使用するWorldを渡すことも可能ですが、指定がない場合は自動で新しいWorldを作成します。

var app = ArchApp.Create();

作成したAppにはSystemを追加できます。追加したSystemは自動的にPlayerLoop上にスケジュールされます。(後述するSystemRunnerを指定することで実行タイミングを設定することも可能です。)

app.AddSystems(systems =>
{
    systems.Add<FooSystem>();
});

app.AddSystems(SystemRunner.FixedUpdate, systems =>
{
    systems.Add<BarSystem>();
});

最後にRun()を呼び出すことでArchAppは動作を開始します。停止したい場合にはStop()を呼び出します。

app.Run();

これらはメソッドチェーンを用いて記述することも可能です。

ArchApp.Create()
    .AddSystems(systems =>
    {
        systems.Add<FooSystem>();
    })
    .AddSystems(SystemRunner.FixedUpdate, systems =>
    {
        systems.Add<BarSystem>();
    })
    .Run();

また、使用後はDispose()で破棄する必要があります。この時内部のWorldも同時に破棄されます。

app.Dispose();

SystemRunner

SystemRunnerはSystemの駆動を抽象化する機能です。Arch.UnityではPlayerLoop上で動作するいくつかのSystemRunnerと、単体テスト用のFakeSystemRunnerが用意されています。

また、ISystemRunnerを実装して独自のSystemRunnerを作成することも可能です。

public interface ISystemRunner
{
    void Run();
    void Add(UnitySystemBase system);
    void Remove(UnitySystemBase system);
}

VContainer

Arch.UnityではArchAppVContainerで扱うための拡張が用意されています。

using Arch.Unity;
using Arch.Unity.Conversion;
using VContainer;
using VContainer.Unity;

public class ExampleLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        // コンテナの構築時に新しいArchAppを作成します
        // 作成したArchAppとWorld、追加したSystemが自動的にDIコンテナに登録されます
        builder.UseNewArchApp(Lifetime.Scoped, EntityConversion.DefaultWorld, systems =>
        {
            systems.Add<FooSystem>();
            systems.Add<BarSystem>();
        });
    }
}

ライセンス

MIT License