{"id":501,"date":"2021-08-21T17:43:54","date_gmt":"2021-08-21T17:43:54","guid":{"rendered":"https:\/\/bronsonzgeb.com\/?p=501"},"modified":"2021-08-21T17:43:55","modified_gmt":"2021-08-21T17:43:55","slug":"scene-selector-tool","status":"publish","type":"post","link":"https:\/\/bronsonzgeb.com\/index.php\/2021\/08\/21\/scene-selector-tool\/","title":{"rendered":"Scene Selector Tool"},"content":{"rendered":"\n<p>In this article, we&#8217;ll build a custom editor window to streamline scene navigation in our project. Along the way, we&#8217;ll learn about custom menu items and the <code>ScriptableSingleton<\/code> class and some other editor scripting goodies.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What are we building?<\/h2>\n\n\n\n<p>We&#8217;ll build an editor window to simplify jumping between scenes in our project. I want the window to present all the scenes in the project with a button to open them. Additionally, I want the option to jump directly into play mode after opening the scene. Finally, if I jump directly into play mode, I want to return to the scene I was in previously when I exit play mode. Admittedly this is a relatively simple editor window, but I designed it to showcase specific handy editor scripting features.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating an Editor Window<\/h2>\n\n\n\n<p>Starting a new editor window is as simple as creating a class that derives from <code>EditorWindow<\/code>. By the way, any editor scripts should reside in a folder called &#8220;Editor&#8221;, or you won&#8217;t be able to build your project.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class SceneSelectorWindow : EditorWindow\n{\n}<\/code><\/pre>\n\n\n\n<p>However, by default, there&#8217;s no way to open your new window. It&#8217;s comforting to know that conceptually the window exists, but let&#8217;s add a <code>MenuItem<\/code> to open it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class SceneSelectorWindow : EditorWindow\n{\n    <strong>&#91;MenuItem(\"Tools\/Scene Selector %#o\")]<\/strong>\n<strong>    static void OpenWindow()\n    {\n        GetWindow&lt;SceneSelectorWindow&gt;();\n    }<\/strong>\n}<\/code><\/pre>\n\n\n\n<p>When you place the <code>MenuItem<\/code> attribute above a method, you can call it from the menu at the top of the editor. In this case, I created a new Tools menu and added Scene Selector to it. The attribute also supports adding keyboard shortcuts to your menu items. In my example, % represents Ctrl (Windows) or Command (macOS), # is Shift and o is, well, the letter o. Then, we use <code>GetWindow<\/code>, which either focuses on an existing <code>SceneSelectorWindow<\/code> or creates a new one if necessary.<\/p>\n\n\n\n<p><code>MenuItem<\/code> is a mighty tool in your editor scripting toolbox. By that, I mean you can attach any keyboard shortcut to any arbitrary method, allowing you to script or automate any editor feature. For example, you could create a shortcut to enter play mode from a specific scene. Another example from my own experience is testing in-game functionality before building in-game UI, such as saving and loading. You could add any number of helpful debug commands. One last note, any menu items you add will appear in the editor&#8217;s shortcut manager window. That means that you can remap your custom menu items without explicitly defining a shortcut in the code.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating the UI<\/h2>\n\n\n\n<p>Now that we have a blank window, it&#8217;s time to create the UI. We&#8217;ll build the UI using UI Toolkit, using the <code>CreateGUI<\/code> method to implement it. <code>CreateGUI<\/code> is a method that runs automatically when the root <code>VisualElement<\/code> is ready to be populated. Let&#8217;s find all the scenes in our project and create an open button for each one. To find all the scenes, we&#8217;ll use <code>AssetDatabase.FindAssets<\/code> with a filter. This method will return a list of GUIDs that we can convert into asset paths, which we need to open a scene.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void CreateGUI()\n{\n    var sceneGuids = AssetDatabase.FindAssets(\"t:Scene\");\n\n    foreach (var sceneGuid in sceneGuids)\n    {\n        rootVisualElement.Add(CreateSceneButton(sceneGuid));\n    }\n}\n\nVisualElement CreateSceneButton(string sceneGuid)\n{\n    var scenePath = AssetDatabase.GUIDToAssetPath(sceneGuid);\n    var buttonGroup = new VisualElement();\n    buttonGroup.style.flexDirection = FlexDirection.Row;\n    buttonGroup.style.marginLeft = 3;\n\n    var sceneAsset = AssetDatabase.LoadAssetAtPath(scenePath, typeof(SceneAsset));\n    var label = new Label($\"{sceneAsset.name}\");\n    label.style.width = 150;\n    buttonGroup.Add(label);\n\n    var openButton = new Button(() =&gt; { EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); });\n    openButton.text = \"Open\";\n    buttonGroup.Add(openButton);\n\n    return buttonGroup;\n}<\/code><\/pre>\n\n\n\n<p>If it&#8217;s not clear, the <code>CreateSceneButton<\/code> method creates a horizontally laid out <code>VisualElement<\/code>. We make a label with the name of the scene and an open button next to it. Afterwards, we&#8217;ll add a second button in the same group. By the way, to make elements lay themselves out horizontally rather than vertically, set <code>flexDirection<\/code> to <code>FlexDirection.Row<\/code>.<\/p>\n\n\n\n<p>Now that we completed the primary UI, let&#8217;s implement a button to jump directly into play mode for a given scene.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Entering and Exiting Play Mode<\/h2>\n\n\n\n<p>Jumping directly into a scene is straightforward. It only takes one extra line of code.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>VisualElement CreateSceneButton(string sceneGuid)\n{\n    var scenePath = AssetDatabase.GUIDToAssetPath(sceneGuid);\n    var buttonGroup = new VisualElement();\n    buttonGroup.style.flexDirection = FlexDirection.Row;\n    buttonGroup.style.marginLeft = 3;\n\n    var sceneAsset = AssetDatabase.LoadAssetAtPath(scenePath, typeof(SceneAsset));\n    var label = new Label($\"{sceneAsset.name}\");\n    label.style.width = 150;\n    buttonGroup.Add(label);\n\n    var openButton = new Button(() =&gt; { EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); });\n    openButton.text = \"Open\";\n    buttonGroup.Add(openButton);\n\n    <strong>var playButton = new Button(() =&gt;<\/strong>\n<strong>    {\n        SceneSelectorSettings.instance.PreviousScenePath = SceneManager.GetActiveScene().path;\n        EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);\n        EditorApplication.EnterPlaymode();\n    });\n    playButton.text = \"Play\";\n    buttonGroup.Add(playButton);<\/strong>\n    return buttonGroup;\n}<\/code><\/pre>\n\n\n\n<p>Here&#8217;s the revised version of the <code>CreateSceneButton<\/code> method. You&#8217;ll see that we turn our open button into a play button with one more line of code. But I snuck our next topic in there as well, that is, the <code>SceneSelectorSettings<\/code>. When entering play mode through our button, store the path to the scene we&#8217;re currently in inside <code>SceneSelectorSettings<\/code>. What is <code>SceneSelectorSettings<\/code>? It&#8217;s a <code>ScriptableSingleton<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Scriptable Singletons<\/h2>\n\n\n\n<p>Scriptable Singletons are an editor class based on <code>ScriptableObject<\/code> to simplify saving settings between assembly reloads and unity editor sessions. Create one by creating a new class that derives from <code>ScriptableSingleton<\/code>. Additionally, specify a <code>FilePath<\/code> attribute, and it&#8217;ll automatically know how to save and load to disk. The <code>FilePath<\/code> attribute specifies a path relative to either the project folder or settings folder, for per-project or per-machine settings, respectively. In our case, let&#8217;s save the settings in the project folder.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using UnityEditor;\n\n&#91;FilePath(\"Assets\/SceneSelectorTool\/Editor\/SceneSelectorSettings.asset\", FilePathAttribute.Location.ProjectFolder)]\npublic class SceneSelectorSettings : ScriptableSingleton&lt;SceneSelectorSettings&gt;\n{\n    public string PreviousScenePath;\n\n    void OnDestroy()\n    {\n        Save(true);\n    }\n}<\/code><\/pre>\n\n\n\n<p>Another important note is that Scriptable Singletons don&#8217;t automatically save. You have the option of saving whenever you modify a setting or do as I did, which is to save in the <code>OnDestroy<\/code> method. Our tool only has a single setting worth saving, but you can add as many fields as needed to the <code>ScriptableSingleton<\/code> in your projects.<\/p>\n\n\n\n<p>The last feature to add is to return to the scene we were in before we pushed the play button. So let&#8217;s learn about the <code>playModeStateChanged<\/code> event.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Detecting play mode state changed<\/h2>\n\n\n\n<p>The <code>EditorApplication<\/code> class raises the event <code>playModeStateChanged<\/code> whenever the play mode changes. When I first set this up, I tried to hook into that event when the user pressed Scene Selector&#8217;s play button. However, since entering and exiting play mode causes a domain reload, I never received the event. We&#8217;ll register for the event in a method marked with the <code>InitializeOnLoadMethod<\/code> attribute to work around this. If you don&#8217;t know, adding the &#8216;InitializeOnLoadMethod&#8217; attribute to a static method will ask the editor to run it after every reload. Then, in our callback, we identify when we&#8217;ve entered edit mode and open the scene stored in the <code>SceneSelectorSettings<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;InitializeOnLoadMethod]\nstatic void RegisterCallbacks()\n{\n    EditorApplication.playModeStateChanged += ReturnToPreviousScene;\n}\n\nstatic void ReturnToPreviousScene(PlayModeStateChange change)\n{\n    if (change == PlayModeStateChange.EnteredEditMode)\n    {\n        EditorSceneManager.OpenScene(SceneSelectorSettings.instance.PreviousScenePath, OpenSceneMode.Single);\n    }\n}<\/code><\/pre>\n\n\n\n<p>Here are the two files in their entirety, but the code is easier to read in the Github project linked at the end of this article.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/SceneSelectorWindow.cs\nusing UnityEditor;\nusing UnityEditor.SceneManagement;\nusing UnityEngine.SceneManagement;\nusing UnityEngine.UIElements;\n\npublic class SceneSelectorWindow : EditorWindow\n{\n    &#91;MenuItem(\"Tools\/Scene Selector %#o\")]\n    static void OpenWindow()\n    {\n        GetWindow&lt;SceneSelectorWindow&gt;();\n    }\n\n    &#91;InitializeOnLoadMethod]\n    static void RegisterCallbacks()\n    {\n        EditorApplication.playModeStateChanged += ReturnToPreviousScene;\n    }\n\n    void CreateGUI()\n    {\n        var sceneGuids = AssetDatabase.FindAssets(\"t:Scene\");\n\n        foreach (var sceneGuid in sceneGuids)\n        {\n            rootVisualElement.Add(CreateSceneButton(sceneGuid));\n        }\n    }\n\n    static void ReturnToPreviousScene(PlayModeStateChange change)\n    {\n        if (change == PlayModeStateChange.EnteredEditMode)\n        {\n            EditorSceneManager.OpenScene(SceneSelectorSettings.instance.PreviousScenePath, OpenSceneMode.Single);\n        }\n    }\n\n    VisualElement CreateSceneButton(string sceneGuid)\n    {\n        var scenePath = AssetDatabase.GUIDToAssetPath(sceneGuid);\n        var buttonGroup = new VisualElement();\n        buttonGroup.style.flexDirection = FlexDirection.Row;\n        buttonGroup.style.marginLeft = 3;\n\n        var sceneAsset = AssetDatabase.LoadAssetAtPath(scenePath, typeof(SceneAsset));\n        var label = new Label($\"{sceneAsset.name}\");\n        label.style.width = 150;\n        buttonGroup.Add(label);\n\n        var openButton = new Button(() =&gt; { EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); });\n        openButton.text = \"Open\";\n        buttonGroup.Add(openButton);\n\n        var playButton = new Button(() =&gt;\n        {\n            SceneSelectorSettings.instance.PreviousScenePath = SceneManager.GetActiveScene().path;\n            EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);\n            EditorApplication.EnterPlaymode();\n        });\n        playButton.text = \"Play\";\n        buttonGroup.Add(playButton);\n        return buttonGroup;\n    }\n}\n\n\/\/SceneSelectorSettings.cs\nusing UnityEditor;\n\n&#91;FilePath(\"Assets\/SceneSelectorTool\/Editor\/SceneSelectorSettings.asset\", FilePathAttribute.Location.ProjectFolder)]\npublic class SceneSelectorSettings : ScriptableSingleton&lt;SceneSelectorSettings&gt;\n{\n    public string PreviousScenePath;\n    \n    void OnDestroy()\n    {\n        Save(true);\n    }\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Wrap-up<\/h2>\n\n\n\n<p>We&#8217;ve covered creating editor windows, menu items with shortcuts, scriptable singletons and detecting changes in the play mode state. Hopefully, this knowledge enables you to take control of your Unity editor experience. Even simple tools can significantly improve your workflow when you tailor them to your specific needs, so I hope you&#8217;re able to implement these ideas into your projects.<\/p>\n\n\n\n<p><strong>Peruse the entire Github project&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/github.com\/bzgeb\/SceneSelectorTool\"><strong>here<\/strong><\/a><strong>. If you like my work,&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/bronsonzgeb.com\/index.php\/join-my-mailing-list\/\"><strong>join my mailing list<\/strong><\/a><strong>&nbsp;to be notified when I write a new post.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, we&#8217;ll build a custom editor window to streamline scene navigation in our project. Along the way, we&#8217;ll learn about custom menu items and the ScriptableSingleton class and some other editor scripting goodies. What are we building? We&#8217;ll build an editor window to simplify jumping between scenes in our project. I want the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":503,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[43,32,1],"tags":[45,44,8,46,5],"class_list":["post-501","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-editor-tools","category-ui","category-unity-programming","tag-editor-scripting","tag-editor-tools","tag-game-development","tag-ui-toolkit","tag-unity"],"_links":{"self":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/501","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/comments?post=501"}],"version-history":[{"count":2,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/501\/revisions"}],"predecessor-version":[{"id":504,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/501\/revisions\/504"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media\/503"}],"wp:attachment":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media?parent=501"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/categories?post=501"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/tags?post=501"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}