テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】ECSでシーンをロードする様にEntity群をロードする

今回はECSにおけるEntityのセーブ及びロードについてです。
つまりGameObjectをSceneやPrefabに固めるように、Entity(Entity & ComponentData)をファイルとして書き出します。

なおUnity 2019辺りのタイミングでちゃんとしたエディターサポートが始まるので、多分今回紹介する内容は良くてローレベルAPI、悪くて産廃となりそうです。ただまぁ、一応調べたのでメモとして残しておきます。

Entityをファイルに保存

現状、Entityの生成はスクリプトから行われます。
ただ昨今の常識的なゲーム開発環境では、ゲーム内オブジェクトの構築を全てスクリプトで制御するのではなく、ある程度メタファイルに情報を書き込んでロードする形を取ります。UnityのGameObjectやScene、Prefab等がコレに該当しますが、残念ながらECSはエディターレベルでのサポートがまだないので、スクリプト全フリとなるのは仕方のない事です。 とはいえ、単一種類のEntityをランダムに配置するもの(ECSでよく見る!)なら兎も角、ちゃんとしたゲームにしたいならばゲーム内に任意のEntityを配置し、それをゲーム実行時にロードするといった事は欲しい所でしょう。

なので今回はECSで配置したEntityの情報を記録(セーブ)しておき、ロードするといった方法を紹介します。

なお幾つか制限があります。

  • Pure ECS向けの機能です。
  • GameObjectEntityをシーンに配置した(つまりEntityにTransform等への参照が有る)物には使えません。
  • GameObjectEntityをEntityManager.Instantiateしたものには使えます。

実装

Entityの生成とセーブ

まずはEntityの生成と保存をやってみます。

生成は良いでしょう。何時ものランダム生成です。問題は保存の方で、SerializeUtilityHybrid.Serializeを使用します。 最初の引数にはEntityManagerを指定しますが、この中にハイブリットな(Componentを含む)Entityが存在すると失敗します 。場合によっては処理用のWorldにEntityを移してからファイルに書き込む等の工夫が必要かもしれません。もしくはPreBuildProcessでビルドする際に色々とやるか。

gist.github.com

これを実行すると、下のようにSpawnObjectにセットしたオブジェクトが大量に生成されるので、Saveを呼び出してファイルに書き出します。

ここで重要なのは、 Saveを押したタイミングで生成されるSharedComponentsオブジェクト です。このオブジェクトをProjectビューへドラッグ&ドロップしてPrefab化します。
意外と知られていない事ですが、ゲームの再生中であってもオブジェクトをドラッグ&ドロップすればPrefab化出来ます。Prefabの上書きで「ゲーム再生中の変更をなかったコトにしない」為のテクニックの一つです。

f:id:tsubaki_t1:20181102233325j:plain f:id:tsubaki_t1:20181102233608g:plain

SharedComponentsオブジェクトの中身は保存した時に利用していたSharedComponentDataの一覧です。SharedComponentsオブジェクトは登録されているコンポーネントの並び順が非常に重要なので、保存するたびにPrefabはちゃんと更新しておくことをお勧めします。

f:id:tsubaki_t1:20181102234639j:plain

セーブしたEntityをロードする

次にファイルに書き出したEntityをロードしてみます。

EntityのロードはSerializeUtilityHybrid.Deserializeを利用します。三番目の引数のsharedDataには、セーブのタイミングでPrefab化したSharedComponentsオブジェクトを指定します。

なおロードできるのは空のWorldのみなので、空のWorldを一旦生成した後MoveEntitiesFrom異世界転移してもらう形を取ります。転移すればWorldは基本的に空になるので、一旦Worldを作成した後は使い回すのが良いです。

gist.github.com

f:id:tsubaki_t1:20181102235120g:plain

なおEntityは同期的に生成されます。

f:id:tsubaki_t1:20181103000648j:plain

セーブしたEntityを非同期でロードする

上でも書いたとおりSerializeUtilityHybrid.DeserializeのEntityのロードは同期的です。せっかく別のWorldにEntityを生成するので、今度は非同期でロード出来るように変更します。

まず最初にSerializeUtilityHybrid.DeserializeSharedComponentsでsharedDataに登録されているSharedComponentDataをEntityManagerに登録しておきます。
次にEntityをロードする部分ですが、SerializeUtility.DeserializeWorldSerializeUtilityHybrid.ReleaseSharedComponentsentityManager.BeginExclusiveEntityTransaction()で取得したExclusiveEntityTransactionを使用してる辺りから察せられる通り、非同期でも動かせます。

あとは 【Unity】ECSのEntityの作成やComponentDataの追加等の操作を別スレッドから実行する - テラシュールブログ でやったような事と同じで、非同期で別WorldにEntityを作り、最後にメインのWorldに同期してやれば、殆ど非同期でロードが完了です。

gist.github.com

f:id:tsubaki_t1:20181103001615j:plain

感想

Entityのロードでした。
今回はランダムなオブジェクトを生成しましたが、実際にはGameObject&ComponentをEntity&ComponentDataに変換して保存…とすれば、かなり良い感じに動作するのではないかなと思います。コレとか正にそのノリです

www.youtube.com

ただEntityのエディターワークフローがもう少しマシになれば多分APIも追加されるでしょうから、今回の内容は「繋ぎ」程度に考えたほうが良さそうな気がします。

関連

非同期でEntityの生成

tsubakit1.hateblo.jp

Worldという概念について

tsubakit1.hateblo.jp

ハイブリットECS

tsubakit1.hateblo.jp