{"id":84459,"date":"2019-06-03T19:18:27","date_gmt":"2019-06-03T19:18:27","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=84459"},"modified":"2026-03-06T11:07:59","modified_gmt":"2026-03-06T11:07:59","slug":"building-a-level-editor-in-unity","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/building-a-level-editor-in-unity\/","title":{"rendered":"Build a Custom Level Editor in Unity with C#"},"content":{"rendered":"\n<p>You can build a custom level editor in Unity using five C# scripts that handle object data (EditorObject), level state management (LevelEditor), camera movement (CameraMove), mouse-based object placement and deletion (MouseScript), and UI\/save-load coordination (ManagerScript). The editor lets users place objects on a grid, move the camera, and save\/load level data &#8211; all through Unity\u2019s built-in UI system. This tutorial provides the complete code for each script with a downloadable pre-configured Unity project so you can focus on the editor logic rather than scene setup.<\/p>\n\n\n\n<p><strong>Read also:<\/strong> <a href=\"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/creating-ccli-wrapper\/\" target=\"_blank\" rel=\"noreferrer noopener\">C++\/CLI wrapper for native game engine code<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-are-video-game-levels-created\">How are video game levels created?<\/h2>\n\n\n\n<p>There are several important parts of a video game, ranging from sound to the player character itself. Arguably the most important aspect is the level, which is essentially the world the player inhabits. Without levels, all the other components of a game won&#8217;t be put to any use and only serve as fun coding projects rather than a piece of a game. Levels come in all shapes, sizes, and formats with some games having multiple smaller levels and others taking place in one giant level. A level can serve a wide variety of purposes, such as teaching a new game mechanic or presenting an important plot point in your story.<\/p>\n\n\n\n<p>How do these levels get created? After designing the world your user will interact with, it becomes time to open up the level editor. Here you&#8217;ll edit the terrain of the world, place enemies, and scatter items. Of course, many game engines come with a level editor out of the box. So why would you want to make your own? One of the biggest reasons is to allow the user to create their own levels, allowing them to get creative with your game as well as have near endless amounts of content to enjoy in your game. Some games, such as Super Mario Maker, base their entire existence around this idea.<\/p>\n\n\n\n<p>As a developer, you may find that the level editor that comes with a game engine doesn&#8217;t quite give what you want, or perhaps it gives you too much. You might also be a part of a larger team, and your teammates could use a level editor that makes their jobs easier. It&#8217;s also possible that just you and a friend are working on this project, but you still need something to lighten the workload or at least make development easier. From the perspective of the developer, there can be any number of reasons to have a custom level editor for your project. After all, why not have better tools in the proverbial tool shed?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-build-a-level-editor-in-unity\">How to build a level editor in Unity<\/h2>\n\n\n\n<p>Level editors are not easy to make. There are several pieces to a level editor and, as you can imagine, plenty of code to write. It will already take lots of time to put together; thus, the setup portion will be skipped in favour of jumping straight to the code. However, there&#8217;s no reason to leave you with nothing to start with. A link will be provided to download the project with all assets (other than the scripts) and with the primary Unity scene prepared ahead of time. Also, another link will take you to the complete project source.<\/p>\n\n\n\n<p>Normally these articles start the project from scratch, but in this case, you&#8217;ll use a pre-existing project. To open this project, first open Unity as usual and click <em>Open <\/em>in the upper right<em>. <\/em>After this, navigate to the folder containing the project. If the folder is associated with a Unity project, the <em>Select Folder <\/em>button will appear<em>. <\/em>Click this button, and the project will begin loading. Keep in mind that you may have to enter into another folder before finding the correct directory that Unity can open.<\/p>\n\n\n\n<p>If downloading the project without the code, you&#8217;ll need to create five scripts inside the Unity Project. The names of these scripts are listed in the figure below:<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"231\" height=\"157\" class=\"wp-image-84460\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image.jpeg\"><br>Figure 1: List of scripts<\/p>\n\n\n\n<p>Level Editor \u2013 no code:<\/p>\n\n\n\n<figure class=\"wp-block-embed\"><div class=\"wp-block-embed__wrapper\">\nhttps:\/\/github.com\/prof-smash\/UnityLevelEditor_without_code\n<\/div><\/figure>\n\n\n\n<p>Level Editor \u2013 complete:<\/p>\n\n\n\n<figure class=\"wp-block-embed\"><div class=\"wp-block-embed__wrapper\">\nhttps:\/\/github.com\/prof-smash\/UnityLevelEditor_complete\n<\/div><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-the-code\">The Code<\/h2>\n\n\n\n<p>First, here\u2019s a breakdown of what all these scripts do. <em>EditorObject<\/em> and <em>LevelEditor <\/em>will be handling the level data. The <em>EditorObject <\/em>script will contain data for an individual object while the <em>LevelEditor <\/em>will hold the data for the entire level. When saving a level, any object with <em>EditorObject <\/em>attached will be noted in <em>LevelEditor, <\/em>and its data will be saved to a json file. <em>ManagerScript <\/em>will be the central hub of the editor, handling tasks like UI functions, level saving, and checking for certain objects. <em>CameraMove <\/em>and <em>MouseScript <\/em>are for user input, specifically placing or destroying objects and changing the camera view.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-editorobject\">The EditorObject<\/h3>\n\n\n\n<p>Starting with <em>EditorObject<\/em>, this script will contain the data of the various objects the user places within the level. It is still a component though, and thus will need <code>using UnityEngine<\/code> to inherit from <code>MonoBehaviour<\/code> so it can be attached to objects. As a result, you won&#8217;t be able to serialize the class entirely. Instead, you can create a public struct within the class called <code>Data<\/code> and serialize that (make sure you have <code>using System<\/code> at the top). Before creating the struct, remove the <code>Start<\/code> and <code>Update<\/code> functions, then make an enum called <code>ObjectType<\/code> and list <code>Cylinder<\/code><em>, <\/em><code>Cube<\/code><em>, <\/em><code>Sphere<\/code><em>, <\/em>and <code>Player<\/code> within the enum. Inside the struct are three public variables: a Vector3 named <code>pos<\/code>, Quaternion called <code>rot<\/code>, and an <code>ObjectType<\/code> specified as <code>objectType<\/code>. The first two variables will store the position and rotation of an object, while <code>objectType<\/code> will hold what kind of object this is attached to whether it be a cube or the player. Finally, a new public <em>Data <\/em>object is declared that you simply call <code>data<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">using System;\nusing UnityEngine;\npublic class EditorObject : MonoBehaviour\n{\n    public enum ObjectType { Cylinder, Cube, Sphere, Player};\n    [Serializable]\n    public struct Data\n    {\n        public Vector3 pos;\n        public Quaternion rot;\n        public ObjectType objectType;\n    }\n    public Data data;\n}<\/pre>\n\n\n\n<p>The code will look like Figure 2:<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"525\" height=\"295\" class=\"wp-image-84461\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-1.jpeg\"><br>Figure 2: <em>EditorObject <\/em>complete script<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-leveleditor\">The LevelEditor<\/h3>\n\n\n\n<p><em>LevelEditor <\/em>has one job \u2013 to hold all the data gathered from objects with <code>EditorObject<\/code> attached. This script will not be attached to any object and therefore does not need to inherit from <code>MonoBehaviour<\/code>. This also means the only two using statements you&#8217;ll need are <code>System.Collections.Generic<\/code> and <code>System. <\/code>Serialize the class, then define a public list with the type of <code>EditorObject.Data<\/code>. Name this list <code>editorObjects<\/code><em>, <\/em>and the <code>LevelEditor<\/code> script will be finished.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">using System.Collections.Generic;\nusing System;\n[Serializable]\npublic class LevelEditor \n{\n    public List&lt;EditorObject.Data&gt; editorObjects;\n}<\/pre>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"422\" height=\"204\" class=\"wp-image-84462\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-2.jpeg\"><br>Figure 3: <em>LevelEditor <\/em>complete script<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-cameramove\">CameraMove<\/h3>\n\n\n\n<p>The two scripts responsible for handling level data are finished, which leaves the rest of the level editor to program. <code>CameraMove<\/code> is a good place to begin as it has the least happening within it. You&#8217;ll need to have <code>using UnityEngine<\/code> and <code>using UnityEngine.UI<\/code> at the top to do everything required in this script. Next comes the variable declaration inside the class as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">public Slider cameraSpeedSlide;\npublic ManagerScript ms;\nprivate float xAxis;\nprivate float yAxis;\nprivate float zoom;\nprivate Camera cam;<\/pre>\n\n\n\n<p>At the start, you have a variable with the <code>Slider<\/code> type called <code>cameraSpeedSlide<\/code>. When looking at the editor, you should notice a UI object with the text \u2018Camera Speed\u2019 above it. This is the slider that <em>CameraMove <\/em>will be using. Most level editors have functionality that allows the user to adjust the speed of the camera, and this level editor will be no exception. By moving the slider, you can adjust how fast or slow the camera moves when pressing the movement keys or zooming the camera in and out. Next comes <code>ms<\/code>, which simply contains a reference to <code>ManagerScript<\/code>. This is only needed to check if certain menus are open and the <code>ManagerScript<\/code> is the one that will know this information.<\/p>\n\n\n\n<p>Three of the four private variables are all created for input, as you&#8217;ll see later on. The last variable, <code>cam<\/code><em>, <\/em>will store the object&#8217;s <code>Camera<\/code> component and manipulate certain values from there. Speaking of <code>cam<\/code><em>, <\/em>that brings you to the next step in the code. In the <code>Start<\/code> function, you&#8217;ll want to have <code>cam<\/code> get the aforementioned <code>Camera<\/code> component.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">cam = GetComponent&lt;Camera&gt;();<\/pre>\n\n\n\n<p>So far, the script will look like what&#8217;s shown in figure 4.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"452\" height=\"420\" class=\"wp-image-84463\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-3.jpeg\"><br>Figure 4: Variables and <em>Start <\/em>function for <em>CameraMove.<\/em><\/p>\n\n\n\n<p>The <code>Update<\/code> function is all that remains for this script to be completed. First, it will check with the <code>ManagerScript<\/code> if a save or load menu has been opened.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">if (ms.saveLoadMenuOpen == false)\n{\n}<\/pre>\n\n\n\n<p>Assuming they aren&#8217;t, the camera will move according to the player&#8217;s input. This will be where <code>xAxis<\/code><em>, <\/em><code>yAxis<\/code><em>, <\/em>and <code>zoom<\/code> come in.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">xAxis = Input.GetAxis(\"Horizontal\");\nyAxis = Input.GetAxis(\"Vertical\");\nzoom = Input.GetAxis(\"Mouse ScrollWheel\") * 10;<\/pre>\n\n\n\n<p>These variables will change as the player presses the keys bound to <code>Horizontal<\/code><em>, <\/em><code>Vertical<\/code><em>, <\/em>and Mouse <code>ScrollWheel<\/code>. In this case that would mean whenever the player presses any of the arrow keys or, you guessed it, scrolls the mouse wheel.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">transform.Translate(new Vector3(xAxis * \n    -cameraSpeedSlide.value, yAxis * -cameraSpeedSlide.value, 0.0f));\ntransform.position = new Vector3(\n\tMathf.Clamp(transform.position.x, -20, 20),\n\tMathf.Clamp(transform.position.y, 20, 20),\n\tMathf.Clamp(transform.position.z, -20, 20));\nif (zoom &lt; 0 &amp;&amp; cam.orthographicSize &gt;= -25)\n\tcam.orthographicSize -= zoom * -cameraSpeedSlide.value;\nif (zoom &gt; 0 &amp;&amp; cam.orthographicSize &lt;= -5)\n\tcam.orthographicSize += zoom * cameraSpeedSlide.value;<\/pre>\n\n\n\n<p>You&#8217;ll notice that <code>cameraSpeedSlide<\/code> is starting to see some use here. By pressing any of the movement keys, the camera will move based on which key is pressed multiplied by the value in the slider. The same thing is done when zooming the camera in or out later on. In addition, <code>Clamp<\/code> is being used to limit how far the camera can go. This is done to prevent said camera moving away from the area where the level editing happens. Similarly, the camera will only be zoomed in or out based on <code>cam's<\/code> <code>orthographicSize<\/code> variable.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"898\" height=\"420\" class=\"wp-image-84464\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-4.jpeg\"><br>Figure 5: <em>Update <\/em>function in <em>CameraMove<\/em>.<\/p>\n\n\n\n<p>This concludes the <code>CameraMove<\/code> script, but you&#8217;ll notice that the first <code>if<\/code> statement has an error. Of course, without doing anything with <code>ManagerScript<\/code> Visual Studio would naturally not know what the <code>saveLoadMenuOpen<\/code> boolean is. You can ignore this for the time being. For now, it&#8217;s time to focus attention on the <em>MouseScript<\/em>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-mousescript\">MouseScript<\/h3>\n\n\n\n<p>Much like how <em>CameraMove <\/em>controls what the user does with the camera, <em>MouseScript <\/em>will dictate the functionality of the user&#8217;s mouse input. In particular, <em>MouseScript <\/em>will allow the creation of objects, have an image of your current object to follow the mouse, and selecting which object to rotate.<\/p>\n\n\n\n<p>To start, <code>using UnityEngine.EventSystems<\/code> will need to be declared at the top. This script will be checking if the mouse is currently over any UI elements and will use <code>EventSystem<\/code> to do this. Checking this is important as you wouldn&#8217;t want users to accidentally place an object when they were trying to select a menu item instead. Once added, the next step is to declare variables inside the class.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">public enum LevelManipulation { Create, Rotate, Destroy };\npublic enum ItemList { Cylinder, Cube, Sphere, Player };\n[HideInInspector]\npublic ItemList itemOption = ItemList.Cylinder;\n[HideInInspector]\npublic LevelManipulation manipulateOption = LevelManipulation.Create;\n[HideInInspector]\npublic MeshRenderer mr;\n[HideInInspector]\npublic GameObject rotObject;\npublic Material goodPlace;\npublic Material badPlace;\npublic GameObject Player;\npublic ManagerScript ms;\nprivate Vector3 mousePos;\nprivate bool colliding;\nprivate Ray ray;\nprivate RaycastHit hit;<\/pre>\n\n\n\n<p>From the beginning, two enums are created called <code>LevelManipulation<\/code> and <code>ItemList<\/code>. Within these two enums are the list of ways to manipulate objects and the possible objects to create respectively. A handful of variables are then declared that are public but have <code>HideInInspector<\/code> above them. These variables will be used by another script, but they don&#8217;t need to be seen in the Unity editor. Variable declaration continues as normal from there with some public variables that will be given values in the editor and private variables that pertain to this script only. Finally, in the <code>Start<\/code> function, you add the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">mr = GetComponent&lt;MeshRenderer&gt;();<\/pre>\n\n\n\n<p>Anyone using your level editor will need a way to know what object they&#8217;re about to place. By manipulating this object&#8217;s <code>MeshRenderer<\/code><em>, <\/em>you can change the current mesh that is following your mouse, thus letting you know which object is currently set to be placed.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"584\" height=\"527\" class=\"wp-image-84465\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-5.jpeg\"><br>Figure 6: <em>MouseScript <\/em>variables and <em>Start <\/em>function.<\/p>\n\n\n\n<p>Moving along to the <code>Update<\/code> function, the first thing to do is allow this object to follow the mouse cursor. First, the current mouse coordinates will be set to <code>mousePos<\/code> followed by converting those screen coordinates to a position in the game world. Like with <code>CameraMove<\/code><em>, <\/em><code>Clamp<\/code> will also be used to limit where this object can go.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">mousePos = Input.mousePosition;\nmousePos = Camera.main.ScreenToWorldPoint(mousePos);\ntransform.position = new Vector3(\n\tMathf.Clamp(mousePos.x, -20, 20),\n\t0.75f,\n\tMathf.Clamp(mousePos.z, -20, 20));<\/pre>\n\n\n\n<p>You&#8217;ll also need a way to change the look of the mouse object based on whether you can place an item in your level or not. Unity&#8217;s <em>Materials <\/em>will be utilized here to give a visual indication that an object can be placed. To accomplish this, a <code>raycast<\/code> will be sent out checking for collision with any objects on the ninth layer. Why the ninth layer specifically? It&#8217;s because when creating objects in a level editor you want to distinguish the player created objects from any objects that might already be present in the current level such as the floor or walls. This is being done by creating a new layer in Unity called <code>SpawnedObjects<\/code> and assigning player created objects to this layer. Much like how you&#8217;d look for something in an array, you specify which layer you wish to check by index.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">ray = Camera.main.ScreenPointToRay(Input.mousePosition);\nif (Physics.Raycast(ray, out hit))\n{\n\tif (hit.collider.gameObject.layer == 9)\n\t{\n\t\tcolliding = true;\n\t\tmr.material = badPlace;\n\t}\n\telse\n\t{\n\t\tcolliding = false;\n\t\tmr.material = goodPlace;\n\t}\n}<\/pre>\n\n\n\n<p>Should the <code>raycast<\/code> detect an object in this layer, the material of the mouse object will be set to the material called <code>badPlace<\/code>. In other words, the colour of the object will change to red. Once the mouse leaves the colliding object, it will set the material to <code>goodPlace<\/code>, back to green, to let the user know that they can create an item in that space.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"537\" height=\"539\" class=\"wp-image-84466\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-6.jpeg\"><br>Figure 7: Beginning of <em>MouseScript&#8217;s Update <\/em>function<em>.<\/em><\/p>\n\n\n\n<p>The next step involves object manipulation. Unity will need to know what to do when the user clicks the mouse button. In addition, it will need to know what conditions are required to create, rotate, or destroy an object.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">if (Input.GetMouseButtonDown(0))\n{\n\tif (!EventSystem.current.IsPointerOverGameObject())\n\t{\n\t\tif (colliding == false &amp;&amp; manipulateOption == \n                        LevelManipulation.Create)\n\t\t\tCreateObject();\n\t\telse if (colliding == true &amp;&amp; manipulateOption == \n                        LevelManipulation.Rotate)\n\t\t\tSetRotateObject();\n\t\telse if (colliding == true &amp;&amp; manipulateOption == \n                        LevelManipulation.Destroy)\n\t\t{\n\t\t  if (hit.collider.gameObject.name.Contains(\n                               \"PlayerModel\"))\n\t\t\t\tms.playerPlaced = false;\n\t\t\tDestroy(hit.collider.gameObject);\n\t\t}\n\t}\n}<\/pre>\n\n\n\n<p>Once the user presses the left mouse button, a series of checks begin. Unity&#8217;s <code>EventSystem<\/code> comes in to play first by confirming that the mouse cursor is not over any UI elements. Assuming this is true then you first check if the there is any collision. If there isn&#8217;t, be sure also to check that the user is looking to create an object. Should there be a collision, you will want to be sure the player isn&#8217;t selecting an object to rotate or destroy. When it comes to destroying an object, you&#8217;ll want to see if the user is destroying an object that has \u2018PlayerModel\u2019 in its name. The rules of this level editor dictate that the user cannot place more than one player object. If that player object is destroyed, Unity will need to know that the user is permitted to place a player object the next time they desire to.<\/p>\n\n\n\n<p>At this point, the <code>Start<\/code> and <code>Update<\/code> functions are finished, and all that remains are the user defined functions, namely <code>CreateObject<\/code> and <code>RotateObject<\/code>. The average user will spend more time placing objects than changing their rotation, so <code>CreateObject<\/code> would be a good place to begin. As the name implies, it will create an object at the mouse cursor&#8217;s location. Which object it places is dependent on which item is currently selected from <code>ItemList<\/code>. This is also where the <code>EditorObject<\/code> script will start to see some use. Once an object is placed, <code>EditorObject<\/code> will be attached to the newly created object as a script component and assigned some data.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">void CreateObject()\n{\n\tGameObject newObj;\n\tif (itemOption == ItemList.Cylinder)\n\t{\n\t newObj = GameObject.CreatePrimitive(PrimitiveType.Cylinder);\n\t newObj.transform.position = transform.position;\n\t newObj.layer = 9;\n\t EditorObject eo = newObj.AddComponent&lt;EditorObject&gt;();\n\t eo.data.pos = newObj.transform.position;\n\t eo.data.rot = newObj.transform.rotation;\n\t eo.data.objectType = EditorObject.ObjectType.Cylinder;\n\t}\n\telse if (itemOption == ItemList.Cube)\n\t{\n\t newObj = GameObject.CreatePrimitive(PrimitiveType.Cube);\n\t newObj.transform.position = transform.position;\n\t newObj.layer = 9;\n\t EditorObject eo = newObj.AddComponent&lt;EditorObject&gt;();\n\t eo.data.pos = newObj.transform.position;\n\t eo.data.rot = newObj.transform.rotation;\n\t eo.data.objectType = EditorObject.ObjectType.Cube;\n\t}\n\telse if (itemOption == ItemList.Sphere)\n\t{\n\t newObj = GameObject.CreatePrimitive(PrimitiveType.Sphere);\n\t newObj.transform.position = transform.position;\n\t newObj.layer = 9;\n\t EditorObject eo = newObj.AddComponent&lt;EditorObject&gt;();\n\t eo.data.pos = newObj.transform.position;\n\t eo.data.rot = newObj.transform.rotation;\n\t eo.data.objectType = EditorObject.ObjectType.Sphere;\n\t}\n\telse if (itemOption == ItemList.Player)\n\t{\n\t if (ms.playerPlaced == false)\n\t  {\n\t   newObj = Instantiate(Player, \n                transform.position, Quaternion.identity);\n\t   newObj.layer = 9;\n\t   newObj.AddComponent&lt;CapsuleCollider&gt;();\n\t   newObj.GetComponent&lt;CapsuleCollider&gt;().center = \n                    new Vector3(0, 1, 0);\n\t   newObj.GetComponent&lt;CapsuleCollider&gt;().height = 2;\n\t   ms.playerPlaced = true;\n\t   EditorObject eo = newObj.AddComponent&lt;EditorObject&gt;();\n\t   eo.data.pos = newObj.transform.position;\n\t   eo.data.rot = newObj.transform.rotation;\n\t   eo.data.objectType = EditorObject.ObjectType.Player;\n\t  }\n\t}\n}<\/pre>\n\n\n\n<p>The one editor object that changes things up during creation is the player object. In this case, the only other step that&#8217;s being added is a <code>CapsuleCollider<\/code> component will be attached along with an <code>EditorObject<\/code> component. In addition, since a prefab is being created for the player <code>Instantiate<\/code> is used as opposed to <code>CreatePrimitive<\/code>. It also makes sure to check if a player object has already been placed. Assuming it hasn&#8217;t, the object is created normally.<\/p>\n\n\n\n<p>One last function remains before the completion of <code>MouseScript<\/code><em>, <\/em>and it&#8217;s a short one. This function will set the object that is selected to be rotated. It uses information gathered from a <code>raycast<\/code> to set <code>rotObject<\/code> to the current object the mouse is over.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">void SetRotateObject()\n{\n\trotObject = hit.collider.gameObject;\n\tms.rotSlider.value = rotObject.transform.rotation.y;\n}\n<\/pre>\n\n\n\n<p>This completes <em>MouseScript<\/em>, which means only one script remains for the level editor to have all the code needed. All those error messages wondering what those variables within <em>ManagerScript <\/em>are about to be resolved.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-the-managerscript\">The ManagerScript<\/h3>\n\n\n\n<p>As this is the script that will read and write your level data to and from a json file, you&#8217;ll need to start by adding <code>using System.IO<\/code>. You&#8217;ll also want <code>using UnityEngine.UI<\/code> in there as well since various UI elements will be needed here. Then you will need to declare several variables:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">[HideInInspector]\npublic bool playerPlaced = false;\n[HideInInspector]\npublic bool saveLoadMenuOpen = false;\npublic Animator itemUIAnimation;\npublic Animator optionUIAnimation;\npublic Animator saveUIAnimation;\npublic Animator loadUIAnimation;\npublic MeshFilter mouseObject;\npublic MouseScript user;\npublic Mesh playerMarker;\npublic Slider rotSlider;\npublic GameObject rotUI;\npublic InputField levelNameSave;\npublic InputField levelNameLoad;\npublic Text levelMessage;\npublic Animator messageAnim;\nprivate bool itemPositionIn = true;\nprivate bool optionPositionIn = true;\nprivate bool saveLoadPositionIn = false;\nprivate LevelEditor level;<\/pre>\n\n\n\n<p>Once again certain variables are made public but kept hidden in the Unity editor itself. Then several public variables are declared, all of which will be filled from the Unity Editor. In the end, a few booleans are declared that will let Unity know when certain menus are in view. The last variable, <code>level<\/code>, is essentially our level data. When it comes time to save the level, this will be used to gather a list of player created objects and serialize their information into a json file.<\/p>\n\n\n\n<p>Here you may go ahead and either comment out or delete <code>Update<\/code> as it will not be used in this script. After this, go ahead and enter the following code for <code>Start<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">rotSlider.onValueChanged.AddListener(delegate { \n          RotationValueChange(); });\nCreateEditor();<\/pre>\n\n\n\n<p>First, a listener will be made and applied to the event <code>onValueChanged<\/code> from <code>rotSlider<\/code>. This means that whenever the value of <code>rotSlider<\/code> has changed, the function <code>RotationValueChange<\/code> will be called, rotating the object selected. After this <code>CreateEditor<\/code> is called which creates a new <code>LevelEditor<\/code> and declares a new list of <code>EditorObject.Data<\/code> before returning <code>level<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">LevelEditor CreateEditor()\n{\n\tlevel = new LevelEditor();\n\tlevel.editorObjects = new List&lt;EditorObject.Data&gt;();\n\treturn level;\n}\nvoid RotationValueChange()\n{\n\tuser.rotObject.transform.localEulerAngles = \n            new Vector3(0, rotSlider.value, 0);\n\tuser.rotObject.GetComponent&lt;EditorObject&gt;().data.rot = \n            user.rotObject.transform.rotation;\n}<\/pre>\n\n\n\n<p>All of the preparations for <em>ManagerScript <\/em>are finished now. From the <code>Start<\/code> function down, the script should look similar to figure 8.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"765\" height=\"350\" class=\"wp-image-84467\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-7.jpeg\"><br>Figure 8: Start, CreateEditor, and RotationValueChanged methods.<\/p>\n\n\n\n<p>Menus are the bread and butter of any level editor, so it only makes sense to get the code for menus ready early on. A good place to start is with the menus appearing on screen. You&#8217;ll want a way to open and close menus for the user to make the desired room on their screen. The following functions will first check if the menu is currently positioned within the user&#8217;s screen or not. Depending on the answer, it will \u2018slide\u2019 the menu into the screen or out of the screen by setting a trigger in the respective menu&#8217;s <em>Animation <\/em>component.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">public void SlideItemMenu()\n{\n\tif (itemPositionIn == false)\n\t{\n\t\titemUIAnimation.SetTrigger(\"ItemMenuIn\");\n\t\titemPositionIn = true;\n\t}\n\telse\n\t{\n\t\titemUIAnimation.SetTrigger(\"ItemMenuOut\");\n\t\titemPositionIn = false;\n\t}\n}\npublic void SlideOptionMenu()\n{\n\tif (optionPositionIn == false)\n\t{\n\t\toptionUIAnimation.SetTrigger(\"OptionMenuIn\");\n\t\toptionPositionIn = true;\n\t}\n\telse\n\t{\n\t\toptionUIAnimation.SetTrigger(\"OptionMenuOut\");\n\t\toptionPositionIn = false;\n\t}\n}<\/pre>\n\n\n\n<p>Note also that these functions are made public. This is done in order to set the <code>OnClicked<\/code> event to the function from within the Unity Editor. Next comes the save and load menus which operate very similarly to the previous menu functions but with an extra command.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">public void ChooseSave()\n{\n\tif (saveLoadPositionIn == false)\n\t{\n\t\tsaveUIAnimation.SetTrigger(\"SaveLoadIn\");\n\t\tsaveLoadPositionIn = true;\n\t\tsaveLoadMenuOpen = true;\n\t}\n\telse\n\t{\n\t\tsaveUIAnimation.SetTrigger(\"SaveLoadOut\");\n\t\tsaveLoadPositionIn = false;\n\t\tsaveLoadMenuOpen = false;\n\t}\n}\npublic void ChooseLoad()\n{\n\tif (saveLoadPositionIn == false)\n\t{\n\t\tloadUIAnimation.SetTrigger(\"SaveLoadIn\");\n\t\tsaveLoadPositionIn = true;\n\t\tsaveLoadMenuOpen = true;\n\t}\n\telse\n\t{\n\t\tloadUIAnimation.SetTrigger(\"SaveLoadOut\");\n\t\tsaveLoadPositionIn = false;\n\t\tsaveLoadMenuOpen = false;\n\t}\n}<\/pre>\n\n\n\n<p>This extra line is simply setting <code>saveLoadMenuOpen<\/code> to true or false, depending on the circumstances. Recall from the <code>CameraMove<\/code> script the boolean that checks if the save or load menu is open. In that script, the check is performed to avoid camera movement while the menu is open.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"526\" height=\"494\" class=\"wp-image-84468\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-8.jpeg\"><br>Figure 9: <em>SlideOptionMenu <\/em>and <em>ChooseSave<\/em>, two of the four menu functions.<\/p>\n\n\n\n<p>Next up is the object selection functions. On the left side of the level editor is a menu with four buttons. Selecting any of these buttons sets the object to be placed as whichever object corresponds with that button. For example, clicking on the button with the cube in it sets the object to place as a cube object.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">public void ChooseCylinder()\n{\n\tuser.itemOption = MouseScript.ItemList.Cylinder;\n\tGameObject cylinder = \n             GameObject.CreatePrimitive(PrimitiveType.Cylinder);\n\tmouseObject.mesh = \n             cylinder.GetComponent&lt;MeshFilter&gt;().mesh;\n\tDestroy(cylinder);\n}\n    \npublic void ChooseCube()\n{\n\tuser.itemOption = MouseScript.ItemList.Cube;\n\tGameObject cube = \n             GameObject.CreatePrimitive(PrimitiveType.Cube);\n\tmouseObject.mesh = cube.GetComponent&lt;MeshFilter&gt;().mesh;\n\tDestroy(cube);\n}\npublic void ChooseSphere()\n{\n\tuser.itemOption = MouseScript.ItemList.Sphere;\n\tGameObject sphere = \n              GameObject.CreatePrimitive(PrimitiveType.Sphere);\n\tmouseObject.mesh = sphere.GetComponent&lt;MeshFilter&gt;().mesh;\n\tDestroy(sphere);\n}\npublic void ChoosePlayerStart()\n{\n\tuser.itemOption = MouseScript.ItemList.Player;\n\tmouseObject.mesh = playerMarker;\n}<\/pre>\n\n\n\n<p>In each function, you begin by setting <code>user.itemOption<\/code> (the <code>itemOption<\/code> variable in <em>MouseScript<\/em>) to the corresponding object. From there, you change the mesh of the mouse object by quickly creating an object in the world and setting <code>mouseObject.mesh<\/code> to the mesh of the created object. Once this is done, the newly created object is immediately destroyed. <code>ChoosePlayerStart<\/code> mixes things up by instead taking the mesh from <code>playerMarker<\/code>, a variable created early on that stores the player mesh.<\/p>\n\n\n\n<p>Why the distinction? Ordinarily, your object selection methods might look like <code>ChoosePlayerStart<\/code><em>, <\/em>but in this example, the user created objects are all basic geometric shapes like cubes. To save on variable declarations, the script instead uses <code>CreatePrimitive<\/code> to make the mesh needed for the mouse. You can, of course, make variables holding these different meshes if you so desire. From there you can make all methods functionally identical or perhaps use a different method altogether. <code>CreatePrimitive<\/code> was used to keep the code simple and easier to follow.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"642\" height=\"494\" class=\"wp-image-84469\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-9.jpeg\"><br>Figure 10: All object selection functions.<\/p>\n\n\n\n<p>That&#8217;s one major menu done, but there&#8217;s still the other part of what is being referred to as the \u2018options\u2019 menu. The save and load buttons are complete, but the remaining buttons currently do nothing. Once again, three separate functions will be used, each doing similar tasks with some slight differences.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">public void ChooseCreate()\n{\n\tuser.manipulateOption = MouseScript.LevelManipulation.Create;\n\tuser.mr.enabled = true;\n\trotUI.SetActive(false);     \n}\npublic void ChooseRotate()\n{\n\tuser.manipulateOption = MouseScript.LevelManipulation.Rotate;\n\tuser.mr.enabled = false;\n\trotUI.SetActive(true);\n}\npublic void ChooseDestroy()\n{\n\tuser.manipulateOption = MouseScript.LevelManipulation.Destroy;\n\tuser.mr.enabled = false;\n\trotUI.SetActive(false);\n}<\/pre>\n\n\n\n<p>With each function, you first set what manipulation option the player is using. From there you enable or disable the mouse object&#8217;s mesh renderer followed by activating or deactivating the rotation slider. This concludes the main menu functionality of the level editor, leaving only the save and load methods. Now the fun begins as you write code to save your level data to a json file stored within a folder that you request Unity to make. To start, you&#8217;ll need to gather up all objects with the <code>EditorObject<\/code> component, which of course signifies objects that were user-generated. Once you have your array of objects, you&#8217;ll need to add the data from these objects to the list created in <code>level<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">public void SaveLevel()\n{\n\tEditorObject[] foundObjects = FindObjectsOfType&lt;EditorObject&gt;();\n\tforeach (EditorObject obj in foundObjects)\n\t\tlevel.editorObjects.Add(obj.data); \n}<\/pre>\n\n\n\n<p>Next, comes the directory and file creation. Unity has built-in json functionality that makes reading and writing a json file considerably easier on you as the developer. This function, called <code>JsonUtility.ToJson<\/code>, will be used to create the json file. Next, a folder is specified by getting the data path of the project, followed by adding the directory <code>LevelData<\/code>. A default filename is created followed by checking to see if the user entered anything in the level name field in the editor. Assuming they have, the json file is given that name. The existence of the <code>LevelData<\/code> directory is confirmed, and finally, the file path is set by using <code>Path.Combine<\/code> which completes all the prep work before saving the file.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">string json = JsonUtility.ToJson(level);\nstring folder = Application.dataPath + \"\/LevelData\/\";\nstring levelFile = \"\";\nif (levelNameSave.text == \"\")\n\tlevelFile = \"new_level.json\";\nelse\n\tlevelFile = levelNameSave.text + \".json\";\nif (!Directory.Exists(folder))\n\tDirectory.CreateDirectory(folder);\nstring path = Path.Combine(folder, levelFile);<\/pre>\n\n\n\n<p>A quick check is performed to make sure a file of the same name already exists. If it does, then it is deleted and simply replaced with the new file. You use <code>File.WriteAllText<\/code> to save the file to the path specified, and the level file is created. Remove the save menu from there, clear out the input field and remove the focus from it, and send a message to the user letting them know their level was saved successfully.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">if (File.Exists(path))\n\tFile.Delete(path);\nFile.WriteAllText(path, json); \nsaveUIAnimation.SetTrigger(\"SaveLoadOut\");\nsaveLoadPositionIn = false;\nsaveLoadMenuOpen = false;\nlevelNameSave.text = \"\";\nlevelNameSave.DeactivateInputField();\nlevelMessage.text = levelFile + \" saved to LevelData folder.\";\nmessageAnim.Play(\"MessageFade\", 0, 0);<\/pre>\n\n\n\n<p>Saving your data is now possible in your level editor. The complete function is shown in the figure below if you wish to check your code.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"593\" height=\"545\" class=\"wp-image-84470\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-10.jpeg\"><br>Figure 11: The complete <em>SaveLevel <\/em>function.<\/p>\n\n\n\n<p>Naturally, someone may wish to make adjustments to their created level later on. A similar menu to the save menu is created for that purpose. The actual functionality of loading a level is effectively doing <code>SaveLevel<\/code> in reverse. You&#8217;ll still begin by specifying the file path to be used when searching for a level to load.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">public void LoadLevel()\n{\n\tstring folder = Application.dataPath + \"\/LevelData\/\";\n\tstring levelFile = \"\";\n\tif (levelNameLoad.text == \"\")\n\t\tlevelFile = \"new_level.json\";\n\telse\n\t\tlevelFile = levelNameLoad.text + \".json\";\n\t\n\tstring path = Path.Combine(folder, levelFile);\n}<\/pre>\n\n\n\n<p>From here you first check to see if the file exists. If not, you&#8217;ll display a message stating that the file could not be found. Otherwise, you&#8217;ll once again gather all the current objects with <code>EditorObject<\/code> attached but this time delete them. This is done simply to make room for the objects about to be loaded from the json file. You also go ahead and set <code>playerPlaced<\/code> to false as the original player object will be destroyed, and you can&#8217;t confirm if the json file will have data for a player object. After that, you read all the text from the file and enter that data into <code>level<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">if (File.Exists(path))\n{\n\tEditorObject[] foundObjects = \n             FindObjectsOfType&lt;EditorObject&gt;();\n\tforeach (EditorObject obj in foundObjects)\n\tDestroy(obj.gameObject);\n\tplayerPlaced = false;\n\t\n\tstring json = File.ReadAllText(path);\n\tlevel = JsonUtility.FromJson&lt;LevelEditor&gt;(json);\n\tCreateFromFile();\n}\nelse\n{\n\tloadUIAnimation.SetTrigger(\"SaveLoadOut\");\n\tsaveLoadPositionIn = false;\n\tsaveLoadMenuOpen = false;\n\tlevelMessage.text = levelFile + \" could not be found!\";\n\tmessageAnim.Play(\"MessageFade\", 0, 0);\n\tlevelNameLoad.DeactivateInputField();\n}<\/pre>\n\n\n\n<p>In case you wish to compare, figure 12 shows the completed <code>LoadLevel<\/code> function.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"613\" height=\"547\" class=\"wp-image-84471\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-11.jpeg\"><br>Figure 12: The complete <em>LoadLevel <\/em>function.<\/p>\n\n\n\n<p>You&#8217;re not finished yet! While <code>level<\/code> has been given some data, it doesn&#8217;t actually do anything with it. Its only job is to hold that data for saving and loading levels. How would you populate the world with the objects listed in the json file? You&#8217;ll notice an undefined method, <code>CreateFromFile<\/code>, in the code near the end there. This method acts almost exactly like the code from <code>MouseScript<\/code> that creates objects when the left mouse button is clicked. It goes through the now populated <code>editorObjects<\/code> list in <code>level<\/code> and creates objects based on the information in that list. The only major difference is that it will also send a message when it&#8217;s complete letting the user know that the level has finished loading.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">void CreateFromFile()\n{\n\tGameObject newObj;\n\tfor (int i = 0; i &lt; level.editorObjects.Count; i++)\n\t{\n\t   if (level.editorObjects[i].objectType == \n               EditorObject.ObjectType.Cylinder)\n\t    {\n\t\tnewObj = \n                   GameObject.CreatePrimitive(PrimitiveType.Cylinder);\n\t\tnewObj.transform.position = level.editorObjects[i].pos;\n\t\tnewObj.transform.rotation = level.editorObjects[i].rot;\n\t\tnewObj.layer = 9;\n\t\tEditorObject eo = newObj.AddComponent&lt;EditorObject&gt;();\n\t\teo.data.pos = newObj.transform.position;\n\t\teo.data.rot = newObj.transform.rotation;\n\t\teo.data.objectType = EditorObject.ObjectType.Cylinder;\n\t}\n\telse if (level.editorObjects[i].objectType == \n                EditorObject.ObjectType.Cube)\n\t{\n\t\tnewObj = GameObject.CreatePrimitive(PrimitiveType.Cube);\n\t\tnewObj.transform.position = level.editorObjects[i].pos;\n\t\tnewObj.transform.rotation = level.editorObjects[i].rot;\n\t\tnewObj.layer = 9;\n\t\tEditorObject eo = newObj.AddComponent&lt;EditorObject&gt;();\n\t\teo.data.pos = newObj.transform.position;\n\t\teo.data.rot = newObj.transform.rotation;\n\t\teo.data.objectType = EditorObject.ObjectType.Cube;\n\t}\n\telse if (level.editorObjects[i].objectType == \n                   EditorObject.ObjectType.Sphere)\n\t{\n\t\tnewObj = \n                   GameObject.CreatePrimitive(PrimitiveType.Sphere);\n\t\tnewObj.transform.position = level.editorObjects[i].pos;\n\t\tnewObj.transform.rotation = level.editorObjects[i].rot;\n\t\tnewObj.layer = 9;\n\t\tEditorObject eo = newObj.AddComponent&lt;EditorObject&gt;();\n\t\teo.data.pos = newObj.transform.position;\n\t\teo.data.rot = newObj.transform.rotation;\n\t\teo.data.objectType = EditorObject.ObjectType.Sphere;\n\t}\n\telse if (level.editorObjects[i].objectType == \n               EditorObject.ObjectType.Player)\n\t{\n\t\tnewObj = Instantiate(user.Player, \n                         transform.position, Quaternion.identity);\n\t\tnewObj.layer = 9;\n\t\tnewObj.AddComponent&lt;CapsuleCollider&gt;();\n\t\tnewObj.GetComponent&lt;CapsuleCollider&gt;().center = \n                    new Vector3(0, 1, 0);\n\t\tnewObj.GetComponent&lt;CapsuleCollider&gt;().height = 2;\n\t\tnewObj.transform.position = level.editorObjects[i].pos;\n\t\tnewObj.transform.rotation = level.editorObjects[i].rot;\n\t\tplayerPlaced = true;\n\t\tEditorObject eo = newObj.AddComponent&lt;EditorObject&gt;();\n\t\teo.data.pos = newObj.transform.position;\n\t\teo.data.rot = newObj.transform.rotation;\n\t\teo.data.objectType = EditorObject.ObjectType.Player;\n\t}\n\t}\n\tlevelNameLoad.text = \"\";\n\tlevelNameLoad.DeactivateInputField();\n\t\n\tloadUIAnimation.SetTrigger(\"SaveLoadOut\");\n\tsaveLoadPositionIn = false;\n\tsaveLoadMenuOpen = false;\n\t\n\tlevelMessage.text = \"Level loading...done.\";\n\tmessageAnim.Play(\"MessageFade\", 0, 0);\n}<\/pre>\n\n\n\n<p>Now the code is complete. All that remains is to assign the various buttons their respective functions and fill the various variables in the Unity editor. Save the VS project before closing it and returning to the Unity project.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-finishing-the-project\">Finishing the Project<\/h2>\n\n\n\n<p>First, the <em>CameraMove, ManagerScript, <\/em>and <em>MouseScript <\/em>will need to be attached to their respective objects. <em>CameraMove <\/em>will be attached to the <em>Main Camera <\/em>object, <em>MouseScript <\/em>attached to <em>Mouse<\/em>, and <em>ManagerScript <\/em>attached to <em>Manager<\/em>. You can add these by clicking on the object in the <em>Hierarchy <\/em>and dragging the script from the <em>Assets <\/em>window into the <em>Inspector<\/em> or clicking <em>Add Component <\/em>in the <em>Inspector <\/em>and searching the script from there. Once complete, you can then move on to filling in the fields for the various script components. Having the least number of variables to fill, it&#8217;s recommended to start with <em>CameraMove<\/em>.<\/p>\n\n\n\n<p>You may wish to lock the <em>Inspector <\/em>to make this part easier. To do this click the lock icon in the upper right corner of the <em>Inspector <\/em>window. Doing so will make field assignment easier. In addition, if the fields don&#8217;t appear for any reason, double check the code as there may be a compile error. The debug log will let you know where the error is located.<\/p>\n\n\n\n<p>Make sure the <em>Canvas <\/em>object is expanded, then expand <em>CameraSpeed <\/em>to reveal two more objects. Click and drag <em>CameraSpeedSlider<\/em> to the <em>Camera Speed Slide <\/em>field in the <em>CameraMove <\/em>component. As you may have guessed, <em>CameraSpeed <\/em>is the slider that will control how fast the camera moves. In addition, click and drag the <em>Manager <\/em>object to the <em>ms <\/em>field to give <em>CameraMove <\/em>its <em>ManagerScript <\/em>reference.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"938\" height=\"617\" class=\"wp-image-84472\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-12.jpeg\"><br>Figure 13: Setting the variables for <em>CameraMove<\/em>.<\/p>\n\n\n\n<p>Moving along to the <em>Mouse <\/em>object, you&#8217;ll need to locate the materials for good and bad placement. They can be found in the <em>Assets <\/em>window by selecting the <em>Materials <\/em>folder.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"327\" height=\"227\" class=\"wp-image-84473\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-13.jpeg\"><br>Figure 14: The <em>Materials <\/em>folder.<\/p>\n\n\n\n<p>Drag <em>BadPlaceMat <\/em>to the <em>Bad Place <\/em>field and <em>GoodPlaceMat <\/em>to the <em>Good Place <\/em>field to cover the materials.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"830\" height=\"233\" class=\"wp-image-84474\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-14.jpeg\"><br>Figure 15: Setting material variables in <em>MouseScript<\/em>.<\/p>\n\n\n\n<p>The <em>Player <\/em>object can be found under the <em>Prefabs <\/em>folder in the <em>Assets <\/em>window. Once there you will need to click and drag the <em>PlayerModel <\/em>prefab into the <em>Player <\/em>field. Afterwards, click and drag <em>Manager <\/em>from the <em>Hierarchy <\/em>to the <em>ms <\/em>field like you would with <em>CameraMove<\/em>.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"929\" height=\"488\" class=\"wp-image-84475\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-15.jpeg\"><br>Figure 16: Finalizing <em>MouseScript<\/em>.<\/p>\n\n\n\n<p>Now you&#8217;re at the big one. <em>ManagerScript <\/em>has quite a few fields that need filling, starting with the <em>Animator <\/em>fields. From the <em>Hierarchy<\/em>, <em>ItemMenu <\/em>will go to <em>Item UI Animation, ManipulateMenu <\/em>goes to <em>Option UI Animation<\/em>, <em>SaveMenu <\/em>goes to <em>Save UI Animation, <\/em>and <em>LoadMenu <\/em>goes towards <em>Load UI Animation<\/em>. From here, you can take <em>Mouse <\/em>and apply that to <em>Mouse Object <\/em>and <em>User <\/em>fields in the component.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"929\" height=\"488\" class=\"wp-image-84476\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-16.jpeg\"><br>Figure 17: Setting animation and mouse fields.<\/p>\n\n\n\n<p><em>PlayerMarker <\/em>is the mesh that will be assigned to the <em>Mouse <\/em>object whenever the player icon is selected in the level editor. Once again you&#8217;ll go to <em>Prefabs <\/em>in the <em>Assets <\/em>window. Expand the <em>PlayerModel <\/em>prefab and locate <em>Alpha_Surface<\/em>. This is the mesh that <em>Manager Script <\/em>is looking for. Click and drag it to <em>Player Marker <\/em>to fill that field.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"488\" class=\"wp-image-84477\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-17.jpeg\"><br>Figure 18: Defining the player mesh.<\/p>\n\n\n\n<p>Back at the <em>Hierarchy<\/em>, find <em>RotateUI <\/em>and drag that to <em>Rot UI<\/em>. Expand <em>RotateUI <\/em>to reveal <em>RotateSlider<\/em>. Drag this to the <em>Rot Slider <\/em>field.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"414\" class=\"wp-image-84478\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-18.jpeg\"><br>Figure 19: Filling rotation related fields.<\/p>\n\n\n\n<p>Find the <em>SaveMenu <\/em>and <em>LoadMenu <\/em>objects and expand them to reveal three child objects each. The <em>ManagerScript <\/em>now needs the input fields that help it assign a name to a json file as well as search for one to load. As the names suggest, <em>LevelNameSave <\/em>will be applied to <em>Level Name Save <\/em>in the <em>ManagerScript <\/em>component while <em>LevelNameLoad <\/em>will fill <em>Level Name Load<\/em>.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"319\" class=\"wp-image-84479\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-19.jpeg\"><br>Figure 20: Filling <em>Level Name Save <\/em>and <em>Load<\/em>.<\/p>\n\n\n\n<p>Finally, the <em>SaveLoadMessage <\/em>object in the <em>Hierarchy <\/em>will fill both the remaining fields in <em>ManagerScript<\/em>.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"319\" class=\"wp-image-84480\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-20.jpeg\"><br>Figure 21: Setting the final fields in <em>ManagerScript<\/em>.<\/p>\n\n\n\n<p>Every script variable has been given a value now. All that remains is to assign functions to the various buttons in the project. Assigning the function has been made easier as the only object the buttons will need to get the function from is <em>Manager<\/em>. To assign a function, you must select the button you wish to edit. The example below will use the <em>OpenClose <\/em>child object under <em>ItemMenu<\/em>. From there, drag <em>Manager <\/em>from the <em>Hierarchy <\/em>to the <em>Object <\/em>field in the button&#8217;s <em>OnClick <\/em>event.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"932\" height=\"279\" class=\"wp-image-84481\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-21.jpeg\"><br>Figure 22: Letting <em>OnClick <\/em>know the function comes from the <em>Manager <\/em>object.<\/p>\n\n\n\n<p>Select the <em>No Function <\/em>dropdown and navigate to <em>ManagerScript-&gt;SlideItemMenu <\/em>to assign the function.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"520\" height=\"366\" class=\"wp-image-84482\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-22.jpeg\"><br>Figure 23: Selecting a function.<\/p>\n\n\n\n<p>Each button will have to be assigned this way. The table below will help you know which function goes to which button. In addition, the <em>Hierarchy <\/em>path to each button will be shown for easier navigation through the project.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Button<\/strong><\/td><td><strong>Function<\/strong><\/td><\/tr><tr><td>Canvas-&gt;ItemMenu-&gt;OpenClose<\/td><td>SlideItemMenu()<\/td><\/tr><tr><td>Canvas-&gt;ItemMenu-&gt;CylinderSelect<\/td><td>ChooseCylinder()<\/td><\/tr><tr><td>Canvas-&gt;ItemMenu-&gt;BoxSelect<\/td><td>ChooseCube()<\/td><\/tr><tr><td>Canvas-&gt;ItemMenu-&gt;SphereSelect<\/td><td>ChooseSphere()<\/td><\/tr><tr><td>Canvas-&gt;ItemMenu-&gt;PlayerStart<\/td><td>ChoosePlayerStart()<\/td><\/tr><tr><td>Canvas-&gt;ManipulateMenu-&gt;OpenClose<\/td><td>SlideOptionMenu()<\/td><\/tr><tr><td>Canvas-&gt;ManipulateMenu-&gt;CreateOption<\/td><td>ChooseCreate()<\/td><\/tr><tr><td>Canvas-&gt;ManipulateMenu-&gt;RotateOption<\/td><td>ChooseRotate()<\/td><\/tr><tr><td>Canvas-&gt;ManipulateMenu-&gt;DestroyOption<\/td><td>ChooseDestroy()<\/td><\/tr><tr><td>Canvas-&gt;ManipulateMenu-&gt;SaveOption<\/td><td>ChooseSave()<\/td><\/tr><tr><td>Canvas-&gt;ManipulateMenu-&gt;LoadOption<\/td><td>ChooseLoad()<\/td><\/tr><tr><td>Canvas-&gt;SaveMenu-&gt;SaveLevelButton<\/td><td>SaveLevel()<\/td><\/tr><tr><td>Canvas-&gt;SaveMenu-&gt;CloseButton<\/td><td>ChooseSave()<\/td><\/tr><tr><td>Canvas-&gt;LoadMenu-&gt;LoadLevelButton<\/td><td>LoadLevel()<\/td><\/tr><tr><td>Canvas-&gt;LoadMenu-&gt;CloseButton<\/td><td>ChooseLoad()<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"caption\">Table 1: All buttons and their corresponding functions<\/p>\n\n\n\n<p>At last, the level editor is complete. Press the play button at the top of the editor and give it a try. Navigate to the project folder in Windows Explorer to check out the created LevelData folder and find your levels saved inside.<\/p>\n\n\n\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" width=\"874\" height=\"391\" class=\"wp-image-84483\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/06\/word-image-23.jpeg\"><br>Figure 24: Completed level editor.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-conclusion\">Conclusion<\/h2>\n\n\n\n<p>Though certainly not an easy task, creating a level editor can have several benefits to both development and the user experience. For the developer, it can be used to make large projects easier to manage in a team or streamline the level creation process. As for the players, they have a fun tool to express their creativity with. There are also long-term benefits to creating a level editor, with the biggest benefit being a longer and stronger interest in the game. Level editors are certainly a lot of work to put together, but it pays off in the end by allowing the project to have near limitless content created for it.<\/p>\n\n\n\n<p><strong>Read also:<\/strong> <a href=\"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/a-tdd-journey-2-naming-tests-mocking-frameworks-dependency-injection\/\" target=\"_blank\" rel=\"noreferrer noopener\">Test-driven development for game components<\/a><\/p>\n\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: Building a Level Editor in Unity<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. How do you create a custom level editor in Unity?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Create five C# scripts: an EditorObject MonoBehaviour to store per-object data (type, position), a static LevelEditor class to hold all placed objects, a CameraMove script for viewport navigation, a MouseScript for raycasting and object placement\/deletion, and a ManagerScript to coordinate UI elements and handle save\/load operations. Attach these scripts to appropriate GameObjects in your Unity scene and wire up the UI elements (buttons, sliders) in the Inspector.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. How do you save and load level data in Unity?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Use Unity\u2019s built-in serialization to convert your level data (object types, positions, rotations) to a savable format. The ManagerScript coordinates save operations by iterating through all EditorObjects tracked by the LevelEditor, serializing their data, and writing it to a file. Loading reverses the process: read the file, deserialize the data, and instantiate the corresponding prefabs at the saved positions.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Step-by-step tutorial for building a custom level editor in Unity using C# scripts. Covers camera controls, mouse input, object placement, save\/load systems, and EditorObject data management.&hellip;<\/p>\n","protected":false},"author":317499,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538,53],"tags":[],"coauthors":[52549],"class_list":["post-84459","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","category-featured"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/84459","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/317499"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=84459"}],"version-history":[{"count":10,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/84459\/revisions"}],"predecessor-version":[{"id":108989,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/84459\/revisions\/108989"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=84459"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=84459"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=84459"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=84459"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}