Tile Editor for Unity: Part II

As I mentioned before, for those who only want to use the editor, as soon as the Asset is available in the Unity Asset Store, you will be able to download it for free and use it.

This is the second part of a series of posts about a small tile editor I’m creating for Unity. If you haven’t read the first part yet, you can check it out here.

The whole explanation is divided in the following parts:

  1. Editor Window Creation
  2. Saving Status
  3. Menus
  4. Game Objects Actions: Create, Delete, Edit
  5. Layers
  6. Grid
  7. Snapping
  8. Transformations
  9. Snapping: Extended

This week we are going to cover point number 3: menus.

Menus

This section includes the concept of game object menu, which is an abstraction of a group of game objects (represented by prefabs) that are loaded inside a menu for the user to dynamically create instances of them in the editor.

As you can see in the picture above, this section was called “Game Objects” and represents a list of menus used to add new prefabs to the editor. It’s possible to add different menus, each one with a set of prefabs to add to the scene, it’s also possible to customize the menu a little to better organize the elements inside of it and remove it if necessary.

In the previous post, we created the main class file for out extension “LevelCreatorEditor”, which handles how the menus are created, all the operations for each menu and general operations as well. As we mentioned before, all the menus are rendered inside the method “OnGUI” and for this new section we added the “BuildMenuObjects” method.

  1. List menus;
  2. EditorObjectsMenu objMenu;
  3.  
  4. void OnGUI()
  5. {
  6.    if (menus == null)
  7.       menus = new List();
  8.    //Header
  9.    BuildMenuHeader();
  10.   //Objects
  11.   BuildMenuObjects();
  12. }
  13.  
  14. void BuildMenuObjects()
  15. {
  16.    int curSel;
  17.    List removedMenus;
  18.  
  19.    //If the group is empty, we disable this section
  20.    EditorGUI.BeginDisabledGroup(layers.Count <= 0);
  21.    //You can change the label if you want
  22.    GUILayout.Label("GAME OBJECTS", EditorStyles.boldLabel);
  23.    //Add menu button
  24.    if (GUILayout.Button("Add Menu"))
  25.       menus.Add(new EditorObjectsMenu());
  26.    removedMenus = new List();
  27.    //GUILayout.BeginVertical();
  28.    gameobjectsScrollPos = GUILayout.BeginScrollView(gameobjectsScrollPos, false, true);
  29.    //Menus
  30.    foreach (EditorObjectsMenu m in menus)
  31.    {
  32.       //Title
  33.       GUILayout.Label("Menu: " + m.folder, EditorStyles.boldLabel);
  34.       //Columns
  35.       m.columns = EditorGUILayout.IntField("Columns", m.columns);
  36.       //Folder's location
  37.       EditorGUILayout.BeginHorizontal();
  38.       m.folder = EditorGUILayout.TextField("Folder Name", m.folder);
  39.       if (GUILayout.Button("Load"))
  40.          m.LoadPrefabs();
  41.       EditorGUILayout.EndHorizontal();
  42.       //Load elements
  43.       if (m.tiles != null && m.tiles.Length >= 0 && m.columns >= 0)
  44.       {
  45.          curSel = GUILayout.SelectionGrid(m.selGridInt, m.tiles, m.columns, GUILayout.Width(position.width - 20),GUILayout.Height(100));
  46.          if (curSel != m.selGridInt)
  47.          {
  48.             m.selGridInt = curSel;
  49.             objMenu = m;
  50.             DeactivateMenus();
  51.          }
  52.       }
  53.  
  54.       //Remove menu
  55.       if (GUILayout.Button("Remove"))
  56.          removedMenus.Add(m);
  57.    }
  58.    GUILayout.EndScrollView();
  59.    EditorGUI.EndDisabledGroup();
  60.  
  61.    //Clean menus
  62.    foreach (EditorObjectsMenu m in removedMenus)
  63.       menus.Remove(m);
  64. }
  65.  
  66. void DeactivateMenus()
  67. {
  68.    foreach (EditorObjectsMenu m in menus)
  69.    {
  70.       if (objMenu == null)
  71.          m.selGridInt = -1;
  72.       else
  73.       {
  74.          if (objMenu != m)
  75.             m.selGridInt = -1;
  76.       }
  77.    }
  78. }

Menus are logically represented using a List of “EditorObjectsMenu” which is a new class that has handles all the functionality inside a menu: loading new prefabs and converting them into usable icons, serializing the data to store changes for future use, etc.

  1. public class EditorObjectsMenu
  2. {
  3.    //Default folder that appears in the folder field
  4.    public const string DEFAULT_FOLDER = "Tiles";
  5.    //This will change depending on the folder's name
  6.    public string name;
  7.    //Folder's name
  8.    public string folder;
  9.    //Selected element
  10.    public int selGridInt;
  11.    //To organize the elements graphically, number of columns
  12.    public int columns;
  13.    //Textures of each prefab (from SpriteRenderer)
  14.    public Texture[] tiles;
  15.    //Prefabs that represent game objects
  16.    public Object[] prefabs;
  17.  
  18.    public EditorObjectsMenu()
  19.    {
  20.       selGridInt = -1;
  21.       name = DEFAULT_FOLDER;
  22.       folder = DEFAULT_FOLDER;
  23.       columns = 3;
  24.    }
  25.  
  26.    /// Load all the prefabs in folder "folder"
  27.    public void LoadPrefabs()
  28.    {
  29.       GameObject obj;
  30.       prefabs = Resources.LoadAll(folder, typeof(Object));
  31.       tiles = new Texture[prefabs.Length];
  32.       for (int i = 0; i &lt; prefabs.Length; i++)
  33.       {
  34.          obj = ((GameObject)prefabs[i]);
  35.  
  36.          if (obj.GetComponent().sprite == null)
  37.          {
  38.             for (int j = 0; j &lt; obj.transform.childCount; j++)
  39.             {
  40.                if (obj.transform.GetChild(j).GetComponent().sprite != null)
  41.                {
  42.                   tiles[i] = obj.transform.GetChild(j).GetComponent().sprite.texture;
  43.                   break;
  44.                }
  45.             }
  46.          }
  47.          else
  48.             tiles[i] = obj.GetComponent().sprite.texture;
  49.       }
  50.    }
  51.  
  52.    /// Get the the selected element
  53.    public Object GetCurrentSelection()
  54.    {
  55.       Object sel;
  56.  
  57.       sel = null;
  58.       if (selGridInt != -1)
  59.          sel = prefabs[selGridInt];
  60.  
  61.       return sel;
  62.    }
  63.  
  64.    /// Transform the object's parameters into a string (to save the state of the object)
  65.    public string Serialize()
  66.    {
  67.       return name + "," + folder + "," + selGridInt + "," + columns;
  68.    }
  69.  
  70.    /// Takes a serialized string and loads all the parameters of the object
  71.    public void Deserialize(string data)
  72.    {
  73.       string[] attributes = data.Split(',');
  74.  
  75.       name = attributes[0];
  76.       folder = attributes[1];
  77.       selGridInt = int.Parse(attributes[2]);
  78.       columns = int.Parse(attributes[3]);
  79.    }
  80. }

The EditorObjectsMenu class has properties such as: name, folder, selGridInt, columns, tiles and prefabs. Name and folder are the same but one is used to display the id in the GUI and the other to decide where should the prefabs be loaded from; selGridInt represents the selected element (prefab); columns, is there to customize the number of columns each menu displays; tiles is an array of textures that are mapped to the prefabs and help to display the prefabs buttons inside the menu.

LoadPredabs takes the folder path (which should be inside the physical folder “Resources” and loads all the prefabs inside that path. This data is stored in the textures and prefabs arrays to be displayed in the menu.

GetCurrentSelection just returns which of the current element was selcted by the user and with that we can create instances of that object in the editor.

Finally Serialize and Deserialize were created to easily convert the data to strings and store it as we explained in the previous post.

Going back to the previous method “BuildMenuObjects” now that the EditorsObjectMenu is defined, we can see that the new attribute called “menus” is a list of elements of EditorsObjectMenu class. With this variable we represent logically all the menus that are rendered in the GUI.

Adding Menus

We create a new button inside the GUI called “Add Menu”, the code can be seen in the “BuildMenuObjects” function above:

  1. if (GUILayout.Button("Add Menu"))
  2.    menus.Add(new EditorObjectsMenu());

Here we just create a new instance of the class and add it to the menu list and it automatically will render the information and inputs for that new menu.

Rendering Menus

All the menus are rendered inside a foreach instruction that handles each input of the menu separately:

  1. foreach (EditorObjectsMenu m in menus)
  2. {
  3.    //Title
  4.    GUILayout.Label("Menu: " + m.folder, EditorStyles.boldLabel);
  5.    //m.name = EditorGUILayout.TextField("Title", m.name);
  6.    //Columns
  7.    m.columns = EditorGUILayout.IntField("Columns", m.columns);
  8.    //Folder's location
  9.    EditorGUILayout.BeginHorizontal();
  10.    m.folder = EditorGUILayout.TextField("Folder Name", m.folder);
  11.    if (GUILayout.Button("Load"))
  12.    m.LoadPrefabs();
  13.    EditorGUILayout.EndHorizontal();
  14.    //Load elements
  15.    if (m.tiles != null && m.tiles.Length >= 0 && m.columns >= 0)
  16.    {
  17.       curSel = GUILayout.SelectionGrid(m.selGridInt, m.tiles, m.columns, GUILayout.Width(position.width - 20),GUILayout.Height(100));
  18.       if (curSel != m.selGridInt)
  19.       {
  20.          m.selGridInt = curSel;
  21.          objMenu = m;
  22.          cursorState = CursorState.Add;
  23.          DeactivateMenus();
  24.       }
  25.    }
  26.  
  27.    //Remove menu
  28.    if (GUILayout.Button("Remove"))
  29.       removedMenus.Add(m);
  30. }

 

Here basically we handle basic information for the menu: name, location. There is also a field called “Folder Name” that specifies the path of the data. After the user writes the name of thr folder down, clicks “Load” and all the prefabs will be loaded automatically inside the menu, using the SpriteRenderer’s texture as icon image. In case that the SpriteRenderer for a prefab is empty, the algorithm automatically searches inside the children of this prefab and takes the first non null texture and use it.

Removing Menus

In the previous code we can see that a “removedMenus” list was created in additio to the general menus list. This is emptied everytime before the cycle for rendering menus starts so if the user removes a menu using the remove button, it will add it to this new list and delete all menus from the main list after the cycle finishes. This is done to prevent collection changes inside the cycle.

Deactivate Menus

Finally, this method verifies if any of the elements on any menu was selected and in case it was not selected it deactivates the whole menu. This was used to have only one element selected at the time, if we do not do this, then two different menus could have one element selected each and we don’t want that when creating new instances.

With the menu deactivation I want to close this post, I’ll continue explaining the rest of the code in upcoming articles. If you have questions, leave in them in the comments.

 

Tile Editor for Unity: Part II

Leave a Reply

Your email address will not be published.

*