Custom Editors in Unity3D – Part 8: CustomEditor

So far, we’ve talked about some simple ways to add information and logic to components within the Unity editor, but so far everything has been about tweaking certain fields within the default inspector, but what if you wanted to have full control over how your component draws in the inspector?  Today we’ll be covering CustomEditors, which do just that!

As an example of CustomEditors, we’re going to build a very simple 2D tile-based game, in which the player must navigate from a starting position to a goal position, one tile at a time.  Here’s a screenshot of the end result:

CustomEditorExampleGame

In our game, we have 4 tile types – Full tiles (or walls) which the player cannot walk on (Red), ‘Empty’ tiles that the player can walk on (White), a Player Start tile (Blue), and a Goal tile (Green).  Here’s a snippet from GameBoard.cs showing the data layout:

[gist https://gist.github.com/ryanmeier/b440ace631ae899cc84f]

At the top, outside of GameBoard, we define GameBoardTile, an enum to represent the different types of tiles that can be in our board, and TilePrefab, a simple serializable class that lets us match a GameBoardTile value to a GameObject (to which we’ll attach a prefab) with the tile’s visual representation.

Inside of GameBoard, we have several relevant variables:

playerObject – The GameObject that represents the player (since this game is very simple, GameBoard.cs will also handle input and control the player’s movement, this is generally not a great practice for larger scale project however)

sizeX, sizeY, SizeX, and SizeY – Variables to determine the number of tiles in each dimension of our Game Board.  sizeX and sizeY  are backing variables for the SizeX and SizeY properties, which just allow us to provide some custom logic when they get modified (specifically, regenerating the board’s data representation whenever the board size changes)

tiles – A list of GameBoardTile values that represents the layout of the board (even though it’s stored as a 1 dimensional list, we access it as a two-dimensional array using index(x, y) = x*sizeY + y)

tilePrefabs – A list of objects of the TilePrefab class that we defined earlier, used to specify the visual representation of each tile on the board.

Unfortunately, the base inspector that we get from this isn’t terribly helpful:

TilesBaseInspector

TilePrefabBaseInspector

TilePrefabs we could fix with a PropertyDrawer for the TilePrefab class, but even then, we’d be using the default Unity List editor (of which I’m not a huge fan), and it’d be nice if we could make the list automatically populate with one entry for each possible value for GameBoardTile.  Tiles, on the other hand, in its current state is nearly unusable – we’d have to do math in our head to figure out the (x, y) value of every element, and that changes whenever the size of the board changes, if possible we’d really like this to be laid out as a grid so we can see what the level will look like to some extent as we’re building it.  Let’s write a custom editor to do just that!

First off, we need to make a CustomEditor class for GameBoard.  This is similar to PropertyDrawer, in that the file should be in a folder under ‘Editor’ in our hierarchy, and the class name should be the same as the file name.  Instead of inheriting from PropertyDrawer however, we’re going to inherit from Editor, and instead of the [CustomPropertyDrawer(type)] attribute, we’re going to use the [CustomEditor(type)] attribute:


[CustomEditor(typeof(GameBoard))]
public class GameBoardEditor : Editor
{
 ...
}

Inside of our editor, we’re going to override the OnInspectorGUI() function, and this is where we’re going to put all of our editor drawing code.  First off, we’ll grab the GameBoard object we’re currently editing using the target field (from Editor) and cast it to a GameBoard, and then go through and ensure that tilePrefabs contains an entry for every value of GameBoardTile:

public override void OnInspectorGUI()
{

     GameBoard boardTarget = (GameBoard) target;

     foreach (GameBoardTile tile in Enum.GetValues(typeof (GameBoardTile)))
     {
          bool addTile = !boardTarget.tilePrefabs.Exists(o => o.Key == tile);
          if (addTile)
          {
               boardTarget.tilePrefabs.Add(new TilePrefab(tile, null));
          }
      }
     ...
}

Next, we’ll display the ‘standard’ fields for playerObject, SizeX, SizeY, and tileSize (See the full source of this example or one of my earlier posts in this series for more information on how to do that), followed by our list of TilePrefabs, such that only the GameObject field is editable:

foreach (TilePrefab pair in boardTarget.tilePrefabs)
 {
      pair.Value = EditorGUILayout.ObjectField(pair.Key.ToString(), pair.Value, typeof (GameObject)) as GameObject;
 }

Then, we’ll display a Horizontal ‘menu’ of tile types so that we can select one to use in editing the board, and then display the actual board layout itself:

//Select 'Active' tile type for editing
 EditorGUILayout.BeginHorizontal();
 foreach (GameBoardTile tile in Enum.GetValues(typeof (GameBoardTile)))
 {
      if (GUILayout.Button(tile.ToString(), (tile == selectedTile ? selectedButton : GUI.skin.button)))
      {
           selectedTile = tile;
      }
 }
 EditorGUILayout.EndHorizontal();

 //Display board editor
 EditorGUILayout.BeginHorizontal();
 for(int x = 0; x < boardTarget.SizeX; x++)
 {
      EditorGUILayout.BeginVertical();
      for (int y = 0; y < boardTarget.SizeY; y++)
      {
           if (GUILayout.Button(boardTarget.GetTileValue(x, y).ToString(), GUILayout.Width(50), GUILayout.Height(50)))
           {
                boardTarget.SetTileValue(x, y, selectedTile);
           }
      } 
      EditorGUILayout.EndVertical();
 }
 EditorGUILayout.EndHorizontal();

Note: The second parameter in the GUILayout.Button call of the first list is just to change the appearance of the button for the currently selected type to look ‘pressed’.  This uses GUIStyle which I’m not going to go into in this post, but feel free to look it up or poke around in the full source to see how it works if you’re interested.

To display the board, we use EditorGUILayout’s Vertical and Horizontal groups to make a grid of buttons, each representing one tile in our board, that when clicked will set the value of that tile to the one selected in the list above.  The result of this editor looks like this (I put in a SizeX and SizeY so that the board wasn’t 0x0):

BoardEditor

Hopefully this has provided a pretty solid overview of writing fully customized inspectors for MonoBehaviors in Unity3D.  There are lots of powerful things you can do with these, the specifics of which all depend on the game you’re making.  If you want to check out all the code for these classes and/or see this CustomEditor in action, you can grab the full source for this example here.

Next time, we’ll take a look at the last major topic I have planned for this series: EditorWindows – stand alone custom editors that aren’t attached to a specific Component or GameObject.

Thanks for reading!  If you have any questions, comments, or suggestions about anything in this post or other things you’d like to see covered, feel free to leave them in the comments below!

 

 

2 thoughts on “Custom Editors in Unity3D – Part 8: CustomEditor

  1. This is awesome.

    Just a couple of weeks ago I wanted a grid editor, and, after starting to delve into it, decided that it was going to provide more headache than value. Wish I’d stumbled across this post at that time!

    (Now I just need to figure out what sort of styles and options the EditorGUILayout function calls request…)

Leave a Reply

Your email address will not be published. Required fields are marked *