{"id":488,"date":"2021-08-08T15:55:40","date_gmt":"2021-08-08T15:55:40","guid":{"rendered":"https:\/\/bronsonzgeb.com\/?p=488"},"modified":"2021-08-08T15:55:41","modified_gmt":"2021-08-08T15:55:41","slug":"unity-editor-tools-the-place-objects-tool","status":"publish","type":"post","link":"https:\/\/bronsonzgeb.com\/index.php\/2021\/08\/08\/unity-editor-tools-the-place-objects-tool\/","title":{"rendered":"Unity Editor Tools: The Place Objects Tool"},"content":{"rendered":"\n<p>In this article, we&#8217;ll develop a custom editor tool to streamline placing objects in the scene. We&#8217;ll cover several helpful editor APIs, including EditorTool, PrefabUtility, Handles and HandleUtility.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What&#8217;s the goal?<\/h2>\n\n\n\n<p>As mentioned, we&#8217;re building a tool to streamline placing objects in the scene. I want to drop an object every time I click. The object can either be a prefab or a clone of a GameObject from the scene. So, we&#8217;ll need a field to specify which thing we&#8217;re placing. Finally, I&#8217;d like to select a new object by right-clicking to pick the GameObject under the mouse cursor.<\/p>\n\n\n\n<p>Given these requirements, we&#8217;ll build a simple tool. I hope this process demonstrates how to make quick and dirty tools to streamline your workflows and increase productivity. When you build tools like this, it&#8217;s ok for them to be rough. The goal is to create something that benefits our project, not make a generic commercial tool. Project-specific tools are significantly faster to write. Additionally, when you tailor a tool to your needs, it can outperform generic tools.<\/p>\n\n\n\n<p>So let&#8217;s build!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The EditorTool<\/h2>\n\n\n\n<p>There are several ways to extend the Unity Editor. For this particular tool, I chose the <code>EditorTool<\/code> API. Subclassing <code>EditorTool<\/code> creates a new tool in the main toolbar. By that, I mean the toolbar containing the Move, Rotate, Scale, Hand, etc. Since our tool relies on the left and right mouse buttons to function, it makes sense to make it an <code>EditorTool<\/code>; otherwise, we&#8217;d fight with whichever tool the user has selected at any given moment. So, create a new class called <code>PlaceObjectsTool<\/code> that inherits from EditorTool. By the way, since this is for Editor use only, you must put the script file in a folder called &#8220;Editor&#8221;. Doing so prevents the script from being compiled into builds of the project, which would fail because we rely on several Editor-only APIs.<\/p>\n\n\n\n<p>Here&#8217;s our barebones script starting point.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>using System;\nusing UnityEngine;\nusing UnityEditor;\nusing UnityEditor.EditorTools;\n\n&#91;EditorTool(\"Place Objects Tool\")]\nclass PlaceObjectsTool : EditorTool\n{\n    &#91;SerializeField] Texture2D _toolIcon; \n    GUIContent _iconContent;\n\n    void OnEnable()\n    {\n        _iconContent = new GUIContent()\n        {\n            image = _toolIcon,\n            text = \"Place Objects Tool\",\n            tooltip = \"Place Objects Tool\"\n        };\n    }\n\n    public override GUIContent toolbarIcon\n    {\n        get { return _iconContent; }\n    }\n\n    public override void OnToolGUI(EditorWindow window)\n    {\n    }\n}<\/code><\/pre>\n\n\n\n<p>By the way, you can override <code>toolbarIcon<\/code> to give your tool an icon, but I won&#8217;t bother. Aside from that, the most important method is <code>OnToolGUI<\/code>. This method is essentially the <code>EditorTool<\/code> equivalent of <code>Update<\/code> in that it runs every time an editor window repaints.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">OnToolGUI<\/h2>\n\n\n\n<p>Let&#8217;s start by blocking out what we want to do.<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Ensure:<ol><li>We&#8217;re the active tool&nbsp;<\/li><li>In the scene view&nbsp;<\/li><li>Have a placeable object<\/li><\/ol><\/li><li>Draw a Handle, so the user knows where the object will go if they click.<\/li><li>If we receive a click, clone the placeable object and place it at the current mouse position.<\/li><li>Force the window to repaint.<\/li><\/ol>\n\n\n\n<p>Some of this work is more complicated than it seems, so let&#8217;s take it one step at a time.<\/p>\n\n\n\n<p>First, we&#8217;ll ensure the preconditions are met.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public override void OnToolGUI(EditorWindow window)\n{\n    \/\/If we're not in the scene view, exit.\n    if (!(window is SceneView))\n        return;\n\n    \/\/If we're not the active tool, exit.\n    if (!ToolManager.IsActiveTool(this))\n        return;\n\n    \/\/If we don't have a placeable object, exit.\n    if (!HasPlaceableObject)\n        return;\n...<\/code><\/pre>\n\n\n\n<p>Pay attention to the ToolManager.IsActiveTool, because the <code>ToolManager<\/code> is a valuable class when writing EditorTools. Here we&#8217;re using it to make sure our tool is the active tool. The reason we do this is that the editor could continue to call <code>OnToolGUI<\/code> on our tool after being initialized, even if it&#8217;s not currently the actively selected tool. There&#8217;s also a <code>HasPlaceableObject<\/code> property that we&#8217;ll return to later.<\/p>\n\n\n\n<p>Next, let&#8217;s draw a circle at the current mouse position in world space.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Handles API<\/h2>\n\n\n\n<p>We&#8217;ll use the <code>Handles<\/code> class to draw our GUI in the Scene View. If you haven&#8217;t used <code>Handles<\/code> before, you&#8217;re in for a treat. This class contains many ways to draw debug tools in the Scene View, including shapes, lines, labels, and existing Unity tools like the Move, Scale and Rotate tools. I highly encourage you to check it out. So follow up the previous line of code with this.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/Draw a positional Handle.\nHandles.DrawWireDisc(GetCurrentMousePositionInScene(), Vector3.up, 0.5f);\n\n...<\/code><\/pre>\n\n\n\n<p>Here we pass in a position, normal and radius for wireframe disc. To calculate the position, I wrote a helper function that returns the position at which an object would spawn if we were to drag it into the scene. In other words, if it finds a surface, it&#8217;ll snap to it, and otherwise, it&#8217;ll choose a position in space.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Vector3 GetCurrentMousePositionInScene()\n{\n    Vector3 mousePosition = Event.current.mousePosition;\n    var placeObject = HandleUtility.PlaceObject(mousePosition, out var newPosition, out var normal);\n    return placeObject ? newPosition : HandleUtility.GUIPointToWorldRay(mousePosition).GetPoint(10);\n}<\/code><\/pre>\n\n\n\n<p>Here we see another valuable class: <code>HandleUtility<\/code>. This class contains tons of utility functions that are the backbone of the built-in Scene View tools. So using <code>HandleUtility<\/code> does the heavy lifting and keeps your tools consistent with the built-in ones.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Object Instantiation<\/h2>\n\n\n\n<p>The next step is to instantiate our selected object. There are a couple of cases to handle. If the selected object is a prefab or belongs to a prefab, we&#8217;ll instantiate a linked prefab. Otherwise, we&#8217;ll instantiate a clone of the original game object. Append this code after the <code>DrawWireDisc<\/code> line.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/If the user clicked, clone the selected object, place it at the current mouse position.\nif (_receivedClickUpEvent)\n{\n    var newObject = `_prefabObjectField.value;\n\n    GameObject newObjectInstance;\n    if (PrefabUtility.IsPartOfAnyPrefab(newObject))\n    {\n        var prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(newObject);\n        var prefab = AssetDatabase.LoadAssetAtPath&lt;GameObject&gt;(prefabPath);\n        newObjectInstance = (GameObject)PrefabUtility.InstantiatePrefab(prefab);\n    }\n    else\n    {\n        newObjectInstance = Instantiate((GameObject)newObject);\n    }\n    newObjectInstance.transform.position = GetCurrentMousePositionInScene();\n\n    Undo.RegisterCreatedObjectUndo(newObjectInstance, \"Place new object\");\n    \n    Event.current.Use();\n    _receivedClickUpEvent = false;\n}<\/code><\/pre>\n\n\n\n<p>There are couple of unfamiliar fields here: <code>_receivedClickUpEvent<\/code> and <code>_prefabObjectField<\/code>. We&#8217;ll come back to these later. The main thing I want to introduce right now is the <code>PrefabUtility<\/code> class. This class contains a bunch of prefab-related utility functions for the editor. For example, you can use <code>PrefabUtility.InstantiatePrefab<\/code> to create a linked prefab in the scene instead of an unlinked game object clone.<\/p>\n\n\n\n<p>So we take the selected object and check if it&#8217;s part of a prefab. If it is, we get the path to the prefab asset, load the prefab asset, and instantiate it. Otherwise, we instantiate a regular game object clone. After instantiating the object, set its position to the mouse position. To be a good editor citizen, we notify the <code>Undo<\/code> system that we created a new object so Ctrl+Z will remove it.<\/p>\n\n\n\n<p>Finally, we <code>Use()<\/code> the current event. By the way, calling <code>Use()<\/code> on events is extremely important because it notifies the other editor systems to ignore the event. For example, when we receive a <code>MouseUp<\/code> event, we want to place an object, but the Scene View selects the object under the mouse by default. So, by calling <code>Use()<\/code>, we tell the Scene View to ignore it instead of picking an object.<\/p>\n\n\n\n<p>The last step in <code>OnToolGUI<\/code> is to repaint the window.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/Force the window to repaint.\nwindow.Repaint();<\/code><\/pre>\n\n\n\n<p>The reason is that the Scene View doesn&#8217;t automatically update at a real-time frame rate, like 60fps. Instead, it updates whenever it receives an event worthy of an update. We want our tool to feel responsive, so we&#8217;ll repaint as long as it&#8217;s the active tool.<\/p>\n\n\n\n<p>Here&#8217;s the entire <code>OnToolGUI<\/code> function.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public override void OnToolGUI(EditorWindow window)\n{\n    \/\/If we're not in the scene view, we're not the active tool, we don't have a placeable object, exit.\n    if (!(window is SceneView))\n        return;\n\n    if (!ToolManager.IsActiveTool(this))\n        return;\n\n    if (!HasPlaceableObject)\n        return;\n\n    \/\/Draw a positional Handle.\n    Handles.DrawWireDisc(GetCurrentMousePositionInScene(), Vector3.up, 0.5f);\n\n    \/\/If the user clicked, clone the selected object, place it at the current mouse position.\n    if (_receivedClickUpEvent)\n    {\n        var newObject = _prefabObjectField.value;\n\n        GameObject newObjectInstance;\n        if (PrefabUtility.IsPartOfAnyPrefab(newObject))\n        {\n            var prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(newObject);\n            var prefab = AssetDatabase.LoadAssetAtPath&lt;GameObject&gt;(prefabPath);\n            newObjectInstance = (GameObject)PrefabUtility.InstantiatePrefab(prefab);\n        }\n        else\n        {\n            newObjectInstance = Instantiate((GameObject)newObject);\n        }\n        newObjectInstance.transform.position = GetCurrentMousePositionInScene();\n\n        Undo.RegisterCreatedObjectUndo(newObjectInstance, \"Place new object\");\n        \n        Event.current.Use();\n        _receivedClickUpEvent = false;\n    }\n\n    \/\/Force the window to repaint.\n    window.Repaint();\n}<\/code><\/pre>\n\n\n\n<p>There&#8217;s a lot more to cover, but this is a good place for a break. Since the code in this post alone won&#8217;t be enough to compile, I shared the complete tool at the end. You can explore the finished code and try the tool while I write the next section.<\/p>\n\n\n\n<p>So in this post, we learned about some useful APIs to build Editor tools, including <code>EditorTool<\/code>, <code>PrefabUtility<\/code>, <code>Handles<\/code> and <code>HandleUtility<\/code>. We learned how to differentiate between plain Game Objects and Prefabs. We also learned how to instantiate a linked Prefab. Finally, we learned how to draw GUI in the Scene View. Later we&#8217;ll go deeper into editor events, build a simple GUI with UIToolkit and create a custom context menu.<\/p>\n\n\n\n<p><strong><strong>Check out the finished project&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/github.com\/bzgeb\/PlaceObjectsTool\"><strong>here<\/strong><\/a><strong>&nbsp;on Github. If you want to be notified when the next part is released,&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>.<\/strong><\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, we&#8217;ll develop a custom editor tool to streamline placing objects in the scene. We&#8217;ll cover several helpful editor APIs, including EditorTool, PrefabUtility, Handles and HandleUtility. What&#8217;s the goal? As mentioned, we&#8217;re building a tool to streamline placing objects in the scene. I want to drop an object every time I click. The [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":491,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[43,1],"tags":[45,44,8,5],"class_list":["post-488","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-editor-tools","category-unity-programming","tag-editor-scripting","tag-editor-tools","tag-game-development","tag-unity"],"_links":{"self":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/488","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=488"}],"version-history":[{"count":3,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/488\/revisions"}],"predecessor-version":[{"id":492,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/488\/revisions\/492"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media\/491"}],"wp:attachment":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media?parent=488"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/categories?post=488"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/tags?post=488"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}