人力Prefab作成入門 ～Unityを使わずPrefabを作る～


本記事は[サムザップ #1 AdventCalendar 2020 #1](https://qiita.com/advent-calendar/2020/sumzap1)の12/12の記事です。

前日の記事は@higuchi_yuta さんの[【Unity】チュートリアル実装で大活躍！逆マスクを作るUnmaskForUGUI【uGUI】](https://qiita.com/higuchi_yuta/items/6e4958ea24b9593ab5fc)でした。

## はじめに
みなさんは**Unityの力を用いずに自力でPrefabを作成してみたい！**と思ったことはないでしょうか？



ちなみに私はありません。
本記事では、Unityにおいて必須機能の**Prefab**について、Unity外から、具体的に言えば**テキストエディタから**作成する方法について紹介します。

Unity外からPrefabを作成する意味はほぼ皆無ですが、Prefabを理解することでgitのDiffから把握できる情報は増えるかもしれません。

なお、UnityのバージョンはLTS版である 2019.4.16f1 を用います。

## Prefabの中身を理解する
テキストエディタからいきなり作るのは難しいため、まずはPrefabの中身について見ていきます。

PrefabはYAML形式で記述されています。
YAMLとは、クラスのインスタンスなどの構造化されたデータを人間から読みやすいように表現したフォーマットです。
詳しく知りたい方は以下を参照してください。
- https://magazine.rubyist.net/articles/0009/0009-YAML.html
- https://yaml.org/spec/1.2/spec.html

### 最も単純なGameObjectの作成
まず、単一のオブジェクトのみの最も単純なGameObjectの中身を見てみます。
手順は以下の通りです。

1. ヒエラルキーでシーン中にGameObjectを作成
2. そのままPrefab化
3. 2.のPrefabをテキストエディタで開く（シンタックスハイライトがつくIDEで開くのがオススメです）

すると、以下のYAMLが表示されます。

```yaml
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6087850051989556154
GameObject:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  serializedVersion: 6
  m_Component:
  - component: {fileID: 2636687155612065025}
  m_Layer: 0
  m_Name: GameObject
  m_TagString: Untagged
  m_Icon: {fileID: 0}
  m_NavMeshLayer: 0
  m_StaticEditorFlags: 0
  m_IsActive: 1
--- !u!4 &2636687155612065025
Transform:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 6087850051989556154}
  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
  m_LocalPosition: {x: 0, y: 0, z: 0}
  m_LocalScale: {x: 1, y: 1, z: 1}
  m_Children: []
  m_Father: {fileID: 0}
  m_RootOrder: 0
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
```

これについて軽く解説をしていきます。
全体を見ると、このYAMLは"---"で区切られて各情報がまとめられています。

```yaml
# ヘッダ情報
%YAML 1.1
...

# GameObject情報
--- !u!1 &ddddd
GameObject: 
...

# Transform情報
--- !u!4 &ddddd
Transform: 
...
```

そのため、Prefabの情報は大きく分けて以下の3つに大別できます。

1. ヘッダ
2. GameObjectの情報
3. コンポーネント情報(ここではTransformコンポーネント)

ここから、Prefabの各項目について解説していきます。

### ヘッダ

```yaml
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
```

これがない場合、他のUnityプロジェクトで使おうとした場合に正常に読み出せません。

### GameObject
```yaml
--- !u!1 &6087850051989556154
GameObject:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  serializedVersion: 6
  m_Component:
  - component: {fileID: 2636687155612065025}
  m_Layer: 0
  m_Name: GameObject
  m_TagString: Untagged
  m_Icon: {fileID: 0}
  m_NavMeshLayer: 0
  m_StaticEditorFlags: 0
  m_IsActive: 1
```

まず最初の行`--- !u!1 &6087850051989556154`文字列について見ていきます。

YAMLでは、`—-`から始まる行は**ドキュメントマーカー**と呼び、Unityではこれを**オブジェクト（コンポーネント）の境界**として用います。

また、`!u!`の後の最初の数値はクラスを表し（この場合ではGameObject）、続く`&`の後の数値はこのオブジェクトの**fileID**(後述)を表します。

ドキュメントマーカー以下にはGameObject本体、名前やタグの情報などに加えて、

```yaml
...
  m_Name: GameObject    # 名前
  m_TagString: Untagged # タグ情報
...
```

付与されているコンポーネントへの参照情報が**fileID**を通じて記述されています。

```yaml
  m_Component:
  - component: {fileID: 2636687155612065025}  # 複数ある場合は下に - component: {fileID: xxxx} と続く
```

なお、各行は

```yaml
変数名: 値
```

で記述されており、それぞれがクラス（ここではGameObject）のメンバ変数に対応しています。

### fileID

Prefab内における各GameObject、コンポーネントへの参照関係はfileIDを使って保持しています。
それぞれのObjectのfileIDは、`---!u! &` 以下に記述されています。

例えば、上記TransformコンポーネントのfileIDは`2636687155612065025`です。
参照関係を保持するため、一つのPrefab内におけるfileIDはユニークでなければなりません。

逆に言えば、ユニークでさえあれば**fileIDは適当に設定しても問題ありません**。

なお、適当に設定してokとは言いましたが、多少の制約はあるようで、fileIDを`0 or 1`に設定するとUnityが**クラッシュ**します。

### コンポーネント

最後に、コンポーネントの情報です。

```yaml
--- !u!4 &2636687155612065025
Transform:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 6087850051989556154}
  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
  m_LocalPosition: {x: 0, y: 0, z: 0}
  m_LocalScale: {x: 1, y: 1, z: 1}
  m_Children: []
  m_Father: {fileID: 0}
  m_RootOrder: 0
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
```

ここには、コンポーネントの各情報が記述されます。

まずGameObject同様、ドキュメントマーカーが記述されていますが`!u!`の後の数値が異なることが分かります。（今回はTransformコンポーネントなので`4`）

本体の記述部には、TransformコンポーネントらしくRotationやPosition、Scaleに加えて、子オブジェクト(Transform)への参照が記述されています。

```yaml
...
  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} # Rotation
  m_LocalPosition: {x: 0, y: 0, z: 0}       # Position
  m_LocalScale: {x: 1, y: 1, z: 1}          # Scale
  m_Children: []                            # 子への参照
...
```

また、それに加えて、付与先のGameObject情報がfileID(後述)を介して記述されています。

```yaml
...
  m_GameObject: {fileID: 6087850051989556154}
...
```

## Prefabの最小構成を模索

さて、これまでPrefabの構成をざっくり解説してきましたが、実際に自分で書くにはいささか行数が多いように見えます。
そこで、Prefabの無駄な行を減らすことを考えてみます。

なお、YAML上で値の指定がない変数は**デフォルト値**が設定されるようです。
そのため、デフォルト値が設定されている行をエラーが出なくなるまで削除していきます。

その結果が下のPrefabです。

```yaml
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6087850051989556154
GameObject:
  m_Component:
  - component: {fileID: 2636687155612065025}
--- !u!4 &2636687155612065025
Transform:
  m_GameObject: {fileID: 6087850051989556154}
```

大分スリムになったと共に、本当に必要な情報はGameObjectとコンポーネントの**双方への参照**であるということが分かりました。（※少なくとも最小のGameObjectにおいては）

なお、GameObjectやTransform自体の情報を削除してしまうと「Prefabが壊れています」といったニュアンスのエラーが出ます。

## 実際に作ってみた

では実際にテキストエディタからPrefabを作成してみます。
まず、空のファイルを作成し、"(任意の文字列).prefab"とリネームします。
Windowsの場合はwarningのポップアップが出ますが無視しましょう。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/960b0e4d-6656-235a-dfbf-8e9b68a672be.png)

次に、テキストエディタで作ったPrefabを開きます。

開いたらまず、YAMLのヘッダ情報を記述します。

```yaml
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
```

これを書かない場合、そもそもPrefabとして認識されない、UnityからPrefabのバージョンが異なるという警告が表示される、などが発生します。

次はGameObjectの情報を記述します。

```yaml
--- !u!1 &GameObjectのfileID
GameObject:
  m_Component:
  - component: {fileID: TransformのfileID}
```

まず、オブジェクトのドキュメントマーカーを記述します。

今回はGameObjectを記述したいので、`!u!`の後の数値は`1`を指定してください。

次に、Transformの情報を記述します。

```yaml
--- !u!4 &TransformのfileID
Transform:
  m_GameObject: {fileID: GameObjectのfileID}
```

ドキュメントマーカーの記述の際、Transformの場合は`!u!`の後の数値は`4`を指定する必要があることに注意してください。

最後に、GameObjectのfileIDとTransformのfileIDを記述して完成です。

今回は適当にそれぞれ256と512を指定しました。

```yaml
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &256
GameObject:
  m_Component:
  - component: {fileID: 512}
--- !u!4 &512
Transform:
  m_GameObject: {fileID: 256}
```

これをUnityプロジェクトのAssets以下に配置して読ませてみます。

![Untitled (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/9a6a2e02-4bdd-9c25-4d44-5a0c7a704dac.png)

無事インポート出来ました。

また、Debugで表示すると、fileID（Inspector上ではLocal Identifier In File）も上記で設定した通り、256と512になっていることが分かります。

![Untitled (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/81f535ea-dbc5-e95b-2c1c-94adb0cd2f42.png)


## 最後に
今回、Prefabの構造のざっくりした解説とテキストエディタからPrefabを作成しました。
繰り返しますが、テキストエディタからPrefabを作成する意味はほぼありませんが、結果的にプルリクからPrefabの変更点を多少読み取れるようにはなりました。

ここまで読んでいただきありがとうございました。

明日は、@s1na9ak1さんの記事です。
また、サムザップのアドベントカレンダーは人数の関係上2つあります。
こちらもよろしくお願いいたします。
[サムザップ #2 Advent Calendar 2020 - Qiita](https://qiita.com/advent-calendar/2020/sumzap2)

参考にしたページ：
- https://www.shibuya24.info/entry/2017/03/27/093000
- https://docs.unity3d.com/ja/2019.4/Manual/FormatDescription.html
- https://magazine.rubyist.net/articles/0009/0009-YAML.html
- https://yaml.org/spec/1.2/spec.html

## 余談

GameObjectからのTransformへの参照に `fileID:0` を指定するとTransformが増殖。

![Untitled (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669902/25c60049-f62f-873b-1b52-36e5b030aeb6.png)