UnityでGameObjectにHideFlagsを設定した時の挙動まとめ

当初はiPhoneXのSafeArea対応のための便利スクリプトを用意する予定でしたが、その過程で調べたHideFlagsの挙動が大変興味深かったため、そちらを纏めてみました。

# 前提
- Unity 2017.2.0p2 MacOS
- シーン上のGameObjectのhideFlagsに設定する
- "オブジェクト" = エディター上から見えるGameObjectとコンポーネントのまとまり (≒GameObject)

# HideFlagsとは
HideFlagsはUnityのObjectが持つhideFlagsプロパティを設定するフラグです。
hideFlagsを設定すると、例えばオブジェクトをヒエラルキー上から不可視にしたりすることなどが出来ます。

# HideFlagsの挙動
## hideFlagsプロパテイ
- GameObjectのhideFlagsに値をセットすると、その値はGameObjectの持つ全てのコンポーネントにその値が上書きされる
- コンポーネントのhideFlagsに値をセットしても、その値はGameObjectや他のコンポーネントには影響しない
- GameObjectにコンポーネントを追加すると、その時のGameObjectのhideFlagの値が自動的にセットされる

**以下の調査はGameObjectとコンポーネントのどちらにHideFlagsをセットされた時の挙動か区別していない点に注意してください。**

## HideFlags.HideInHierarchy
- オブジェクトがHierarchyビューに表示されなくなる
- オブジェクトをSceneビューから選択できなくなる
    - スクリプトからSelection.objectに入れる事で選択状態にすることはできる

## HideFlags.HideInInspector
- コンポーネントがInspectorビューに表示されなくなる
    - オブジェクトの名前やActiveなどは表示されたまま操作可能

## HideFlags.DontSaveInEditor
- オブジェクトがシーンに保存されなくなる
- オブジェクトを編集したり、ヒエラルキーを変更してもシーンにDirtyがつかなくなる
- アプリをビルドしたとき、オブジェクトがシーンに含まれなくなる
    - 保存されていないシーン上で設定されていてもビルドに含まれない
    - `DontSaveInBuild` と同じ
- Prefabにも保存されなくなる
    - バグでPrefabに含まれてしまうバージョンもある ([Issue Tracker](https://issuetracker.unity3d.com/issues/applying-changes-to-prefab-adds-hideflags-dot-dontsave-object-to-prefab-file))
    - ルートに `DontSaveInEditor` が設定されたオブジェクトをPrefab化しようとするとエラーを出してくれる

## HideFlags.NotEditable
- オブジェクトを選択した時のInspectorビューの表示が全て灰色になり編集できなくなる
  - ヒエラルキーの移動は可能
  - コンポーネントの値のコピーは可能
- Sceneビュー上でハンドルが表示されなくなる

## HideFlags.DontSaveInBuild
- アプリをビルドしたとき、オブジェクトがシーンに含まれなくなる
    - 保存されていないシーン上で設定されていてもビルドに含まれない
- `DontSaveInEditor` とは異なり、シーンには保存される

## HideFlags.DontUnloadUnusedAsset
- オブジェクトやそのコンポーネントに影響は見られない

## HideFlags.DontSave
- `DontSaveInBuild` と `DontSaveInEditor` と `DontUnloadUnusedAsset` の複合

## HideFlags.HideAndDontSave
- `HideInInspector` 以外の全ての複合

# 複合的な条件下で起きる現象
以下のいずれかの条件を満たすと、オブジェクトは「シーンに属さないオブジェクト」に変化しました。

- *[条件1]* シーン直下にあり、 `HideInHierarchy`,`DontSaveInEditor`,`DontSaveInBuild` が同時に設定される
- *[条件2]* シーン直下にあり、 `DontSaveInEditor` が設定され、所属するシーンがアンロードされる

実験の結果、こうなったオブジェクトには以下のような特徴があるようです。

- 通常のオブジェクトと同様に動作する
    - Sceneビュー、Gameビューにも表示される
- `Object.FindObjectOfType` で取得できない
- `Scene.GetRootGameObjects` で取得できない
- `GameObject.scene` が無効なシーンを指す
- **シーンのアンロードで削除されない**
    - **リコンパイル後も削除されない**
- 選択してもGUI上から削除できない
    - 明示的なDestroyやDestroyImmediateで削除できる
- *[条件1]* を満たさないHideFlagsが設定されるとその時アクティブなシーンの配下に戻る

**要はリークオブジェクトになります。**

## 消失したオブジェクトを探すには？
前述の通り、消失したオブジェクトはシーンから直接探すことはできません。
ですが、固有の名前やコンポーネントがついている場合は `Resources.FindObjectsOfTypeAll` から探すことができます。
ただし、エディターは内部的に同様の「シーンに属さないオブジェクト」を複数保持しているため、 `GameObject.scene` の値だけでこれを判別することはできません。

また、 **生成時にstatic変数に参照を取っておくのは良い手ではありません。**
リコンパイル時に参照が全てクリアされてしまい、リークオブジェクトとなってしまうからです。

## ランタイムでも起きるのか？
`DontSaveInEditor` の付いたオブジェクトはビルドに含まれません。が、ビルドされたアプリであっても実行時に `DontSaveInEditor` をセットすることで同じ現象が再現するようです。
エディターコード外でHideFlagsを使う時はプリプロセッサで明確に処理を分けておくべきです。

## バグなのか？
手元のマシンに入っているUnityで軽く試してみましたが、5.4, 5.5, 5.6, 2017.1 のいずれのバージョンでも再現しました。由緒正しい挙動のようです。

Unity Issue Tracker を調べてみると似た問題がヒットしました。
[Issue Tracker - FINDOBJECTOFTYPE DOES NOT FIND GAMEOBJECT WITH HIDEFLAGS.DONTSAVE](https://issuetracker.unity3d.com/issues/findobjectoftype-does-not-find-gameobject-with-hideflags-dot-dontsave)
条件が甘いですが、 `Object.FindObjectOfType` で取得できないのは **仕様** らしいです。
オブジェクトが消えない方を現象として報告すれば通るでしょうか？

# まとめ
長年悩みのPrefabInPrefab処理や、ちょっとした便利機能のつもりで作ったシングルトン(?)クラスなど、HideFlagsを利用していそうなコードに心当たりがあれば、一度見直しをしてみる事をおすすめします。

ハックは用法用量を守って楽しいUnityライフを！


# 追記(2017/12/20)
初めてバグレポート送ってみました。せめて仕様かどうか判明することを期待しています。
https://issuetracker.unity3d.com/issues/gameobject-with-hideflags-dot-dontsaveineditor-is-still-visible-in-a-newly-created-scene