第27章 HideFlags - エディター拡張入門

第27章 HideFlags

HideFlags は「オブジェクトの見え方」と「オブジェクトの保存対象」を制御するための機能です。シーン内にあるゲームオブジェクトは、ヒエラルキーに表示されているものがすべてだと思っていませんか?必ずゲームオブジェクトは、シーンファイルに保存されるものと思っていませんか?本章では、いくつかの例を出して HideFlags の仕組みについて紹介していきます。

27.1 ゲームオブジェクトを非表示にする

まずは、HideFlags の効果を実感してもらいます。下記のコードを実装し、"Assets/Create New GameObject" メニューでゲームオブジェクトを作成します。

[MenuItem ("Assets/Create New GameObject")]
static void CreateNewGameObject ()
{
  new GameObject ("New GameObject");
}

何事も問題なく 図27.1 のようにゲームオブジェクトが作成されます。

シーン内に New GameObject が作成された

図27.1: シーン内に New GameObject が作成された

作成されたゲームオブジェクトを削除して、下記のように HideFlags についてのコードを追加します。

[MenuItem ("Assets/Create New GameObject")]
static void CreateNewGameObject ()
{
  var go = new GameObject ("New GameObject");

  //ヒエラルキーに表示しない
  go.hideFlags = HideFlags.HideInHierarchy;
}

すると、"Assets/Create New GameObject" メニューを実行してもヒエラルキーには New GameObject は表示されません。

この状態("Assets/Create New GameObject"を実行したが New GameObject が表示されない状態)で、シーンを保存してみましょう。そしてシーンファイルをテキストエディターで見てみます。*1

シーンファイルの該当部分。 New GameObject が存在することが分かる。

--- !u!1 &1082080032
GameObject:
  m_ObjectHideFlags: 1
  m_PrefabParentObject: {fileID: 0}
  m_PrefabInternal: {fileID: 0}
  serializedVersion: 4
  m_Component:
  - 4: {fileID: 1082080033}
  m_Layer: 0
  m_Name: New GameObject
  m_TagString: Untagged
  m_Icon: {fileID: 0}
  m_NavMeshLayer: 0
  m_StaticEditorFlags: 0
  m_IsActive: 1

または、GameObject.Find で New GameObject が存在することを確認します。

[MenuItem ("Assets/Find New GameObject")]
static void FindNewGameObject ()
{
  var go  = GameObject.Find ("New GameObject");
  Debug.Log (go);
}

27.2 HideFlags.HideInHierarchy の挙動

ゲームオブジェクトに対しての挙動

HideFlags.HideInHierarchy は、シーンビュー上での Selection 操作を無効にします。

Cube を作成

[MenuItem ("Assets/Create New GameObject")]
static void CreateNewGameObject ()
{
  var go =  GameObject.CreatePrimitive (PrimitiveType.Cube);
  go.name = "New GameObject";
  go.hideFlags = HideFlags.HideInHierarchy;
}
Cube をクリックしても何も起こらない

図27.2: Cube をクリックしても何も起こらない

ですが、スクリプトからだと Selection 操作が可能なことを覚えておきましょう。

[MenuItem ("Assets/Create New GameObject")]
static void CreateNewGameObject ()
{
  var go =  GameObject.CreatePrimitive (PrimitiveType.Cube);
  go.name = "New GameObject";
  go.hideFlags = HideFlags.HideInHierarchy;
  Selection.activeGameObject = go;
}
ハンドルとインスペクターが表示されている

図27.3: ハンドルとインスペクターが表示されている

アセットに対しての挙動

HideFlags.HideInHierarchy はアセットに対しても効果があります。ただし サブアセットのみということを覚えておいてください。メインアセットに HideInHierarchy を設定した場合、アセットとして認識されなくなり、依存関係に不具合が生じる可能性があります。

また、HideInHierarchy について 第4章「ScriptableObject」 の「ScriptableObject の親子関係」でも詳しく説明しています。

左側がすべて表示、右側が 2nd と 3rd に HideInHierarchy を設定

図27.4: 左側がすべて表示、右側が 2nd と 3rd に HideInHierarchy を設定

[MenuItem ("Assets/Create SubAssets")]
static void HideReference ()
{
  var path = "Assets/1st.anim";
  var first = new AnimationClip { name = "1st" };

  var second = new AnimationClip {
    name = "2nd",
    hideFlags = HideFlags.HideInHierarchy
  };
  var third = new AnimationClip {
    name = "3rd",
    hideFlags = HideFlags.HideInHierarchy
  };

  AssetDatabase.CreateAsset (first, path);

  //サブアセット化
  AssetDatabase.AddObjectToAsset (second, first);
  AssetDatabase.AddObjectToAsset (third, first);

  AssetDatabase.ImportAsset (path);
}

27.3 HideFlags.DontSave の挙動

Unity で、ゲーム再生時に変更したパラメーターがゲーム停止時にリセットされる現象をよく見ます。

再生中にパラメーターを変更し停止させると元に戻る

図27.5: 再生中にパラメーターを変更し停止させると元に戻る

このリセットされる仕様は、リソースの解放(オブジェクトの破棄)も含まれます。以下に「Unity でのオブジェクト管理を簡潔にまとめたもの」として箇条書きにします。

  • ゲーム再生後に生成されたオブジェクトは自動的に破棄される
  • ゲームを停止するとオブジェクトはゲーム再生前の状態に戻る
  • 不要なリソースはアンロードされる
  • これらはすべてメモリ上のみに存在するオブジェクトを対象としている

上記の仕様をすべて無視することのできる HideFlags.DontSave があります。HideFlags.DontSave は、実際には3つのフラグから構成されています。

HideFlags.DontSaveInBuild
ビルド時にシーン内にゲームオブジェクトを保存しない。または、リソースとしてアセットを含めない。
HideFlags.DontSaveInEditor
エディター上でオブジェクトを保存対象としない。
HideFlags.DontUnloadUnusedAsset
Unity の仕様に則ったリソース管理から除外する。

この3つの中で重要なのは HideFlags.DontSaveInEditorHideFlags.DontUnloadUnusedAsset の2つになります。

HideFlags.DontSaveInEditor

HideFlags.DontSaveInEditor はオブジェクトが自動で破棄されてしまうのを無効にするフラグです。

以下のコードを試してみます。

public class Hoge : ScriptableObject {}

public class HogeWindow : EditorWindow
{
  [MenuItem ("Window/Hoge")]
  static void Hoge ()
  {
    GetWindow<HogeWindow> ();
  }

  Hoge hoge = null;

  void OnEnable ()
  {
    hoge = ScriptableObject.CreateInstance<Hoge> ();
  }

  void Update ()
  {
    Debug.Log (hoge);
  }
}

ウィンドウを開いた状態でゲームを再生します。

ゲーム再生直後の状態。OnEnable でインスタンスが生成され、オブジェクトが存在していることが分かる

図27.6: ゲーム再生直後の状態。OnEnable でインスタンスが生成され、オブジェクトが存在していることが分かる

ゲーム停止直後の状態。オブジェクトがリセット(破棄)され null となっている

図27.7: ゲーム停止直後の状態。オブジェクトがリセット(破棄)され null となっている

このようにゲーム再生直後に生成されたオブジェクトは、エディター拡張側のオブジェクトであっても破棄されてしまいます。そこで、オブジェクトを自動で破棄しない方法として HideFlags.DontSaveInEditor があります。

先ほどのコードに、DontSaveInEditor を追加することで違いを確認することができます。

public class Hoge : ScriptableObject {}

public class HogeWindow : EditorWindow
{
  [MenuItem ("Window/Hoge")]
  static void Hoge ()
  {
    GetWindow<HogeWindow> ();
  }

  Hoge hoge = null;

  void OnEnable ()
  {
    hoge = ScriptableObject.CreateInstance<Hoge> ();
    hoge.hideFlags = HideFlags.DontSaveInEditor;
  }

  void Update ()
  {
    Debug.Log (hoge);
  }
}
ゲームを停止しても null にならずにオブジェクトがあることがわかる

図27.8: ゲームを停止しても null にならずにオブジェクトがあることがわかる

HideFlags.DontUnloadUnusedAsset

HideFlags.DontUnloadUnusedAsset は、オブジェクトが自動でアンロードされてしまうのを無効にするフラグです。

以下のコードを試してみます。

public class Hoge : ScriptableObject {}

public class HogeWindow : EditorWindow
{
  [MenuItem ("Window/Hoge")]
  static void Hoge ()
  {
    GetWindow<HogeWindow> ();
  }

  Hoge hoge = null;

  void OnEnable ()
  {
    hoge = ScriptableObject.CreateInstance<Hoge> ();

    var hoges = Resources.FindObjectsOfTypeAll<Hoge> ();

    Debug.Log (hoges.Length);
  }
}

ゲーム再生 -> ゲーム停止を繰り返しても、オブジェクトの数が常に 1 になることが確認できます。(スクリプトのコンパイル直後のみ 2 になります。これは自動アンロードのタイミングがゲーム停止時だからです)

ゲームの再生/停止を繰り返してオブジェクトの数を確認する

図27.9: ゲームの再生/停止を繰り返してオブジェクトの数を確認する

次に、スクリプトのコンパイル時の挙動です。スクリプトのコンパイルが行われるとすべてのクラスが初期化され、保持していた参照がすべて外れてしまいます。

以下のコードはコンパイル後の hoge 変数の参照を確認するコードです。

Create -> Check -> Compile -> Check の順で実行する

public class Hoge : ScriptableObject {}

public class Test
{
  static Hoge hoge = null;

  [MenuItem ("Test/Create")]
  static void Create ()
  {
    hoge = ScriptableObject.CreateInstance<Hoge> ();
  }

  [MenuItem ("Test/Compile")]
  static void Compile ()
  {
    UnityEditorInternal.InternalEditorUtility.RequestScriptReload ();
  }

  [MenuItem ("Test/Check")]
  static void Check ()
  {
    Debug.Log (hoge);
  }
}

参照が外れてしまった後は、Resources.FindObjectsOfTypeAll を使ってメモリ上のオブジェクトを取得し、参照を割り当てます。ですがこの時、自動で行われるリソースのアンロードを考慮しなくてはいけません。本章で何度か出ていますが、参照されていないメモリ上にあるオブジェクトは特定のタイミング(ゲーム停止時や EditorUtility.UnloadUnusedAssetsImmediate の呼び出し時)で破棄されます。

このように、コンパイル後などで参照が外れてしまったときに、自動でオブジェクトがメモリ領域から解放されないようにするために HideFlags.DontUnloadUnusedAsset が使用されます。

HideFlags.DontSave の使いどころ

基本、HideFlags.DontSaveInEditor と HideFlags.DontUnloadUnusedAsset を単体で扱うことはなく、それらを組み合わせた HideFlags.DontSave を使います。すでに Unity 側で実装されている HideFlags.DontSave を使った代表的なものは EditorWindow です。また、Animator ウィンドウのようなノードベースのグラフエディターでもノードやトランジションで扱われています。

このように、ゲーム再生/停止・コンパイルに関係なく存在し続けなければいけないオブジェクトに対して HideFlags.DontSave を使用します。

[*1] 事前にアセットのシリアライズモードを Force Text にしておく必要があります。

第26章 AssetDatabase 第28章 AssetPostprocessor