13.7K Views
April 07, 25
スライド概要
皆さんは、Unityのアセットファイルの仕組みについてご存知でしょうか? 今回はアセットがどのようにシリアライズされているかを紹介し、AssetDatabase APIを利用した場合と直接読み書きした場合の比較と利用例を紹介します。
登壇者 : 佐藤 勇人 / 株式会社ディー・エヌ・エー
---------
Unity エディタ拡張 完全に理解した 勉強会
https://unity-fully-understood.connpass.com/event/347805/
動画アーカイブ : https://www.youtube.com/watch?v=9twjUwKQ-4M
DeNA が社会の技術向上に貢献するため、業務で得た知見を積極的に外部に発信する、DeNA 公式のアカウントです。DeNA エンジニアの登壇資料をお届けします。
UnityYAMLを紐解く! Unityを使わない自動化のすゝめ 佐藤 勇人 ゲームサービス事業本部開発運営統括部第一技術部 テクノロジー推進第四グループ 株式会社ディー・エヌ・エー © DeNA Co., Ltd. 1
自己紹介 佐藤 勇人 ゲームサービス事業本部開発運営統括部第一技術部 テクノロジー推進第四グループ ・横断組織所属 ・23新卒 ・プライベートでよくブログを書く © DeNA Co., Ltd. 2
0 イントロ みなさん、UnityのPrefabやSceneがどのように実現されているか知っていますか? 仕組みを理解することで以下のことなどができるようになります! ● コンフリクト解消 ● ファイル破損の解決 ● エディタ拡張を利用しないアセット操作 © DeNA Co., Ltd. ❓ ❓ 3
0 イントロ また以下の2つの手法について比較も行いたいと思います ● エディタ拡張やバッチモードでAssetDatabase APIを活用しアセットを読み書きする方法 ● Unityを用いずにアセットを読み書きする方法 公式ドキュメント曰く 「AssetDatabase APIを使用すべき」 https://docs.unity3d.com/ja/2018.4/Manual/AssetDatabase.html © DeNA Co., Ltd. 4
0 イントロ 下記のUnity Blogが非常に参考になります。 今回はこちらの内容も含めつつ、より実用を見据えたお話をしようと思います。 Unity のシリアライズ言語 YAML を理解する © DeNA Co., Ltd. 5
目次 1 アセットファイルの仕組み 2 抑えておきたいポイント 3 “アセット直接操作”と”AssetDatabase API利用”の比較 4 利用例紹介 5 まとめ © DeNA Co., Ltd. 6
1. アセットファイルの仕組み © DeNA Co., Ltd. 7
SceneやPrefabなどアセットの仕組み
1
●
アセットファイルの正体は YAMLのサブセット で記述されたテキストデータ
○
UnityYAMLというUnityに組み込まれているYAMLライブラリを使用
○
YAMLの標準仕様との違いは 公式ドキュメント を参照
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6818660400763891736
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1684944823617977893}
m_Layer: 0
m_Name: SampleGameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
…
© DeNA Co., Ltd.
8
ヘッダー
1
必ず以下の2行ではじめる
どのYAMLバージョンを利用しているか
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
「tag:unity3d.com,2011:」に対して
「!u!」というマクロを作成
とあるPrefabファイルの中身
どんなPrefabか当ててみよう!
© DeNA Co., Ltd.
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6818660400763891736
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1684944823617977893}
m_Layer: 0
m_Name: SampleGameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1684944823617977893
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6818660400763891736}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 10, y: 10, z: 10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
9
オブジェクト
1
---がオブジェクトの区切りを表す
【オブジェクト】
●
ゲームオブジェクト
●
コンポーネント
●
その他のシーンデータ
ゲームオブジェクト
コンポーネント
© DeNA Co., Ltd.
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6818660400763891736
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1684944823617977893}
m_Layer: 0
m_Name: SampleGameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1684944823617977893
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6818660400763891736}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 10, y: 10, z: 10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
10
1
オブジェクトの中身
先頭にClass IDとFile IDが記述されている
File IDはオブジェクト同士で参照で利用するので重要な要素
Class ID : どのクラスに属しているか
--- !u!1 &6818660400763891736
File ID : オブジェクト自体のID(ファイル内で一意)
© DeNA Co., Ltd.
ClassIDの一覧は公式ドキュメントに掲載
--- !u!1 &6818660400763891736
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1684944823617977893}
m_Layer: 0
m_Name: SampleGameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
11
1
シリアライズされたプロパティ
シリアライズされたプロパティは プロパティ名: 値 で表される
m_Name: SampleGameObject
GameObject名 は SampleGameObject
であるという意味
© DeNA Co., Ltd.
--- !u!1 &6818660400763891736
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1684944823617977893}
m_Layer: 0
m_Name: SampleGameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
12
1
ローカル参照
プロパティに {fileID: 値} を指定して他オブジェクトへの参照入れる
fileID: 0 はNull参照を表している
m_Component:
- component: {fileID: 1684944823617977893}
GameObject に fileID が 1684944823617977893
であるコンポーネントがアタッチされているという意味
m_Icon: {fileID: 0}
アイコンは設定されていないという意味
© DeNA Co., Ltd.
--- !u!1 &6818660400763891736
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1684944823617977893}
m_Layer: 0
m_Name: SampleGameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
13
1
実際に読んでみる
GameObjectが定義
Transformがアタッチ
GameObject名は
SampleGameObject
GameObjectはActive
© DeNA Co., Ltd.
おまじない
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6818660400763891736
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1684944823617977893}
m_Layer: 0
m_Name: SampleGameObject
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1684944823617977893
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6818660400763891736}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 10, y: 10, z: 10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
Transformが定義
GameObject に アタッチ
Position : (10, 10, 10)
Rotation : (0,0,0)
Scale : (1,1,1)
親や子のGameObjectなし
14
1 正解はこちら SampleGameObject という名前のGameObject Transformがアタッチ Activeである Position : (10, 10, 10) Rotation : (0,0,0) Scale : (1,1,1) © DeNA Co., Ltd. 15
2. 抑えておきたいポイント © DeNA Co., Ltd. 16
2 外部ファイルへの参照 例えば入れ子になっているPrefabは複数ファイルを読み込まないと構築できない 外部ファイルへの参照には.metaに記述されたGUIDを活用する 【Parent.prefab】 m_SourcePrefab: {fileID: 100100000, guid: b58ef508df66c405dbd17762ef8850db, type: 3} 【Child.prefab.meta】 fileFormatVersion: 2 guid: b58ef508df66c405dbd17762ef8850db © DeNA Co., Ltd. 外部アセットファイルを参照するときは 参照先ファイルの.metaのGUIDを指定 17
2
外部ファイルのオブジェクトへの参照
外部ファイルのオブジェクトにアクセスする際にはfileIDとguidを組み合わせる
【Parent.prefab】
target: {fileID: 1684944823617977893, guid: b58ef508df66c405dbd17762ef8850db, type: 3}
【Child.prefab】
--- !u!4 &1684944823617977893
Transform:
m_ObjectHideFlags: 0
【Child.prefab.meta】
fileFormatVersion: 2
guid: b58ef508df66c405dbd17762ef8850db
Child.prefabのTransformを参照
© DeNA Co., Ltd.
18
2
NestedPrefab と PrefabVariant と シーンでのプレハブ利用
PrefabInstanceオブジェクトを用いて参照先のプレハブのプロパティを上書きして実現
--- !u!1001 &8642938754110244128
PrefabInstance:
m_ObjectHideFlags: 0
m_Modifications にプロパティの上書き情報が記述
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 6371353098771123362}
m_Modifications:
- target: {fileID: 6674253363220435635, guid: 04b5f3589053a4270abcd8f19d3d083f, type: 3}
propertyPath: m_Name
value: 2
objectReference: {fileID: 0}
参照先Prefab と 参照先のオブジェクト
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
上書きするプロパティ名と値
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 04b5f3589053a4270abcd8f19d3d083f, type: 3}
© DeNA Co., Ltd.
※一部省略しています
19
間接的に参照しているPrefabのオブジェクトのfileID
2
間接的に参照しているPrefabのオブジェクトのfileIDは以下の計算式で算出される
(元のfileID ^ 参照先のPrefabInstanceのfileID) & 0x7fffffffffffffff
最上位ビットを0
にする
【Depth0.prefab】
- target: {fileID: 5611948040336456695, guid: 04b5f3589053a4270abcd8f19d3d083f, type: 3}
【Depth1.prefab】
--- !u!1001 &878257330087765307
5611948040336456695 ^ 878257330087765307 & 0x7fffffffffffffff
= 4742738325859180236
PrefabInstance:
【Depth2.prefab】
--- !u!4 &4742738325859180236
Transform:
© DeNA Co., Ltd.
参照
参照
20
2
間接的に参照しているPrefabのオブジェクトのfileID
【Depth0.prefab】
- target: {fileID: 4183921033801563863, guid: 7199b03a47dc342ab8fc5760053727c2, type: 3}
【Depth1.prefab】
--- !u!1001 &8642938754110244128
PrefabInstance:
4183921033801563863 ^ 8642938754110244128
^ 878257330087765307 & 0x7fffffffffffffff
= 4742738325859180236
【Depth2.prefab】
--- !u!1001 &878257330087765307
PrefabInstance:
【Depth3.prefab】
--- !u!4 &4742738325859180236
Transform:
© DeNA Co., Ltd.
参照
参照
参照
21
3. “アセット直接操作”と”AssetDatabase API利用”の比較 © DeNA Co., Ltd. 22
3 “アセット直接操作”と”AssetDatabase API利用”の比較 アセットを直接読み書き AssetDatabase APIを利用 CI/CDでの利用しやすさ ◯Unityが不要 × Unityが必要 実行速度 ◯Unityが不要 × Unityが必要/コンパイルが遅い 不要な差分の出づらさ ◯自由に作れる × UnityYAMLを使用するため 実装難易度/手軽さ × 自分でYAMLを処理 ◯ APIを利用するだけで良い 確実性/安全性 × 仕様を自分で調べるため ◯ Unityが提供 仕様変更の強さ × 自前実装のため ◯ Unityが提供/更新 © DeNA Co., Ltd. 23
4. 利用例紹介 © DeNA Co., Ltd. 24
4 CI/CDでの自動コンポーネント差し替え ● 例 : Imageコンポーネント から Imageを継承した独自コンポーネントへ差し替え ○ PRが作成されたらImageが利用されていないかチェックを行う ○ 修正が必要な場合はPRを自動作成 自動で別コンポーネントへの差し替え GitHub Actions等で自動修正 © DeNA Co., Ltd. 25
5. まとめ © DeNA Co., Ltd. 26
まとめ 5 ● アセットファイルは YAMLのサブセット のテキストデータ ○ ● 仕様を理解する事でUnityを用いずに読み書きができるようになる ○ ● UnityYAMLというYAMLライブラリを使用して読み書きする コンフリクト解決/ファイル破損の対応でも便利なので覚えて損はない デメリットを理解し、許容できる場合のみアセットを直接操作したい ○ © DeNA Co., Ltd. AssetDatabase APIだけで問題ないなら素直に活用した方がよい 27
5 © DeNA Co., Ltd. さいごに 28
© DeNA Co., Ltd. 29