レベルフォーマット
レベルはフォルダーです:中身はlevel.jsonまたはlevel.blob、メタデータはmetadata.jsonまたはmetadata.blob、それにカバーとメディアファイル。バックアップ、記録、プレイヤーの設定はデバイスに残ります
レベルフォルダー
レベルフォルダー内のファイル名はすべてFileNamesで固定されています:
| ファイル | モデル | 中身 |
|---|---|---|
level.jsonまたはlevel.blob | Level | 内容:設定、オブジェクトとイベント、オーディオ、リソース、ヒント |
metadata.jsonまたはmetadata.blob | LevelMeta | タイトル、説明、作者、ライセンス、各リソースの記録、カバーへの参照 |
logo.pngまたはlogo.jpg | - | カバー |
| オーディオトラック、画像、フォント | - | レベルが参照するメディアファイル |
拡張子がそのままフォーマットです。ファイルの中にはどこにも書かれていません。読み込む側は、level.jsonとlevel.blobのどちらが存在するかを見ます
レベルとメタデータは、それぞれ独立にフォーマットを選びます。たとえば組み込みレベルnew-zero-demoは、level.blobとmetadata.jsonを並べて持っています
metadata.jsonはあえて別ファイルにしています。カタログは1000個のレベルを、1000個の小さなメタデータファイルから表示します。そのためにレベルを1つも開く必要はありません
リソース
リソースはuriとuri_type(ResourceUriType)の組で指定します:
| 値 | 種類 | ファイルの場所 |
|---|---|---|
| 1 | LevelPath | レベルフォルダーの中。この種類だけがレベルを持ち運び可能にする |
| 2 | AbsolutePath | このデバイス上の別の場所 |
| 3 | DirectUrl | ネットワークからダウンロード |
| 4 | StreamingAssets | レベルではなくゲームに同梱 |
AbsolutePathは、曲を元にレベルを作成したときに生じます。アーカイブをエクスポートすると、そのようなファイルは中にコピーされます。詳細:アーカイブと保護
カバーは、他のリソースと同じくLevelMeta.LevelLogoから取得できます。logo.pngになるかlogo.jpgになるかは、元の画像の名前ではなく、ファイル自体のバイトで決まります
カバーの作者情報の記録は、resource_typeが9(LevelLogo)のResourceMetaです。resource_idもresource_guidも入っていません。レベルのカバーは1つなので、種類だけでアドレスが完全に決まります
AIに関する申告
LevelMetaと各ResourceMetaは、AiGeneration型のai_generatedを持ちます:
| 値 | 名前 | 意味 |
|---|---|---|
| 0 | NotSpecified | 何も申告されておらず不明。デフォルト値で、キーがない場合もこの値として読まれる |
| 1 | No | AI生成を使わずに作ったと申告 |
| 2 | Yes | AI生成だと申告 |
LevelMetaの値は、レベル自身の内容だけを対象とします。各リソースとカバーは、それぞれ自分の記録で申告します。レベルにAIコンテンツが含まれるかどうかは保存されません。それに答えるのはLevelMeta.ContainsAiContent()で、数えるのはYesだけです
レベルと一緒に移動しないもの
これらのファイルはlevelsフォルダーの隣に置かれ、レベルフォルダーの中には決して入りません。レベルをアーカイブしても、送っても、削除しても影響を受けません:
| フォルダーまたはファイル | 内容 | 残る理由 |
|---|---|---|
backups/ | エディターの自動保存。レベルIDごとのフォルダーにbackup_level_<time>.<extension> | 守る対象のフォルダーの中にあるコピーは、そのフォルダーと一緒に削除されてしまう |
stats/ | LevelIdごとの記録と進行状況、statistics.json | 送られたレベルが最初からクリア済みで届いてしまう |
settings.json | UserSettings、デバイス全体に対するプレイヤーの設定 | レベルではなくプレイヤーのもの |
resources/themes、effects、shapes、prefabs | デバイスの共有ライブラリ | デバイス上のすべてのレベルで共有される |
reports/ | 診断レポート | - |
JSONのキー
JSONは常にインデントなしのコンパクトな形で書かれます。目で読むときは、テキストエディターで整形してください
キーの長さは、そのキーがレベル内に何回現れるかで決まります:
- ファイルに1回だけ現れるキーは、
snake_caseの完全な単語で書きます:level_id、min_generation - 何千回も繰り返されるキーは数文字に短縮します:
objs、f、e、v
キーはレベルファイルのバイト数の52%を占めるので、これは好みの問題ではなくルールです。すべてのキーはNames.csで宣言されています
ポリモーフィックな値は[tag, data]として書かれます。プレーンな文字列は[0,{"v":"Author"}]、ローカライズされた文字列は[1,{"strs":[...]}]です
ObjectIdのようなIDのラッパーは、そのままの数値または文字列として書かれます
ID
整数IDの符号には意味があります。0は常に「未設定」を意味します:
| ID | JSONのキー | 正 | 負 |
|---|---|---|---|
ObjectId | id、親はpid | レベルのオブジェクト | ゲームのオブジェクト:-1はカメラ、-2はローカルプレイヤー、-3はプレハブのテンプレートのルート |
| リソースID | txid、fnid、auid、byid、ttid | ゲームに同梱されたリソース | レベル独自のリソース |
オーディオトラックのAudioId | aid | このレベルのトラック | 使われない |
pidが0なら、オブジェクトに親はありません。-3より小さい数はゲームの実行中にだけ存在し、ファイルには書かれません
Marker、Checkpoint、BeatSegmentにはIDがありません。そのアドレスは時間上の位置です。マーカーとチェックポイントはフレームf、ビートセグメントはそのスパンspの最初のフレームです(セグメントは重なりません)。こうしたオブジェクトを動かすと、アドレスも変わります
プレハブのオーバーライドは、フィールドをJSONのキーではなく番号で指定します。配置はオーバーライドをmodリストに持ちます。各オーバーライドのkeyは3つの数を持ちます:
id:レベル内のコピーではなく、テンプレート内のオブジェクトf:フィールドの恒久的な番号i:リストフィールドの要素、またはフィールド全体なら-1
番号はフィールドに永久に結び付いています。そのフィールドのJSONキーを変更しても、すでにレベルに保存されたオーバーライドは壊れません
外側の「g」
シリアライズのルートはすべて、2つのキーを持つエンベロープで包まれています:
{"g":2,"v":{"level_id":"18df5f61-3aa4-4812-bf69-d357f2201bc3","vrs":"1.0", ... }}
gはモデルの世代(Names.Generation)、vはデータです
エンベロープは入れ子になります。Levelの中では、LevelSettings、GameLevel、AudioLevel、LevelResources、LevelHintsがそれぞれ自分のエンベロープを持ちます。世代の仕組み:バージョン管理とマイグレーション
gとvrsは別の数です。vrsは作者が付けるレベルのバージョン(LevelMeta.LevelVersion)で、フォーマットとは関係ありません。gを手で書き換えないでください。ビルドが知っているより大きいgを持つファイルは、まったく読み込まれません