今回は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でビルドする際に色々とやるか。
これを実行すると、下のようにSpawnObjectにセットしたオブジェクトが大量に生成されるので、Saveを呼び出してファイルに書き出します。
ここで重要なのは、 Saveを押したタイミングで生成されるSharedComponentsオブジェクト です。このオブジェクトをProjectビューへドラッグ&ドロップしてPrefab化します。
意外と知られていない事ですが、ゲームの再生中であってもオブジェクトをドラッグ&ドロップすればPrefab化出来ます。Prefabの上書きで「ゲーム再生中の変更をなかったコトにしない」為のテクニックの一つです。
SharedComponentsオブジェクトの中身は保存した時に利用していたSharedComponentDataの一覧です。SharedComponentsオブジェクトは登録されているコンポーネントの並び順が非常に重要なので、保存するたびにPrefabはちゃんと更新しておくことをお勧めします。
セーブしたEntityをロードする
次にファイルに書き出したEntityをロードしてみます。
EntityのロードはSerializeUtilityHybrid.Deserialize
を利用します。三番目の引数のsharedDataには、セーブのタイミングでPrefab化したSharedComponentsオブジェクトを指定します。
なおロードできるのは空のWorldのみなので、空のWorldを一旦生成した後MoveEntitiesFrom
で異世界転移してもらう形を取ります。転移すればWorldは基本的に空になるので、一旦Worldを作成した後は使い回すのが良いです。
なおEntityは同期的に生成されます。
セーブしたEntityを非同期でロードする
上でも書いたとおりSerializeUtilityHybrid.Deserialize
のEntityのロードは同期的です。せっかく別のWorldにEntityを生成するので、今度は非同期でロード出来るように変更します。
まず最初にSerializeUtilityHybrid.DeserializeSharedComponents
でsharedDataに登録されているSharedComponentDataをEntityManagerに登録しておきます。
次にEntityをロードする部分ですが、SerializeUtility.DeserializeWorld
とSerializeUtilityHybrid.ReleaseSharedComponents
はentityManager.BeginExclusiveEntityTransaction()
で取得したExclusiveEntityTransactionを使用してる辺りから察せられる通り、非同期でも動かせます。
あとは 【Unity】ECSのEntityの作成やComponentDataの追加等の操作を別スレッドから実行する - テラシュールブログ でやったような事と同じで、非同期で別WorldにEntityを作り、最後にメインのWorldに同期してやれば、殆ど非同期でロードが完了です。
感想
Entityのロードでした。
今回はランダムなオブジェクトを生成しましたが、実際にはGameObject&ComponentをEntity&ComponentDataに変換して保存…とすれば、かなり良い感じに動作するのではないかなと思います。コレとか正にそのノリです
ただEntityのエディターワークフローがもう少しマシになれば多分APIも追加されるでしょうから、今回の内容は「繋ぎ」程度に考えたほうが良さそうな気がします。
追記:こちらの機能でワークフロー対応したSubSceneが追加されました。
関連
非同期でEntityの生成
Worldという概念について
ハイブリットECS