ScriptableObjectにステージ配置データを保存、復元する仕組みの話
ブログ名を少し短くしました。(自分で読みにくかった…)
ステージ読込周りで、ステージ構成データみたいなScriptableObjectを割とよくつくるので、今回はその話です。
ステージの配置パーツをつくる
まず、ステージ内に配置する対象の「キャラ」「アイテム」といった個々のパーツは、
- このコンポーネントが予め設定されている
- こういうデータ初期化が必ずされている
みたいにある程度決まった形に制限されたパーツであって欲しいので、それぞれprefabとして作ることがほとんどです。
ここまでは多くの人の共通見解だと思います。
ステージの配置パーツをどう持たせるか問題
次に、これらの配置パーツを複数組み合わせて固有のステージデータを作ることを考えます。
まず思いつくのは、
- キャラやアイテムを多数配置した、1ステージにつき1つのprefabをつくり、それをInstantiateする
- キャラやアイテムを多数配置した、1ステージにつき1つのシーンをつくり、それをシーン読込する
辺りの統合型…100%自由に配置しちゃうぜ!方式だと思います。
このアプローチで進める場合、キャラやアイテムという単位で一度固めたprefabを複製しまくって配置する、といったワークフローになるのかな…と思うのですが、これをやると数が増えたり、パーツprefabに後で変更が加わったりした場合、問題が起こりそうです。
- 複製したキャラ1000体に、実はこのコンポーネントが共通で必要だったので、手動で付け直さないといけない…
- 複製したキャラ1000体のうち1体だけ、誤って必須コンポーネント外しちゃった。ごめんねてへぺろ☆
みたいな。後日発覚する拡張問題、ヒューマンエラーなどは普通に起こりうる話で。
パーツprefab+配置のみの情報でロードするアプローチの検討
「キャラ」「アイテム」といった配置パーツは、後日更新があり得るものなので、ある時点で完成品としてコピーするのではなく、通常通りのprefab(複製元)として扱い、自動生成する仕組みが欲しくなってきます。 実行時ではなく、ステージデータの作成中(非実行時)にです。
そこで、エディタ拡張でボタンを押すとprefabとして生成する風なアプローチを考えてみました。
こんな感じのです。
- キャラやアイテムの配置情報だけをステージ配置データ(マスターデータ)として持つ
- ステージ配置データの持たせ方はScriptableObject
- インスペクタをエディタ拡張して、"Save"を押したらHierarchyからステージを読み取って位置を抽出、保存
- インスペクタをエディタ拡張して、"Load"を押したら配置データにprefabをInstantiateして復元
下準備:ステージ配置パーツprefabの用意
これを実際やってみます。 ステージ配置周りの仕様は、ザックリ以下のように決めました。
- 複数種類ある前提で試すため、CharacterとItemの2つのprefabをつくる
- Resourcesフォルダ内にprefabを入れてそこから読む
- Hierarchyウィンドウ側(実際に配置した際)は、"Stage"という空のGameObjectをつくってその下に配置
- prefab化したGameObjectには、種類を識別するためStageCharacter, StageItemスクリプトを貼り付ける
Resourcesの下にこう入れておいて
こんな風に生成されるイメージじゃ。
配置データのオブジェクト名は、キャラ名やモンスター名、アイテム名が設定されると考えられるので、貼り付けた固有スクリプトで識別します。
StageCharacter, StageItemスクリプトについても、実際にはガッツリ固有処理が入る想定ですが、今回はそこは本筋ではないので、
using UnityEngine;
public class StageCharacter : MonoBehaviour {}
using UnityEngine;
public class StageItem: MonoBehaviour {}
これで。
ステージ配置データを定義
ScriptableObjectをステージ配置データ型DataStageをつくります。
gist.github.com
クラスというよりも構造体のような状態。
DataCharacterとDataItemという子クラスの配列を持たせています。
4行目に書かれている通り、"Stage Data"という名前でアセットとして生成できるようになります。
ステージ配置データの拡張エディタ処理を定義
Editorフォルダをつくり、その下にDataStage型の拡張エディタ用クラスをつくって、
インスペクタの拡張処理を書き書き。
Hierarchy上から”Stage”という名前になっているオブジェクトを探し出し、
その子階層にある"StageCharacter"や"StageItem"コンポーネント一覧を取得。
インスペクタには、まずSave/Loadボタンを表示します。
- Saveボタンで、配置パーツ種別毎にTransformパラメータとオブジェクト名を保存。
- Loadボタンで、配置パーツ種別毎にStage配下の当該種別をクリア、再生成して位置を設定。
してます。(ボタン処理は確認ダイアログを出したほうが良いです。その辺簡易版です…)
最後にDrawDefaultInspector();でデフォルトのインスペクタを表示しています。
実際に動かしてみます。
Saveで配置情報が配列に保存され、Hierarchyから消しても、Loadでprefab再生成されます。
ステージ配置データできた!
そんな感じで、ステージ配置データを保存、復元する仕組みができました。
- 複製したキャラ1000体に、実はこのコンポーネントが共通で必要だったので、手動で付け直さないといけない…
→普通にprefabにコンポーネント付けて再Loadしたら直るよ
→スクリプトで自動生成なので、こういうヒューマンエラーは防止できるよ
ゲームフローから実際に読み込む場合は、Load部分をそのままメソッド化して、ステージ配置データのアセットを参照、Loadメソッドを呼び出せば良さげですね。