LoginSignup
0
0

More than 3 years have passed since last update.

【Unity】シーンに自動で追加されるEventSystemを削除する方法【Zenject】

Last updated at Posted at 2019-09-08

はじめに

uGUIを使っていて自動でHierarchyにEventSystemが生成される挙動が邪魔だったので、自動で生成されないようにできないか調べたメモ。

uGUIとEventSystem

uGUIではその動作に必要なEventSystemをシーン毎に配置するというのがUnityの想定しているやり方なわけですが、これはプロジェクト全体でEventSystemをまとめて変更したくなったときに面倒で、例えば全てのEventSystemの値を同期するコンポーネントを作ったりが必要です。

Zenjectとシーン

Zenject を使っているとシーンのペアレンティング/デコレート機能によりシーンを分割した設計・運用にしやすくなります。

また、ManagerクラスのコンポーネントなどはProjectContextプレハブ内に置いてバインドすることでシーンを単体で再生しても読み込み時に展開・利用でき、シーン毎に配置する必要もないので開発時に便利です。

コンポーネントをプロジェクト全体で一つに集約できるので、変更があっても一箇所で済みます。

EventSystemの挙動

EventSystemも再生時に常に一つだけあればいいので先述の運用にすると楽なのですが、EventSystemはUnityが勝手にHierarchyに配置することがあり、プロジェクト全体で一つだけにしてシーンから消してもいつの間にか復活・シーンに保存されている場合があります。

Zenjectで機能ごとに細かくシーン分割していると気づけばいろんなシーンに雨後の筍のようにEventSystemが生成・保存されていて、いちいち削除するのも面倒です。

では自動でEventSystemが生成されないようにする設定があるかというと、そんなものはありません。以下はUnity2019.1がEventSystemを自動生成している処理です。

MenuOptions.cs(Bitbucket)
MenuOptions.cs
...
        [MenuItem("GameObject/UI/Text", false, 2000)]
        static public void AddText(MenuCommand menuCommand)
        {
            GameObject go = DefaultControls.CreateText(GetStandardResources());
            PlaceUIElementRoot(go, menuCommand);
        }
...
        private static void PlaceUIElementRoot(GameObject element, MenuCommand menuCommand)
        {
            GameObject parent = menuCommand.context as GameObject;
            bool explicitParentChoice = true;
            if (parent == null)
            {
                parent = GetOrCreateCanvasGameObject();
...
        static public GameObject GetOrCreateCanvasGameObject()
        {
            GameObject selectedGo = Selection.activeGameObject;

            // Try to find a gameobject that is the selected GO or one if its parents.
            Canvas canvas = (selectedGo != null) ? selectedGo.GetComponentInParent<Canvas>() : null;
            if (IsValidCanvas(canvas))
                return canvas.gameObject;

            // No canvas in selection or its parents? Then use any valid canvas.
            // We have to find all loaded Canvases, not just the ones in main scenes.
            Canvas[] canvasArray = StageUtility.GetCurrentStageHandle().FindComponentsOfType<Canvas>();
            for (int i = 0; i < canvasArray.Length; i++)
                if (IsValidCanvas(canvasArray[i]))
                    return canvasArray[i].gameObject;

            // No canvas in the scene at all? Then create a new one.
            return MenuOptions.CreateNewUI();
        }
...
        static public GameObject CreateNewUI()
        {
            // Root for the UI
            var root = new GameObject("Canvas");
            root.layer = LayerMask.NameToLayer(kUILayerName);
            Canvas canvas = root.AddComponent<Canvas>();
            canvas.renderMode = RenderMode.ScreenSpaceOverlay;
            root.AddComponent<CanvasScaler>();
            root.AddComponent<GraphicRaycaster>();

            // Works for all stages.
            StageUtility.PlaceGameObjectInCurrentStage(root);
            bool customScene = false;
            PrefabStage prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
            if (prefabStage != null)
            {
                root.transform.SetParent(prefabStage.prefabContentsRoot.transform, false);
                customScene = true;
            }

            Undo.RegisterCreatedObjectUndo(root, "Create " + root.name);

            // If there is no event system add one...
            // No need to place event system in custom scene as these are temporary anyway.
            // It can be argued for or against placing it in the user scenes,
            // but let's not modify scene user is not currently looking at.
            if (!customScene)
                CreateEventSystem(false);
            return root;
        }
...
        [MenuItem("GameObject/UI/Event System", false, 2100)]
        public static void CreateEventSystem(MenuCommand menuCommand)
        {
            GameObject parent = menuCommand.context as GameObject;
            CreateEventSystem(true, parent);
        }

        private static void CreateEventSystem(bool select)
        {
            CreateEventSystem(select, null);
        }

        private static void CreateEventSystem(bool select, GameObject parent)
        {
            StageHandle stage = parent == null ? StageUtility.GetCurrentStageHandle() : StageUtility.GetStageHandle(parent);
            var esys = stage.FindComponentOfType<EventSystem>();
            if (esys == null)
            {
                var eventSystem = new GameObject("EventSystem");
                if (parent == null)
                    StageUtility.PlaceGameObjectInCurrentStage(eventSystem);
                else
                    GameObjectUtility.SetParentAndAlign(eventSystem, parent);
                esys = eventSystem.AddComponent<EventSystem>();
                eventSystem.AddComponent<StandaloneInputModule>();

                Undo.RegisterCreatedObjectUndo(eventSystem, "Create " + eventSystem.name);
            }

            if (select && esys != null)
            {
                Selection.activeGameObject = esys.gameObject;
            }
        }

どうやら右クリックメニューからのuGUIコンポーネント生成時にCanvasがなければEventSystemと一緒に生成しているみたいです。

自作関数に[MenuItem]を指定してUIの生成処理を上書きできないかも試しましたが、


Cannot add menu item 'GameObject/UI/Image' for method 'MenuOptions.AddImage' because a menu item with the same name already exists.

という警告が出て上書きできませんでした。

EventSystemが追加されたら削除するクラスの実装

そもそも生成されないようにするのはできなさそうだったので、生成されたら即消すクラスを実装してみました。適当なEditor/以下のディレクトリに配置すると動作します(Unity2017.3.1f1で動作確認済み)。

Editor/EventSystemDestroyer.cs
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;

public class EventSystemDestroyer
{
    [InitializeOnLoadMethod]
    private static void Initialize()
    {
        EditorApplication.hierarchyChanged += () =>
        {
            var activeScene = SceneManager.GetActiveScene();
            var go = activeScene.GetRootGameObjects().LastOrDefault();
            if (go == null || go.name != "EventSystem") return;

            GameObject.DestroyImmediate(go);
            Debug.unityLogger.LogWarning("GameObject with EventSystem in root was destroyed", activeScene.name);
        };
    }
}

Hierarchyに変更が起きたときにEventSystemがシーンのルートかつ一番最後に存在していれば削除します。

Unityに自動で生成されたかどうかは判断してないので、もっと賢くやるなら「Hierarchyに最初からEventSystemが存在していたかどうか」などを判断する必要がありそうです。必要に応じて実装してください。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0