Project KURUKURU

ゲームプログラミングについて、興味を持ったものを雑多に取り上げるブログです

Unity ECS & C# Job System その1

はじめに

こんにちはこんばんは。繰繰廻(くるくる・めぐる)です。

初めましてから期間が空きすぎですね。最初からこんな調子で大丈夫なのでしょうか。

今回やっていくのはUnity2018で追加された新機能のECS(Entity Conponent System)および C# Job System について、解説していこうかなと思います。

今回はECSの概念と、とりあえず球体を描画するまでをやっていきます。

 

 

なんぞそれ?

これまではMonoBehaiverに依存したオブジェクト指向的な設計だったUnityが、オブジェクト指向の問題点であるCPUのキャッシュちゃんと使いましょうよ問題に目を向けて、データ指向のプログラミングが可能な仕組みを提供してくれました。それがECSです。

そして、要素が1列に並んでるデータ指向のおかげで、GPGPUのような並列処理が可能になりました。C# Job SystemではGPUではなく、CPUの余っているコアを使って自動で並列処理(マルチスレッド化)をしてくれます。

Unity5.4くらいから脳みそが止まっている自分にとっては「自分の知っているUnityとは違う……」と感じさせられるほどの超絶最適化です。

 

そもそもデータ指向とは?

データ指向って何ぞやというお話は、自分が書くよりも下記ブログが参考になります。

データ指向設計 | Cygames Engineers' Blog

 

要するに、できるだけ連続してメモリを読めるようにしようねというところです。

こういうとややこしく見えますが、最も身近で単純な例を示すとこんな感じです。

for (int i = 0; i < kMaxI; i++)
{
    for (int j = 0; j < kMaxJ; j++)
    {
        auto test = array_test[i][j];
    }
} 

二次元配列があるときに、当たり前のように後ろの添え字を内側のfor文の中に記します。普通以下のようには書きません。

for (int i = 0; i < kMaxI; i++)
{
    for (int j = 0; j < kMaxJ; j++)
    {
        auto test = array_test[j][i];
    }
}

連続したメモリを読んだ方がキャッシュを有効に使えますからね。

 

ECSとは?

ただしこのデータ指向、言うのは簡単ですが実際に作ってみるとすごく難しいです。

特に

・要素をどうやって削除するか?

オブジェクト指向でいう派生クラス(要素を足して振る舞いを変えたい)をどうやって作るか?

 の2点がハードルかなと思います。これをいい感じに簡単にUnityで作れるようにしてくれているのがECSです。

 

ECSの用語

ECSには新しい用語がいくつも出てくるので、それの整理からしてみます。

Entity

これがオブジェクト指向でいうところの個々のクラス(オブジェクト)のイメージでしょうか。ただしクラスとは違って、型は定義されず、Componentの集合でEntityが定義づけられます。

Component

Entityを構成する要素の一つです。Componentが一つでもあればEntityとして定義できます。

上記データ指向のブログの例では、_positionや_velocityや_aiStateがComponentであり、EnemyMoveやEnemyAiがEntityです。

System

この存在がオブジェクト指向とは明らかに異なるところです。一般的にいうところの関数がかろうじて一番近いでしょうか……?

普通の関数であれば、関数の呼び出し元が引数の値や呼び出しタイミングを定義しますが、Systemはそのような仕組みではなく、引数(関数の処理に使う値)は、「このSystemはこのEntityを使う!」と定義するだけで、(そのEitityが存在すれば)自動で設定されて、自動でSystemが動きます。

factorioでたとえると、何か処理をする機械とインサータといろんなものが大量に入ったチェストが一列に並んでいるとき、その機械が使うものだけを自動で取り出してくれる……みたいな感じでしょうか。機械がSystem、いろんなものがComponent、インサータがSystemを自分で定義したときに出てくる[Inject]のイメージです。(誰がわかるんだこの例え)

 

ECS実装……の前に

ECSもC# Job System も、Unity2018で追加された機能と言いましたが、どちらもまだBeta機能です。これからまだまだ変化していく可能性があります。

今回実装したバージョンは「Unity 2018.2.0f2」です。

Beta機能を使うためには、Projectフォルダ直下のPackages内のmanifest.jsonを、以下のサンプルの内容に置き換える必要があります。

また、.NETのバージョンの変更も必要です。

PlayerSettingの下図の項目を.NET 4.x Equivalent に変更します。

f:id:kurukuru_meguru:20180729235532j:plain

以上で、ECSを使う準備が整いました。using Unity.Entities;が使えれば成功です。

時々上記設定をしてもうまくいかないときがあります。その時はプロジェクトを開いたときに自動生成されるファイル(.vs,Library,obj)を削除してみてください。

 

 

ECS実装

まずできるだけスクリプトを書かずに(Unityが用意してくれている機能を用いて)、球体を一つ出してみようと思います。

空のGameObjectを作成して、以下のComponentをInspectorからAdd Componentします。

・Mesh Instance Renderer Component

 次に示すTransform Matrix Componentの行列をもとに、登録したメッシュとマテリアルで描画をします。

・Transform Matrix Compoennt

 ワールド変換行列(float4x4)をスクリプトで入力すれば、エンティティがワールド空間に配置されます。ただし、行列に値をセットするのは大変なので、次のコンポーネントも追加します。

・Position Component

 position情報をTransform Matrix Component に渡してくれます。親戚にRotation Component があります。

 

これらを追加し終えた段階でこのようになります(Game Object Entityは自動で追加されます)。

f:id:kurukuru_meguru:20180730231518j:plain

次に、Mesh Instance Renderer Component のSerialized Dataを展開して、Meshに好きなメッシュを(今回はSphereを登録します)、Materialに「インスタンシングされた」マテリアルを追加します。マテリアルのインスタンシングは、マテリアルの項目の一番下のEnable GPU Instancingをオンにするとできます。

 

f:id:kurukuru_meguru:20180730223716j:plain

 

この2項目をセットすると、このようになります。

f:id:kurukuru_meguru:20180730231947j:plain

 

次に、Position ComponentのSerialized Dataを展開すると、XYZの値が表示されます。ここに好きな値を設定して、ワールド空間での座標を決定します。

MonoBehaiverでこれまで使用してきた、一番上のTransformは一切意味がありません

 

ここまで設定すると、シーンのプレビューでもプレイでも球体が描画されます。

 

次の記事へ

記事が長くなってきたので、このあとは次の記事でご説明いたします。

Systemはまだ出てきていませんが、Unity付属のComponentを使用してきたので、SystemもUnityが標準で用意してくれたものが既に動いています。

次の記事ではSystemの作成と複数の球体のスポーンまではやります。C#Job Systemについてはさらにその次になるかも……?