Tilemaps In Unity 2017.2 Part 3

Previous posts:

In the first post of this series we took a look at the new Tilemaps in Unity 2017.2 at a high level, without going into too much detail. In the second post we started building a minesweeper game using the new Tilemaps. We created a map to store the board state, then defined two custom Tiles, one for revealed squares and one for unrevealed squares. A lot of the game logic has been implemented, but there are a couple details we need to finish:

  • We need a way to create our custom Tiles
  • While not strictly necessary, we will implement a custom Editor for our custom Tiles. This makes it easier to work with the tiles in the Unity Editor
  • The board needs to respond to mouse/pointer clicks

In this post we will look at the first two items above.

Creating custom tiles in Unity

In Part 2 we created our two custom tiles, RevealedSquare and UnrevealedSquare. Both of these inherit from TileBase and have fields where we will specify the Sprites for each Tile. We now need to create instances of these Tiles so we can assign the appropriate Sprites to them.

However since TileBase are at their core ScriptableObjects, by default we can’t create instances of our new Tiles. The Unity documentation suggests using ScriptableObject.CreateInstance<>() followed by AssetDatabase.CreateAsset() to create instances of our tiles.

But doing that would require implementing our own Editor method, and adding the MenuItem attribute to that method. While that would work, there is an easier way, the CreateAssetMenuAttribute. We can just add this attribute to our 2 Tile classes, then our tiles appear in Unity when we right-click->Create in the Assets folder.

With this attribute our Tile classes now look like:

[CreateAssetMenu(menuName = "Minesweeper/Unreavealed Square")]
public class UnrevealedSquare : TileBase
{
    ...
}

[CreateAssetMenu(menuName = "Minesweeper/Revealed Square")]
public class RevealedSquare : TileBase
{
    ...
}

We can now create instances of our Tiles from Unity

Custom Editor for our Tile

With our tiles created we can now assign Sprites to the Tiles (Part 2 has a link to the tileset I’m using for this). However while the default Editor Unity creates for our Tiles is usable, it isn’t very pretty

Default editor for the Revealed Square

This is because we are using an array to store the Sprites, and Unity doesn’t have any information about how many elements it has. So lets create a custom Editor for the RevealedSquare.

This works just like any other custom editor, there isn’t anything special about Tiles, so we just need to

  • Create a class inside an ‘Editor’ folder and inherit from the Editor class
  • Add the [CustomEditor(typeof(..)] to the class
  • Implement OnInspectorGUI to draw the UI

Here’s the full implementation for reference, we’ll discuss it more below

[CustomEditor(typeof(RevealedSquare))]
public class RevealedSquareEditor : Editor
{
    private RevealedSquare tile { get { return (target as RevealedSquare); } }

    public void OnEnable()
    {
        if (tile.NumberedSprites == null || tile.NumberedSprites.Length != 9)
        {
            tile.NumberedSprites = new Sprite[9];
            EditorUtility.SetDirty(tile);
        }
    }

    public override void OnInspectorGUI()
    {
        float oldLabelWidth = EditorGUIUtility.labelWidth;
        EditorGUIUtility.labelWidth = 210;

        EditorGUI.BeginChangeCheck();
        tile.NumberedSprites[0] = (Sprite)EditorGUILayout.ObjectField("0", tile.NumberedSprites[0], typeof(Sprite), false, null);
        tile.NumberedSprites[1] = (Sprite)EditorGUILayout.ObjectField("1", tile.NumberedSprites[1], typeof(Sprite), false, null);
        tile.NumberedSprites[2] = (Sprite)EditorGUILayout.ObjectField("2", tile.NumberedSprites[2], typeof(Sprite), false, null);
        tile.NumberedSprites[3] = (Sprite)EditorGUILayout.ObjectField("3", tile.NumberedSprites[3], typeof(Sprite), false, null);
        tile.NumberedSprites[4] = (Sprite)EditorGUILayout.ObjectField("4", tile.NumberedSprites[4], typeof(Sprite), false, null);
        tile.NumberedSprites[5] = (Sprite)EditorGUILayout.ObjectField("5", tile.NumberedSprites[5], typeof(Sprite), false, null);
        tile.NumberedSprites[6] = (Sprite)EditorGUILayout.ObjectField("6", tile.NumberedSprites[6], typeof(Sprite), false, null);
        tile.NumberedSprites[7] = (Sprite)EditorGUILayout.ObjectField("7", tile.NumberedSprites[7], typeof(Sprite), false, null);
        tile.NumberedSprites[8] = (Sprite)EditorGUILayout.ObjectField("8", tile.NumberedSprites[8], typeof(Sprite), false, null);
        tile.BombSprite = (Sprite)EditorGUILayout.ObjectField("Bomb", tile.BombSprite, typeof(Sprite), false, null);
        if (EditorGUI.EndChangeCheck())
            EditorUtility.SetDirty(tile);

        EditorGUIUtility.labelWidth = oldLabelWidth;
    }
}

We know our array will always need 9 elements (numbers 0-8 and the bomb sprite), so in OnEnable we will make sure the Sprite array is initialized to size 9.

OnInspectorGUI then just displays an ObjectField for each Tile, setting the type to Sprite and giving appropriate labels for each Sprite. We also set allowSceneObjects to false since this is stored as an Asset and can’t have scene references. We also wrap the entire block in a ChangeCheck and dirty the Asset if anything changed.

That’s it! With that the Inspector in Unity now looks like:

Our new Editor

Final Thoughts

That’s it! Creating instances of our new Tile, then defining a custom Editor for them is pretty straightforward. In the final part of this series we will look at mouse handling with Tilemap, so we can detect which square the user clicks on and respond accordingly.