Tactics RPG Board Generator

In this lesson we will focus on creating one of our pre-production tools. We will create a scene from which we can generate boards to fight on. Along the way we will create an editor script for enhancing the inspector which will allow us to both randomly generate and hand modify the end result. Finally we will use scriptable objects to persist our data.

Before we get started, I want to mention that I have decided to share a repository for this project here. Each commit to the master branch will reflect a “release” (a blog post), so by checking out those commits you can see what the project looked like before or after each week’s tutorial. The completed version from last week, this week, and (if you are impatient) next weeks is already there.

Point Struct

The location of each tile on the board will be represented by a struct holding two int values. There is no data structure in Unity (or native C#) which matches our need, so we need to create one ourselves. We will call it a Point.

Create a sub-folder called Model under the Scripts folder. The use of the word “Model” here refers to an architectural pattern called “MVC” which stands for “Model View Controller” and should not be confused with a “Model” in the sense of a 3D mesh. A “Model” in this pattern is an object which holds data – and that’s pretty much it. Create a new script named Point.cs located within our Model folder. Remove the template code Unity provided for us (the Start and Update methods) – we wont be needing them here.

Just above the declaration of the Point class, add the tag:
[System.Serializable]

so that this data container will be able to display properly in the inspector.

I will be creating a lot of Point instances (while pathfinding, etc.) but I dont want to make a ton of new allocations and add to the burden of garbage collection. Therefore, I will change this from a class to a struct. Change the declaration from this:
public class Point : MonoBehaviour

to this:

public struct Point

If you aren’t familiar with structs and their pro’s, cons’s and gotcha’s, I would recommend you read my post on them here

Our struct contains two int fields. By convention these values will be named x and y, much like you may have seen with using a Vector2. However, in our board the x and y point values will actually be referring to x and z coordinates in 3D space. Hopefully that is not too confusing. Add the following field declarations to our script.

public int x;
public int y;

Rather than having to create instances of our struct and then assign fields one at a time like this…

Point p = new Point();
p.x = 1;
p.y = 2;

…Let’s add a constructor so we can do this instead…

Point p = new Point(1, 2);

Add the following code (our constructor) beneath our field variables

public Point (int x, int y)
{
	this.x = x;
	this.y = y;
}

It would be convenient to be able to work with our Point struct like other value types. For example, add points using the ‘+’ or check equality using ‘==’. This can be done through something called operator overloading. Add the following methods benath our constructor so that we can easily add, subtract, and compare equality or inequality:

public static Point operator +(Point a, Point b)
{
	return new Point(a.x + b.x, a.y + b.y);
}

public static Point operator -(Point p1, Point p2) 
{
	return new Point(p1.x - p2.x, p1.y - p2.y);
}

public static bool operator ==(Point a, Point b)
{
	return a.x == b.x && a.y == b.y;
}

public static bool operator !=(Point a, Point b)
{
	return !(a == b);
}

Because we have implemented the equality operator, it is expected that we will also override Equals and GetHashCode. You can read more on these topics at the links provided below:

As a side bonus of following these guidelines, your code will run faster! The default implementation of Equals works through reflection which is not as efficient as our direct implementation. Add the following methods to our Point struct beneath the operator overloads. Also note that I added the simple version of GetHashCode, but there are faster versions which are better suited for avoiding collisions (read the link I provided above for more).

public override bool Equals (object obj)
{
	if (obj is Point)
	{
		Point p = (Point)obj;
		return x == p.x && y == p.y;
	}
	return false;
}

public bool Equals (Point p)
{
	return x == p.x && y == p.y;
}

public override int GetHashCode ()
{
	return x ^ y;
}

When developing something complex like this particular project, I often find myself liberally using Debug.Log messages all over my code. Rather than having to manually print out the x and y values of our struct, it would be nice to be able to just log the Point and let it determine how to print out its own contents. We can do this by overriding the ToString method as follows:

public override string ToString ()
{
	return string.Format ("({0},{1})", x, y);
}

Tile Script

Not everything fits well into the “MVC” architectural pattern. Sometimes it makes more sense to blur the lines between the model and view. Actually there is another pattern called “MVVM” which stands for Model-View-ViewModel which might feel a bit more aligned with our next script. To be most correct I am just creating a “Component” which is a great architectural pattern all by itself, but because I can have controllers that are components etc, it feels a little confusing to organize by that word.

For the organization of our next script I decided on creating a sub-folder of Scripts called View Model Component but if you dont like that, feel free to rename and organize however you wish.

Create a new script called Tile.cs. In the Project pane, select the Tile prefab and then in the inspector click the Add Component button. Type Tile and select our new script when it appears. Because we have modified a prefab, make sure to save the project.

Open the Tile script so we can begin editing it. I dont want the tiles on the board to be as tall as they are wide. I would like four steps to be the equivalent height of the width of a tile. Of course it’s possible I will change my mind on this height during development, so I will make use of a const which I can modify later if necessary. Add the following to your script:

public const float stepHeight = 0.25f;

I also want the tile to be able to track its own position and height, so add the following fields:

public Point pos;
public int height;

Next I will add a convenience property called center which lets me place other objects in the center of the top of the tile:

public Vector3 center { get { return new Vector3(pos.x, height * stepHeight, pos.y); }}

Anytime the position or height of a tile is modified, I will want it to be able to visually reflect its new values. Add the following method:

void Match ()
{
	transform.localPosition = new Vector3( pos.x, height * stepHeight / 2f, pos.y );
	transform.localScale = new Vector3(1, height * stepHeight, 1);
}

Our board creator will create the boards by randomly growing and or shrinking tiles. Let’s create methods to allow the data to be changed and the view to update at the same time:

public void Grow ()
{
	height++;
	Match();
}

public void Shrink ()
{
	height--;
	Match ();
}

Finally I will add another method called Load but overload it to accept various parameter types. This will make it easy for me to persist the Tile data as a Vector3 later.

public void Load (Point p, int h)
{
	pos = p;
	height = h;
	Match();
}

public void Load (Vector3 v)
{
	Load (new Point((int)v.x, (int)v.z), (int)v.y);
}

LevelData Scriptable Object

The data that represents a gameboard will be saved to a Scriptable Object called LevelData. All I need to store is the position and height of each board tile, so a List of Vector3 would be sufficient to store my data without requiring me to create another data type.

Create a new script called LevelData.cs in the Scripts/Model folder. Because this script is so simple I will just post it in its entirety:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class LevelData : ScriptableObject 
{
	public List<Vector3> tiles;
}

Board Creator

For organization sake, start out by adding a sub-folder to the Scripts folder called PreProduction. This helps me separate things which are used to create the game, but not used in the game itself. Inside the PreProduction folder create a new script called BoardCreator.cs.

I want the board creator to be a visual editor, so I will need to provide it a reference to our Tile prefab. I will also want to be able to hand modify the board one tile at a time. To show the selected Tile, we will provide a reference to our Tile Selection Indicator prefab. Add the following fields to our script:

[SerializeField] GameObject tileViewPrefab;
[SerializeField] GameObject tileSelectionIndicatorPrefab;

I used the tag [SerializeField] to expose these fields in the inspector without them being visible to any other script, although it would have been ok to just leave them as public instead.

The instances of tiles will be created and destroyed by custom buttons we will add to the inspector that cause tiles to grow and shrink. However, there wont be a trigger to create the selection indicator. In order to make sure it exists whenever I need to use it, I will make use of something called Lazy Loading. This pattern is implemented through a getter of our property which checks whether or not the object has been instantiated, and if necessary it will instantiate it. Add the following to our script:

Transform marker
{
	get
	{
		if (_marker == null)
		{
			GameObject instance = Instantiate(tileSelectionIndicatorPrefab) as GameObject;
			_marker = instance.transform;
		}
		return _marker;
	}
}
Transform _marker;

Next I create a Dictionary which maps from a Point struct to a Tile instance. Our board will be non-square and could have holes, etc, so this will make it easy to determine whether or not our board contains a tile based on a specified coordinate position, as well as to grab the reference to it. Add the following line:

Dictionary<Point, Tile> tiles = new Dictionary<Point, Tile>();

In order to use a generic Dictionary we need to include the proper namespace. Add using System.Collections.Generic; at the top of your script. Alternatively, you can right click on Dictionary and then choose Resolve->using System.Collections.Generic; to have MonoDevelop automatically add the dependency.

The next three fields specify the maximum ranges or extents that the board should reach. The width field maps to world space X units. The depth field maps to wold space Z units. The height field maps to step units as defined by the Tile script (smaller than world units). The concept of a step will have an affect on a units ability to traverse the terrain based on their stats (such as jump height). Add the following fields to your script:

[SerializeField] int width = 10;
[SerializeField] int depth = 10;
[SerializeField] int height = 8;

Let’s add another field called pos to represent where individual modifications to the game board would be made (regardless of whether or not a tile exists there yet).

[SerializeField] Point pos;

We will want our script to be able to load previously saved boards so we can edit them again later. In order to facilitate this requirement we will expose a field which accepts a LevelData:

[SerializeField] LevelData levelData;

The quickest way to rough out a level will be through Growing and Shrinking tiles in a random area. The methods will be triggered through an Editor Inspector script which we will write next, but in order for that to work, the methods we want triggered should be public. Add the following methods:

public void GrowArea ()
{
	Rect r = RandomRect();
	GrowRect(r);
}

public void ShrinkArea ()
{
	Rect r = RandomRect();
	ShrinkRect(r);
}

The RandomRect method call generates a Rect struct somewhere within the region we specified by our max extents fields above. Add the following:

Rect RandomRect ()
{
	int x = UnityEngine.Random.Range(0, width);
	int y = UnityEngine.Random.Range(0, depth);
	int w = UnityEngine.Random.Range(1, width - x + 1);
	int h = UnityEngine.Random.Range(1, depth - y + 1);
	return new Rect(x, y, w, h);
}

The GrowRect and ShrinkRect then loop through the range of positions specified by the randomly generated rect area, growing or shrinking a single specified Tile at a time. Add the following methods:

void GrowRect (Rect rect)
{
	for (int y = (int)rect.yMin; y < (int)rect.yMax; ++y)
	{
		for (int x = (int)rect.xMin; x < (int)rect.xMax; ++x)
		{
			Point p = new Point(x, y);
			GrowSingle(p);
		}
	}
}

void ShrinkRect (Rect rect)
{
	for (int y = (int)rect.yMin; y < (int)rect.yMax; ++y)
	{
		for (int x = (int)rect.xMin; x < (int)rect.xMax; ++x)
		{
			Point p = new Point(x, y);
			ShrinkSingle(p);
		}
	}
}

To grow a single tile, I must first get a reference to the Tile from the tiles Dictionary. If the Tile does not exist, I instantiate one from the prefab we provided earlier. Add the following methods:

Tile Create ()
{
	GameObject instance = Instantiate(tileViewPrefab) as GameObject;
	instance.transform.parent = transform;
	return instance.GetComponent<Tile>();
}

Tile GetOrCreate (Point p)
{
	if (tiles.ContainsKey(p))
		return tiles[p];
	
	Tile t = Create();
	t.Load(p, 0);
	tiles.Add(p, t);
	
	return t;
}

void GrowSingle (Point p)
{
	Tile t = GetOrCreate(p);
	if (t.height < height)
		t.Grow();
}

To shrink a single tile, I must check to see if the Tile exists (but not create one if it doesn’t). Whenever shrinking existing tiles to a height less than or equal to zero, the tile is destroyed:

void ShrinkSingle (Point p)
{
	if (!tiles.ContainsKey(p))
		return;
	
	Tile t = tiles[p];
	t.Shrink();
	
	if (t.height <= 0)
	{
		tiles.Remove(p);
		DestroyImmediate(t.gameObject);
	}
}

Occasionally you may wish to hand modify a level a single tile at a time. Add some methods which allow you to Grow or Shrink a single tile based on the pos Point field.

public void Grow ()
{
	GrowSingle(pos);
}

public void Shrink ()
{
	ShrinkSingle(pos);
}

It will be nice to see which tile will be modified, so lets expose a method for updating the position of our Tile Selection Indicator:

public void UpdateMarker ()
{
	Tile t = tiles.ContainsKey(pos) ? tiles[pos] : null;
	marker.localPosition = t != null ? t.center : new Vector3(pos.x, 0, pos.y);
}

If the user wants to clear the board and start over, or before loading a pre-existing LevelData set, we will need a way to quickly clear the board and reset everything. Add a method to Clear our data – it will loop through all of the tiles we have created and destroy them and then clear our Dictionary of references.

public void Clear ()
{
	for (int i = transform.childCount - 1; i >= 0; --i)
		DestroyImmediate(transform.GetChild(i).gameObject);
	tiles.Clear();
}

When the user manages to create a satisfactory level, we will need to provide a way to persist the data. We will be creating our LevelData as a ScriptableObject using the LevelData script we defined earlier. It stores each of the tile’s position and height data in a list of Vector3. Add the following methods:

public void Save ()
{
	string filePath = Application.dataPath + "/Resources/Levels";
	if (!Directory.Exists(filePath))
		CreateSaveDirectory ();
	
	LevelData board = ScriptableObject.CreateInstance<LevelData>();
	board.tiles = new List<Vector3>( tiles.Count );
	foreach (Tile t in tiles.Values)
		board.tiles.Add( new Vector3(t.pos.x, t.height, t.pos.y) );
	
	string fileName = string.Format("Assets/Resources/Levels/{1}.asset", filePath, name);
	AssetDatabase.CreateAsset(board, fileName);
}

void CreateSaveDirectory ()
{
	string filePath = Application.dataPath + "/Resources";
	if (!Directory.Exists(filePath))
		AssetDatabase.CreateFolder("Assets", "Resources");
	filePath += "/Levels";
	if (!Directory.Exists(filePath))
		AssetDatabase.CreateFolder("Assets/Resources", "Levels");
	AssetDatabase.Refresh();
}

The AssetDatabase reference requires using UnityEditor; and the Directory reference requires using System.IO; so make sure to include them.

Finally, should the user wish to restore a LevelData which they had previously saved, they will need to link up the reference in the inspector, and then Load it with the following method:

public void Load ()
{
	Clear();
	if (levelData == null)
		return;

	foreach (Vector3 v in levelData.tiles)
	{
		Tile t = Create();
		t.Load(v);
		tiles.Add(t.pos, t);
	}
}

Inspector Script

Now it’s time to add the custom inspector script which will allow us to create buttons we can use during edit time. Inside the Editor folder create BoardCreatorInspector.cs and open it for editing.

Since we are creating an Editor script we need to make sure and include the correct namespace:

using UnityEditor;

We will also need to tell Unity what type of script our custom inspector is targeting. Add the following line immediately before the class declaration:

[CustomEditor(typeof(BoardCreator))]

Modify the class declaration itself to inherit from Editor:

public class BoardCreatorInspector : Editor 

The selected object is available through a property called target. Let’s wrap that in a property that is cast to the correct type:

public BoardCreator current
{
	get
	{
		return (BoardCreator)target;
	}
}

The method we need to override in order to modify the appearance of the inspector is OnInspectorGUI. To include the default implementation (allow Unity to show all of our serialized fields) we use DrawDefaultInspector. I provide a button for each of the public methods defined in the BoardCreator script to trigger them. Then I check for changes and when a value has changed I make sure the Marker is positioned correctly. Add the following:

public override void OnInspectorGUI ()
{
	DrawDefaultInspector();

	if (GUILayout.Button("Clear"))
		current.Clear();
	if (GUILayout.Button("Grow"))
		current.Grow();
	if (GUILayout.Button("Shrink"))
		current.Shrink();
	if (GUILayout.Button("Grow Area"))
		current.GrowArea();
	if (GUILayout.Button("Shrink Area"))
		current.ShrinkArea();
	if (GUILayout.Button("Save"))
		current.Save();
	if (GUILayout.Button("Load"))
		current.Load();

	if (GUI.changed)
		current.UpdateMarker ();
}

Using The Board Creator

Create a new scene called BoardCreator. Add an Empty GameObject by the same name, and attach our script. Connect the relevant prefabs from the Project pane to the fields on our script in the Hierarchy pane. Save the scene and project.

Note that you don’t need to press play in order to use this tool. You can simply start pressing some buttons and see the results in the scene view.

Try clicking the Grow Area button a few times to get a basic mound of tiles. Then change the pos coordinates of the selected tile by dragging left and right on the x or y fields and notice how the indicator automatically updates and appears at the correct place. Now try Growing or Shrinking a single tile. When you are happy, Save your board. You will see a Scriptable Object (your LevelData) saved as Resources/Levels/Board Creator – it picked the name based on the name of the GameObject the script was attached to. Keep that in mind so that you don’t accidentally overwrite other files.

Clear your board and then assign the LevelData to the exposed field in the inspector. Then reload the board and see that you can continue editing from where you left off!

Summary

This was a HUGE lesson, and may have been better broken up into a few parts, but I wanted to make sure you had something new to look at by the end. Over the course of this post we learned about making custom classes or structs Serializable, operator overloading, guidelines on overriding Equals and GetHashCode, and Lazy Loading. We created a custom Scriptable Object which we used for persisting our level data. We also created a custom inspector script to provide editor time functionality and access to trigger our public methods. I used the Directory class to determine whether or not proper folder hierarchy had been set up, and AssetDatabase to create folders when necessary. The end result of all our hard work is a nice little re-usable tool to visually create the levels of our Tactics RPG game.

151 thoughts on “Tactics RPG Board Generator

  1. Great tutorial, and I especially applaud you for doing a tactical RPG since I’ve been interested in looking at such an implementation for a while but nobody else is doing such a tutorial and I haven’t had the spare time to work through it myself.

    However, I noticed an issue with the RandomRect() method while playing around with it. It doesn’t actually reach the bounds of the area specified by the Width and Depth variables, but is off by 1 since the maximum value passed into Random.Range is exclusive. The code below seems to properly reach the full specified area:

    int x = UnityEngine.Random.Range(0, width);
    int y = UnityEngine.Random.Range(0, depth);
    int w = UnityEngine.Random.Range(1, width – x + 1);
    int h = UnityEngine.Random.Range(1, depth – y + 1);
    return new Rect(x, y, w, h);

    1. Great catch Blake! You are right, and you also caught a problem with me referencing height instead of depth when assigning to y. I’ll update the code to reflect your version. I’m also glad you’re enjoying the series so far!

  2. I finally got the time to follow (and not only read) this tuto, and I admit that the result looks insanely great ! πŸ™‚

  3. Excellent!!! Muchas gracias por este tutorial. No sabes cuando me sirve. Es posible que el valor de x e y de la posiciΓ³n se incremente y decremente utilizando las flechas de desplazamiento en el teclado?

    1. Hey Gustavo, sorry but I don’t know your language. Are you asking if you can move the cursor around using keyboard events instead of by using the inspector? I do this during play mode on the PathFinding tutorial which you might want to check out. It should also be possible to do at Edit time, although you will have to use different code. I found this link which has an example (though I have not tested it myself…) http://answers.unity3d.com/questions/130898/how-to-check-if-a-key-is-down-in-editor-script.html

      Hope that helps!

      1. Actually he’s asking if it’s possible to change the x and y value of the tile selector with the move arrow on the keyboard. Like pressing “up” arrow will increment x when the field has the focus. But what you’re suggesting John is far better πŸ™‚

    1. I’m not sure I understand what you are asking because isometric can still be done with 2D or 3D. Are you imagining something like a top down view of a board like chess? Either way a lot of the code would be reusable – the biggest effort would be in the code for the “View” portion, as in switching to 2D sprites instead of 3D models, making sure that the ground is correctly sorted and layered to block units which are behind them etc. If the ground were completely flat or from a top down view it wouldn’t be much effort at all.

  4. I have been playing around with the board creator by generating a random board and then altering individual tile textures and adding objects like trees etc. As our level data saving is done programmatically, I have been trying to alter the “Save” method to account for changes that I make. I cant quite seem to figure out how to code for this however. Any suggestions?

    1. You might consider making another asset such as a β€œBoardSkin” for the textures. When saving the board, it would loop through all the renderers on all the board tiles and get the material name(s) that had been applied. You could save it as a dictionary where the material name was the dictionary key and a list of point positions was the dictionary value and helped determine what tiles received that material. As long as the material was in a Resources directory it would be an easy matter to load it based on that string name in the Dictionary key.

      Likewise you could make another asset such as a β€œBoardDecoration” for all the props like trees etc. If the props are directly tied to a tile, then you would do something like loop through the tiles and see if they have children objects. If so, those children would be the β€œcontent” and would use the same point position as the tile it was parented to.

      By keeping each of these steps as separate assets, you could easily experiment with how you want to save the data, as well as easily change just certain parts of a level so you could try a few variations and see which one you like best.

      If you are struggling with saving data, you may also want to check out my post on the topic for some extra practice.

        1. Did you (or anyone) have any success with this? I know this is a really complex project to learn on but I have infinite time and a lot of patience πŸ™‚ Directions on how to change the saving portion of BoardCreator to account for texture would be amazing, even just as an example.

          Since 1 unity unit seems to happily accommodate a 32×32 sprite, would it work to make all the tiles transparent and put a tilemap underneath (given a 2d oriented camera perspective)?

          1. No one has posted any completed samples, though several have worked through the basic idea with me on my forums. Feel free to follow along there or post a new thread with your own attempts and I’ll be happy to help you along the way as well as I can. I haven’t spent any time with Unity’s new tilemaps yet (but I definitely need to). If you wanted to try it though, don’t bother making “transparent” tiles – you could simply not include a mesh or renderer at all.

        1. An asset would be something that is saved to the project itself via the “AssetDatabase” class. In this post I used a script to create an “asset” which held a description of a board layout. The asset itself was a serialized form of a “LevelData” object which inherited from a “ScriptableObject” base class. You can store just about anything you want such as references to textures, 3d models, etc in these kinds of objects.

          1. So would it be possible to simply change the list in LevelData to be a list of both Materials and Point locations so when loading it will check the location, instantiate it, then apply the material needed all in one step?

          2. Yes – that would certainly be possible, but you would also probably need to refactor a little because I scaled the tiles for height and that will cause materials to squish or stretch. You could also just reference a prefab tile that already is modeled with materials pre-applied or perhaps could do a naming convention and have prefab tiles at various pre-made heights. Probably the best solution may involve a pre-created level model so the entire thing could be a single mesh. There would still be a variety of ways to show selection tiles over the mesh, and some experimentation may be in order.

  5. I’m fairly certain that you want to be implementing the IEquatable interface here to get the speed optimization on the struct when its used within generic containers, i.e. List. You implemented the Equals function (Equals(Point p)) but you didn’t implement the interface (Point : IEquatable), so the containers won’t be able to benefit from your implementation meaning they will end up using the Equals(object obj) version of Equals.

    1. Great catch, thanks for pointing it out! I ran a test to see which method was called and you are correct that it was using the object version. Im surprised the MSDN docs didn’t mention it (or if it did I guess I just missed it).

      Anyone following along can use:
      public struct Point : IEquatable
      (Note that there is supposed to be a Point type after IEquatable but for some reason wordpress keeps stripping it out even when I add code tags. Oh well – future pushes to my repository will include the corrected script.

      and be sure to add
      using System;

  6. This is probably beyond the scope of this particular lesson, but how would you make it so the user can click on a tile to select it?

    I know you’d use a raycast, but I’m not very familiar with using events with input so I’m still trying to wrap my head around how it would be tied together.

    1. There are several ways to do what you want. As you guessed, you can use raycast, and Unity’s docs provides several examples of this including getting a ray from a screen point http://docs.unity3d.com/ScriptReference/Physics.Raycast.html

      However, the raycast examples are based on the old input event system. I would recommend spending some time with Unity’s new UI and event system. You can make anything act like a button by adding that kind of component. So for example you could add the button component to a tile and then register methods for its click event. Note that you must have an EventSystem created in your scene (creating a canvas will make one automatically), and you must also add a Physics Raycaster component to your camera so your 3D content can be detected by the event system.

      1. Wow, I actually never thought of adding the UI button component to the tiles.

        I’ve looked into this and I’m having a bit of trouble wrapping my head around it. I’m a bit new to Unity, so please pardon me if this question is a bit basic. Here is my setup:

        – Each tile has a UI button component with an OnClick() event.
        – The camera has a Physics Raycaster and there is an EventSystem in the scene.

        The actual trouble I am having is writing the function called by the OnClick() event. For the board generator to update the currently selected tile, it needs to know the Point of that tile (correct me if wrong). Since the Tile shouldn’t be aware of the tile selection indicator, I figured the function called by the OnClick() event should be in BoardGenerator.cs

        How would I pass in the Point of the selected tile to this function, or would you do this a different way?

        1. For a better idea of the solution I would use, read my post here:
          Sorted Shop

          Pay particular attention to the ItemCell script. The “OnBuyButton” is a method which could be attached to the Button component via the inspector, and everything can be self-contained as an object inside of the prefab. Within the implementation of that event handler I post a notification.

          Other scripts can register to listen to the notification without needing to get a reference to any particular instance of the tile. In addition the notification will pass along the object that triggered the event so you will be able to determine the specifics which are relevant at the time (for example the tile’s point coordinates).

          I would not put the notification handler logic in the BoardGenerator script, but would put it in Board State scripts and allow them to register and unregister as the state enters and exits. This way a board tap only has any effect where you actually want it to.

  7. I was wondering how difficult it would be to actually create a script that generate a board from a Terrain object while assigning a Hexagon grid on it, or even simpler ill create the boards myself yet I’m wondering how hard it is to implement a hex grid on that terrain that will function as a movement grid that can detect certain heights are invalid grids or obstacles, ofcourse that grid will function as a navigation grid aswell.

    1. If you “model” the board in code it won’t be any more difficult for a hex grid to determine traversal heights than for the square grid I set up here. The “view” for the “model” (your terrain) doesn’t need to have an effect on that part of the logic. The harder parts in my opinion would be that you will need to be able to handle how to highlight “tiles” over the terrain, move your selection cursor, etc which might be slightly trickier to keep looking correct and nice if your terrain has much variation in it.

      If it were me, I would create the board using pre-made hex tile shapes which had highlighted and non-highlighted materials already set up. Ideally they would be able to be kit bashed together so that they pretty much always look good no matter which tiles you place next to each other. You would still have hard-edges but if you design it right it could look pretty nice anyway.

  8. wouldent it be super heavy to create a Openworld or a sandbox out of hexagonal shapes? loading that amount of prefabs could take awhile, ill try and figure a way of doing that, maybe just generate the hexagonal shapes on the terrain itself and making sure that once a hex is cut when he reached the edge of the Terrain he is assigned as the Edge hex and set his Viewablity to hidden or something like that. what do you think?

    1. I am keeping an eye out for ideas for my next project. I admit the temptation to do a sandbox game like Minecraft is there but so far I am completely stumped on how to do it (not to mention do it well) in Unity. My experience with Unity is that it really struggles with Memory issues – the heap continues to grow until my apps crash even when I destroy unused objects and tell Resources to unload. It’s great for limited scope levels, but I haven’t yet found a good way to continually load and unload content.

      Even just trying to store the amount of data that must be necessary for that large of a world blows my mind. I’m a self taught programmer so there is probably a lot about data-base programming that I need to discover before that kind of game is within my grasp.

      In the meantime I got a request to do card based games like hearthstone. I haven’t done any multiplayer games with Unity yet so that one appeals to me too. Anyway, good luck with school, and keep in touch πŸ™‚

  9. I can highly recommend pulling some of the new and free unity assests out there, ill link some here.
    all of them are availble to download on the asset store and for free ofcourse.

    https://www.youtube.com/watch?v=pXWAsayTFTo – The Blacksmite (Animation + Camera)
    https://www.youtube.com/watch?v=iV-224nMwN8 – Viking Village (Open World)
    https://www.assetstore.unity3d.com/en/#!/content/33835 – Unity Labs (Shaders)

    “https://www.assetstore.unity3d.com/en/#!/search/page=1/sortby=relevance/query=price:0-0” – Search link.

    https://www.assetstore.unity3d.com/en/#!/content/32351 – Unity free sample Controllers, Prefabas, Animations and a whole of amazing things.

  10. well I can highly recommend for everyone to have look on those free assets released by Unity for version 5 and above, some very amazing and unique ways to wield the powers of this software.

    https://www.assetstore.unity3d.com/en/#!/search/page=1/sortby=relevance/query=price:0-0

    https://www.assetstore.unity3d.com/en/#!/content/46209 – Multiplayer game, pretty amazing.

    https://www.youtube.com/watch?v=G-zLx5JVMVE – The Blacksmith

    https://www.youtube.com/watch?v=iV-224nMwN8 – Viking Village (Oopen world)

  11. Yep, I’ve seen that and it is really impressive. I haven’t downloaded the asset personally, but I would bet that a lot of it is marked as static. By this I mean you wouldn’t be able to destroy the content or build something new like you could with Minecraft. You probably couldn’t walk to another village while loading and unloading prefabs, etc either. Have you downloaded it?

    On the other hand, Unity has been making a lot of steps toward loading and unloading scenes additively (kind of like a prefab) and that could be the solution to the problem that allows large open worlds at this level of quality. I am curious now.

  12. Transform marker
    {
    get
    {
    if (_marker == null)
    {
    GameObject instance = Instantiate(tileSelectionIndicatorPrefab) as GameObject;
    _marker = instance.transform;
    }
    return _marker;
    }
    }

    I noticed if you save the scene with the tile selection indicator present, then load the scene again and do something, a new tile selection indicator is spawned. I presume this is because _marker is no longer set once you load another scene then come back to this one.

    Is there a way to make it so a duplicate marker is not created if one already exists?

    1. Yep, there are several ways. For example, you could provide a unique tag or name for the object and then in the getter just before it creates a new one, you could use a ‘GameObject.Find’ or ‘FindWithTag’ to check for pre-existing instances.

  13. Probably simple and dumb question but how do I change the texture on each tile for different levels? Every time I change the material on a tile it just reverts back to Dirt even after saving the level

    1. It’s not a dumb question. The reason the tile loads with the Dirt material is because when we load a level we are instantiating a prefab which had that material applied. Modifying the instances of the tile in the scene are not modifying the original prefab asset unless you apply the changes through the editor. If you instantiate a different prefab, or modify the prefab, you can see a different texture applied to the whole level.

      If you want different textures per tile, look above to the advice I gave Michael.

  14. This may be a dumb error, but I can’t get around it. I’m getting “NullReferenceException: Object reference not set to an instance of an object.” from the Tile GetOrCreate(Point p) function on the line t.Load(p, 0);

    This happens when I press the Grow or Grow Area buttons in the inspector. Please help.

        1. It’s hard to say for sure. I would put a Debug Log on the line just before the “t.Load(p, 0);” and print whether or not the “t” is null. I don’t see how anything in the Load method could be an issue, but I also don’t see how the tile would be null if you have connected the prefab as you say.

          If the “t” isn’t null keep checking each other object one at a time until you figure out “what” is null and then it will be easier to help you.

  15. Hi!

    This is a great tutorial and I’ve learned a lot just by doing this section.
    I’m having some problems though. I noticed that sometimes shrinking a single tile doesnt work, and my deduction its because its a tile grown from the “Grow Area” method. Another one is that I couldnt load a previously saved level data (dragging and dropping to the slot does not work, nor selecting manually from the menu).

    Thanks in advance!

    1. I’m glad you are enjoying it. You should be able to shrink or grow any tile position using the inspector script, regardless of if you have used the “Grow Area” method or not. It may not be the answer you are hoping for, but I would re-read the lesson and very carefully compare your setup against my own. If you have followed everything exactly (copy and paste if necessary) it will work.

      Regarding the loading of a level, make sure you drag the level data onto the slot and then click the “Load” button in the inspector. If you have done both of those and it is still not working, you will need to compare your own work against my own.

      Unfortunately I can’t give better help because the problems and questions aren’t specific enough. Good luck!

      1. Thanks for the reply

        I looked things up and retraced my code. Apparently Unity needs a file to be a “.asset” file for it to be loaded in most places, and the levelData save file my code generated didn’t have that extension. I added an extra string at the filename generator and it now works fine.

        As for tile selection bug, sometimes the tile selector doesnt want to scale up tiles (it goes under them and disappear), but clearing the board and reloading the levelData fixed this. I’m still not sure why but this is fine I think.

  16. I really love the tutorial! I know it’s been a while but I’m just now getting into coding.

    I like to try and tinker to figure things out a little more as I follow tutorials as well as make it more my own.

    I know you were talking about not having square maps having holes in them and such but from what I saw the empty spaces aren’t used at all. For instance if I had a map with a bridge or something that was walk movement can only make it through the bridge. I want the flying movement or teleport movement to be able to jump on to another part of the (let’s say castle) by going past the empty space avoiding the bridge.

    I’m doing so by implementing a map size setting and a bottom tile (which has no height) and is a subclass of the tile which is the same as it’s base just with a height set 0 and tied to different prefab (also thinking about doing something similar to vary the tiles).

    Anyway I have the board controller also having properties and a button to set the map size based on an point and build it out (in terms of row and column for the point). The battle state checks fist if there is not a bottom tile (returns), if not a tile (returns the position of the bottom tile), and finally sets the position to p. Now thinking about it I may not want bottom tile to be a subclass of tile. Since I could easily handle different tile types if they are sub classes of the tile.

    I eventually want to make the board creator both usable in game and out of game with either mouse and buttons or menus and keys.

    Helps me think a bit if I type it out. Forgive me if you’ve already done something akin to this later on I just took a bit of time to experiment. I’ll continue on now. Once I learn more about the UI I’ll come back to finish the in game map creation.

      1. It’s been fun. Haha after some sleep I decided to scrap what I was doing an implement it differently. I modified the tiles to be vector4 instead of vector3 so it’s last int is a material selection. Then with the board creator I have a material selection but when it’s creating it’ll set the material to a void black for me. This way I can still have odd shaped maps but still allow flying to take full advantage

        As well as setting a base height and the match portion to add the base height too. This will let me use most of the same code but the base tiles which flying people can use have a height .001 and the next tile that walk units could use would be .252. This way I can just filter or expand search as needed. Now I can continue on to the UI lessons and on lol πŸ˜‰

  17. Hi, I know you have moved on from this project but I was hoping you might take a few minutes to help with an issue I am having. I tried implementing your suggestions on altering level aesthetics but couldn’t make too much sense of your implementation. Thus I am simply trying to add some properties to the LevelData class so that I can save all needed aspects of my levels such as, which tiles should have items on them or have different materials, trees, enemies etc. Initially this seems to work out fine, but sometimes I get a null reference issue as if the property just doesn’t exist. I made a short video illustrating the issue.

    Video below:

    1. Hey Michael, it looks like you are trying to save a “Dictionary” as part of the level, but Dictionaries are not something Unity knows how to serialize. It would work as you saw while it could remain in memory, but once you changed scenes it was gone and was unable to be recreated. There are a bunch of people out there who have created their own versions of dictionaries that can be serialized, but usually they just end up with a paired set of lists. Also, you can move a material reference to the tile itself and save it there. Hope that helps!

      1. Thank you very much. I was not familiar with serialization and dictionaries in C#. After reading up, the simplest fix for me was to use the paired set of lists as you suggested. I am still curious how dictionaries are actually used however in Unity. It seems at first glance that another collection is always a better choice? Do people just create temp dictionaries out of keyValuePaired lists or something? Thanks again!

        1. It sounds like you’ve got the right idea already. The main purpose of the Dictionary is for faster lookup times. If you are simply using it for loading a level like this, then you may never see any benefit for converting back to a dictionary.

          Unity doesn’t work with Dictionaries any differently than anything else, so I can’t say for sure why they don’t support serialization for them – it could be as simple as not having a convenient way to show an inspector for it, but I’m not sure.

          1. Any chance of showing how you got the paired list set up in the end? Have been trying to get it to work so that I can add a tree to a tile from the editor window, but can not get my head around it. Should I replace the dictionary with a list or something? Afraid I dont understand how to pair it. (Not a programmer really, just self learning as I find interesting things. This is a great tutorial series so far!)

          2. When people say that they want to use a paired list instead of a dictionary then they are using a list to represent the “key” and a list to represent the “value”. For example if you used a “string” as the key and an “int” as a value then you could use two lists like so:
            public List string keys; // There should be generic brackets around string but they get stripped out in comments
            public List int values; // There should be generic brackets around int but they get stripped out in comments
            Then you can iterate over the keys of your dictionary, add them to the list of keys, and add the corresponding value for the key to the list of values. Usually this is just a temporary serialization step – you wouldn’t want to work with it like this because you don’t want to risk them getting out of order, etc.

  18. Awesome tutorial! You definitely need a donate button (like paypal) on your blog! Your next beer would be on me then… πŸ˜‰

  19. Hello,

    Love the tutorial.

    For anyone else out there, I was having an issue where my indicator wasn’t lining up with my grows and shrinks. It’s because my BoardCreator object wasn’t set at (0,0,0). Click the board creator in the hierachy window and edit those values in the inspector. I know this is noob, but I spent a good hour figuring out what’s going on. Hope this helps someone.

  20. Hey sorry to bother you on this project again but im like a bit confused… I did everything till now exactly how you said and I can’t create a board still… the buttons for doing so wont appear anywhere on my screen… ive restarted the project 3 times already and done the same and its still not showing up… So i tought you could give a tip on what i might be doing? thank you loving the tutorial so far ^^

    1. Hey, no worries, I am always glad to help where I can. Still, there are a ton of places where something “could” go wrong so its hard to say for sure. The very best thing you can do is to download the demo project and compare your project against it to see if you can find what step might have been missed or misunderstood.

      Otherwise, if I understand correctly and the problem is that the inspector buttons for the “board creator” are not appearing, then your problem probably has to do with its “inspector script” – go back and re-read that section in case you missed something. There are a few gotcha’s which I mention such as making sure to stick this script in an “Editor” folder – the naming must be exactly that or it won’t work. Copy and paste the code snippets if necessary to confirm you haven’t missed anything or had any typos etc.

      1. ^^” nevermind it I kept doing the same mistake over and over… I was placing “[CustomEditor(typeof(BoardCreator))]” inside the class instead of before the class declaration… well RIP I guess it’s working and thank you for you answer keep up the good work ^^ sorry for the bother

  21. Hello! I am running into a snag during the final part, where we attach all the board creator script to an empty game object and this should allow us to start using the scripts to build a board, I am attaching Tile and Tile selection indicator but cant seem to figure out where BoardCreationInspector figures into all this, and thus cant seem to get it to work.

    1. There’s a lot to point out here, so your best bet is to download the project from the repository and take a look for yourself.

      In the meantime I will point out that you don’t attach “inspector” scripts to anything. Make sure they are saved in the “Editor” folder and they will work auto-magically with whatever component they are specified as the “CustomEditor” for.

      What that means is that you would actually attach the “BoardCreator” script to the game object and the “BoardCreatorInspector” will just magically make the window in Unity’s Inspector pane look special. I hope that helps!

  22. Awesome tutorial! This is a tutorial I will follow from start to end, to learn πŸ™‚

    For newcommers, here is two tips for the editor:
    1. If you want to get the Tile you click, first lock the Inspector Window with BoardCreator, then this code will solve it for you:
    if (ActiveEditorTracker.sharedTracker.isLocked)
    {
    var test = Selection.activeGameObject.GetComponent();
    if (test != null)
    {
    current.pos = test.Position;
    current.UpdateMarker();
    }
    }

    2. If you want to be able to navigate with your arrows, press the Inspector Window with BoardCreator, then this code will solve that problem:
    Event e = Event.current;
    switch (e.type)
    {
    case EventType.keyDown:
    {
    switch (Event.current.keyCode)
    {
    case KeyCode.LeftArrow:
    current.pos = current.pos – new Point(1, 0);
    break;
    case KeyCode.RightArrow:
    current.pos = current.pos + new Point(1, 0);
    break;
    case KeyCode.UpArrow:
    current.pos = current.pos + new Point(0, 1);
    break;
    case KeyCode.DownArrow:
    current.pos = current.pos – new Point(0, 1);
    break;
    }
    current.UpdateMarker();
    break;
    }
    }

  23. Hello I have been working on your tutorial up to the pathfinding page so far. I am really enjoying it. I was wondering though if you ever worry about the size of levels becoming to unweildy for unity. Do you have any idea how many tiles would become to much for unity to handle. I tried handling 50×100 but that seemed like it didnt work too well. Do you have any thoughts on the matter?
    Thanks,
    Ninnec

    1. If you keep the project designed as I have it then yes you would definitely run into problems with large levels. GameObjects can be very expensive, and having a new GameObject plus collider, mesh component and mesh, etc times 5000 tiles makes a huge impact on performance. If you want something that large then you will need to explore ways to pre-create your level meshes. Perhaps you could model the entire thing in Maya or Blender etc and import it as a single mesh or at least as a collection of chunks of it. You can also programmatically create the tiles in chunks if art programs aren’t an option.

  24. Hi! I just started trying out this project too. Huge fan of FFT. Would really like to get through this from top to bottom. Sadly I’m stuck at the first script(Point.cs) already. I’m not sure if I missed a step or did something wrong on my script. I’ve been trying out some suggested fix by visual studio but I still can’t get rid of the errors. I’m using Unity 2017.1.1f1. Was hoping if I could ask for a new updated Point.cs script that would work on the latest Unity. Then I’ll use it as a guideline to continue with the lesson. πŸ™‚

    1. To my knowledge, there is nothing in the “Point.cs” script that should need updating. I have run the project successfully all the way up to Unity version 2017.2.0f3, of course I am using a mac and MonoDevelop so it is possible your environment with visual studio is configured differently enough to cause an issue. Perhaps if you share the errors someone can help.

  25. Oh, is it possible that the errors appear because I haven’t started working on the other scripts yet? Because currently, the error that I get is coming from this one:

    public Point (int x, int y)
    {
    this.x = x;
    this.y = y;
    }

    Saying that ‘method must have a return type.’

    1. I’m not sure what your skill level is with programming, so forgive me if I say something too obvious. The method here is a special kind of method called a constructor. Its signature doesn’t look like other methods and is why you don’t see a return type provided. In order for that code to be able to compile, it must appear inside of a class or struct definition by the same name. Keep in mind that C# is also case sensitive. I have a series of simple posts to help teach C# if you need any refreshers, http://theliquidfire.com/tutorials/

      1. I see. I must’ve separated it from a supposed class that it’s supposed to be in. Will try to work it out. Thanks very much. Appreciate the help.

  26. Really appreciate this kind of tutorial. I’m learning a lot!
    However, I’m having trouble with this —
    (after copying all your script and steps, and also trying the same with your finished project, this doesn’t seem to work for me. the indicator stays at 0,0.

    “Try clicking the Grow Area button a few times to get a basic mound of tiles. Then change the pos coordinates of the selected tile by dragging left and right on the x or y fields and notice how the indicator automatically updates and appears at the correct place.”

    1. Are you saying that you get the same problem in the finished project as you do in your own project? The sample project definitely works, it has for me and many others so I am not sure why you would see an issue there. Usually when people are experiencing a problem like this it comes down to the way that project assets are referenced. References to prefabs are talking about the asset in the project pane. References to instances are instantiated objects that appear in the scene. Make sure that the board creator holds a reference to the tile marker “prefab” and that it is able to successfully create an “instance” of it in the “marker” lazy loaded property. You could also place break points or Debug Log messages in the “UpdateMarker” method to verify that you are finding tiles at the current position to move the tile marker to. Finally, make sure you have the Board Creator’s inspector script in an “Editor” folder so that it will use our custom “OnInspectorGUI” to make changes. You could also place a break point or Debug Log there to make sure it is being called. Good luck!

  27. Hey there! Truly awesome job on this! I appreciate your thoughtfulness on this. Personally, I struggle with keeping a project organized as I go, and it’s good to see. how much thought you’ve put into organization.

  28. When I hit/spam grow area my project only grows at (1,1) and no where else. I downloaded the completed project and it works fine. After spending much too long analyzing the scripts and comparing them, they look identical, but I still can’t grow outside of (1,1). It will pile up nice and tall, but that is it.

    Is there perhaps something I could be missing in the Editor itself that might be causing this?

    1. I cant think of any Editor problem that would cause this. It is more likely a code issue. Here are a few suggestions to try out:
      * Check the “BoardCreatorInspector” script and verify that the “GrowArea” button is invoking the “GrowArea” method rather than the “Grow” method.
      * Use the debugger – place breakpoints and step through the code verifying expectations along the way, or at least print DebugLog messages to confirm expectations. For example, you might want to print the rect which is returned by invoking the “RandomRect” method inside of the “GrowArea” method to see if it looks correct. If it looks good so far, then check the internals of the “GrowRect” method. Make sure it was passed the same rect that was randomly generated. Make sure your outer loop and inner loop iterate over the correct range, and perhaps log the point before the call to GrowSingle inside the innermost loop because it should be a changing value. Just keep following this idea into “GrowSingle” then “GetOrCreate” etc until you spot the problem. Hope that helps!

      1. I’ve always found it amusing how dedicated you are to replying to spammers, and comments in general too of course. Great tutorials btw, I just finished up the Ability Menu section, so I’m tempted to start tweaking the tile code while I have everything in a working state again. I’m just really glad I can compare my code to the repo.

  29. Thanks for any other informative site. Where else may just I
    am getting that kind of information written in such a perfect means?
    I have a undertaking that I’m just now operating on, and I’ve been on the look out for such
    information.

  30. I’m having an issue with an error when I place and save all the scripts from this tutorial.

    I get all of the following in the Unity compiler:

    Assets/Scripts/Animation/EasingControl.cs(8,28): error CS0066: `EasingControl.updateEvent’: event must be of a delegate type

    Assets/Scripts/Animation/EasingControl.cs(9,28): error CS0066: `EasingControl.stateChangeEvent’: event must be of a delegate type

    Assets/Scripts/Animation/EasingControl.cs(10,28): error CS0066: `EasingControl.completedEvent’: event must be of a delegate type

    Assets/Scripts/Animation/EasingControl.cs(11,28): error CS0066: `EasingControl.loopedEvent’: event must be of a delegate type

    Assets/Editor/BoardCreatorInspector.cs(9,12): error CS0246: The type or namespace name `BoardCreator’ could not be found. Are you missing an assembly reference?

    I’m fairly certain I followed all the directions down to the letter. Am I missing something? I’m familiar with C# and delegates, but everything in the script looks correct. It seems the delegates are subscribed in another script, and these errors imply that the event objects are not properly “typed” to be used as delegates. I’ve also had a long day today and I’m tired so I’m not thinking completely straight, lol. What am I missing?

    1. I’m guessing that you haven’t changed anything in the Animation classes, so if I am correct then we can probably ignore those as misleading. The last error though about the BoardCreator not being found sounds like a good lead. Make sure that you don’t have any issues with typos in the class name, and also make sure that BoardCreator exists in the normal Scripts directory, not in the Editor folder along with the BoardCreatorInspector script. If that doesn’t help, try temporarily removing the BoardCreatorInspector script and see if the project will compile.

      1. “I’m guessing that you haven’t changed anything in the Animation classes, so if I am correct then we can probably ignore those as misleading.”

        Correct sir. So far, the animation classes etc have not been touched. They were taken as-is from the source provided in the tutorial.

        “The last error though about the BoardCreator not being found sounds like a good lead. Make sure that you don’t have any issues with typos in the class name, …”

        No typos to be had – triple checked. Though the IDE (I use Visual Studio) does tend to complain about a naming convention where it would seem to prefer the event names to be all uppers rather than camel case, e.g. UpdateEvent versus updateEvent. I forgot to mention this part, but then again, when I “corrected” this issue it made absolutely no difference whatsoever.

        “… and also make sure that BoardCreator exists in the normal Scripts directory, not in the Editor folder along with the BoardCreatorInspector script.”

        It was and continues to be as you say, wise one. πŸ™‚ Still no positive effect.

        “If that doesn’t help, try temporarily removing the BoardCreatorInspector script and see if the project will compile.”

        This, I have not thought to try. I will attempt it and post another reply with my results.

        And let me just say, thank you most sincerely for responding with such speed. I didn’t expect answers so quickly, but this is very reassuring! πŸ˜€

        1. So, I removed BoardCreatorInspector as advised and it did not correct the problem.

          Further research seems to suggest that the EventHandler, at some point, needs be declared as a delegate type… example,

          public delegate void EventHandler(object o, EventArgs args);

          …or something similar. Just to mess around I declared this line:

          public delegate void EventHandler(); //…with no args…

          …just to see what it would do, and while it removed the previous errors 11 others popped up complaining about, you guessed it, improper number of arguments for the delegate (none take 0 args).

          So I hit Ctrl+F and searched the entire solution for the keyword “delegate” and it appears nowhere, in any of the files. Where then, is the custom EventHandler being typed as a delegate? The class is overridden and derives from MonoBehaviour but I don’t see any case anywhere about it being used as a delegate, as though the events being declared in my original comment have an implicit cast of some sort as delegates.

          In the meantime while I wait for another reply, I’ll continue to tinker. Thanks for all you do! πŸ˜€ (And in case I’d not made it readily apparent, while I understand C# and delegates, it is not my forte… up until Unity I’d been making modifications to different engines, like Irrlicht and Torque, using C++ instead. Lol.)

          1. EventHandler is already defined as a delegate – it is provided for us in the System namespace. There is both a normal and generic version.

            By adding the line here, you have re-defined your own delegate of the same name, but with a different signature. The system version takes as parameters an object sender and an EventArgs, whereas your version takes no parameters. Since for some reason your compiler is failing to see that EventHandler is defined as a delegate, it may not have seen it defined at all and so it would make sense why defining your own would help fix some of the errors, but cause others since the observers of the event expect it to have a different set of values.

            Unfortunately I dont have much experience with Visual Studio. I have been using MonoDevelop all along and it worked fine for me there. Now that Unity comes bundled with it instead of MonoDevelop I am sure I will use it, but I dont know what kinds of various configurations or options you may have specified in your own version that differ from the defaults that come bundled with Unity (note, it builds successfully in a fresh install of Unity’s bundled Visual Studio Community).

            When you said, “The class is overridden and derives from MonoBehaviour but I don’t see any case anywhere about it being used as a delegate”, what did you mean? I am not aware of such a class, and if it exists in your project it will almost certainly be causing you problems.

        2. Nevermind, I appear to have fixed it and found the problem. I took a wild guess at the problem being one of typecasting over EventHandler, and I was apparently correct. Instead of the compiler assuming EventHandler, it apparently needs to be explicitly TOLD this… why I don’t know, I thought EventArgs WAS the default generic typing but hey, it is what it is.

          So to be clear, the code lines that read as such in EasingControl.cs:

          public event EventHandler updateEvent;
          public event EventHandler stateChangeEvent;
          public event EventHandler completedEvent;
          public event EventHandler loopedEvent;

          … need to be changed to the following in order to correct the specific compiler errors in my original post:

          public event EventHandler updateEvent;
          public event EventHandler stateChangeEvent;
          public event EventHandler completedEvent;
          public event EventHandler loopedEvent;

          Hopefully this helps others who may have come across this same error and get hung up. And hopefully, the EventArgs type of generic is a correct implementation. Going to go and test it out now – I’ll report back if it acts wonky. πŸ˜€

          1. That post isn’t what I’d typed. Don’t know how to edit it, can’t find a way for it to let me. What it SHOULD say is,

            “Instead of the compiler assuming EventArgs, it apparently…”

            And in the corrected lines of code, they should have the term “EventArgs” in angle brackets “” placed directly after the EventHandler keyword. I’m assuming the WordPress setup on this page doesn’t play nice with angle brackets typed into comments. Haha!

          2. This is not correct, no change needs to be made to this code. There is both a non-generic and a generic EventHandler defined in the System namespace (see the links in the other comment).

          3. That’s odd… I understand why it’s not correct, and I’d not have thought it was either, but the idea came to me to just put in the EventArgs just to see if that would “shut it up” about the compiler errors. And it did. And the script works as intended, that is, I can create, save and load tiles with it.

            Do you think maybe something with an update in Unity in recent years caused it to need some sort of explicit dictation like this? I see the tutorial is from 2015, and here we are 3 years and several Unity versions later. But I wouldn’t think that versioning would cause syntax to behave in an odd way.

            Kludge though it may be, it works, and if I find any bugs with leaving it this way, I’ll report back. For now it seems to be performing well.

          4. I’m glad you’ve at least got it working, good job finding your own solution. I don’t think it has anything to do with an update in Unity, and I was leaning more toward a comment you made – it sounded like you might have said you had an EventHandler class in your project, one which inherited from Monobehaviour. Perhaps you made that by accident, or misread a step somewhere and thought it was an action you were supposed to take. It doesn’t really matter, but if you do have a class or any sort of definition of EventHandler, then it makes sense why the compiler would get confused, and also makes sense why converting your event to use the generic signature would allow things to continue working again.

  31. Trying to post a comment about an error I’m getting but it isn’t letting me post. This is just to test to see if I post a smaller comment if it will succeed.

    1. Sorry for the delay, I manually approve all comments so that I don’t have to worry about spam flooding my posts.

      1. Completely understood. πŸ™‚ It just wasn’t responding at all when I went to save, and then magically, there was my post… lol.

  32. Just finished the project, and I’ve been going through again for research and reference purposes while making my own project (absolutely fantastic tutorial project, btw), and I was wondering what the reasons are for placing the growth and shrink scripts on the Tile script itself. I think I can see a few benefits, but I figured I’d ask for the sake of clarification and building up better habits if there was something less obvious that I was missing, like performance reasons or the like. Thanks.

    1. Glad you liked it! It’s been awhile since I wrote this, but I imagine it was simply a matter of convenience since the fields and local methods that would need to be touched were all together already. It may have been more correct to keep those methods in the editor script unless I felt there would be a chance of using them in game as well.

  33. Hey Jon! First of all, this is the best and most comprehensive tutorial series out there that I’ve been able to find for creating a game in this genre. Thank you, and great work.

    Anyway, I’ve been working on my own tactics RPG project and had a similar solution to yours in place for the game board, involving individual game objects for each tile of the map. However, I created my own asset for the level geometry and used that for the board itself, which is something you suggested to someone earlier in this comment thread. My easy temporary fix for being able to still use the tiles was simply flattening them, making them translucent, and laying them over my pre-made level geometry. It’s functional for now, but the game still has to render all those tiles at all times. As somebody pointed out, this is going to get costly and unwieldy in larger and more complicated environments.

    You proposed to someone earlier simply removing the mesh and renderer components from the tiles all together, and that’s the direction I intend to go in. Here’s my question: What would you suggest is the best/cheapest method for displaying highlighted tiles? My first thought is adding a Projector component to the empty tile object which highlights the area of the tile, and is only activated when necessary. What do you think?

    Thanks again!

    1. Great questions. I have actually recently experimented with all of this – using a pre-made mesh for the entire board with a projector to show highlighted tiles. It worked ok, and has the benefit of being able to project even if the underlying board tile isn’t flat which is better than the simple cube geometry could do. I programmatically created the texture to project from the projector, making its pixel dimensions match the board dimensions, so it was a simple matter of “masking” pixel to tile coordinate to highlight the tiles I wanted active. I think this solution will probably work for you if you want to try it.

      FYI, I also tested Unity’s ECS as a solution. I was able to make ridiculously large boards, each as their own tile object just like the current setup. Unity was able to handle it all at a nice high frame rate without any problems. I still might prefer a single modeled mesh, but it’s nice to have the option of creating individual tiles and composing them in a reusable or procedural way.

      1. That’s awesome! Thanks for the response, glad to see you’re still engaging with this stuff. Good to know I’m on the right track, too. If I’m understanding correctly, you’re just using a single projector? Could you explain briefly (or point me toward some material that does, if you’re aware of any) how you were able to programmatically generate the texture it uses, and use masking to achieve the highlighting effect?

        I think I understand conceptually. In my head you have some script component on the projector that takes the necessary information from the level data to determine the area it projects onto (the entire map). Then maybe some part of the battle controller tells the projector when to activate and what parts of the texture to exclude based on what tiles are meant to be highlighted. Am I close?

        I’m going to keep poking around and investigating, but would you happen to know of any educational material or documentation that would provide some insight on the techniques you’re using here? I have the project repo, if that helps. Thanks again.

        * I actually had no idea about ECS at all until looking through some of the comments here prompted me to investigate. I’m impressed and intrigued. Another thing I’ll definitely be experimenting with now that I know about it.

  34. Thank you for this amazing tutorial, it really helped a beginner like me to get started on Tactic RPG.
    I have a silly question though, if I don’t want jump and height mechanics (that is, the game plays on a flat board or tile map) should I omit everything related to height in the script?
    Again, sorry if my question is silly.

    1. Glad you are enjoying the tutorial. As a beginner, I would recommend you leave the code alone and just create flat game boards to fight on. If you are a little more advanced, then you can start removing things regarding height, but keep in mind that it will effect many things and could be a bit overwhelming to remove.

  35. Holy crap, this was a huge step up from lesson 1. I’m pretty sure this is way above my current knowledge level, but I managed to follow along and it all worked at the end! I’m pretty sure I learned some things along the way, but my head’s still spinning at the moment.

    I can’t express enough how much I appreciate your work! I intend to consistently revisit all of the links you’ve provided so far, aiming to understand more each time; These are almost my very first steps in Unity, so hopefully I can continue to keep up with your instructions.

    Huge thanks also to everyone who helped Jon revise any errors in the code – I remember halfway through the BoardCreator script just thinking “If this returns a compiler error, I’ve got no chance of fixing it!”

    If there are any other resources/tutorials you think I should be tackling first/as well, I’d be ever so appreciative of any guidance you can offer. C# is making more sense every day, but damn if this isn’t the most complicated and arcane stuff I’ve ever put my mind to.

    Once again, I can’t thank you enough for the work you’ve put into this project and for your generosity in sharing your knowledge. What a gent!

    1. Really glad to hear that, good luck on your journey! The series is admittedly advanced content, but I really tried hard to explain things as thoroughly as I could – it’s possible that each lesson will feel a bit like this one did. If it’s too much, you can take a brief detour and look at my own C# tutorials here, http://theliquidfire.com/tutorials/. Otherwise, my favorite way to learn is by following along with projects just like this one.

  36. Than you so much for the tutorial! Can I ask a question?
    I followed everything in the tutorial and all the buttons in the inspector work well. However, my indicator does not automatically appear at the correct place when I move the tiles. Do you have any idea what can cause this problem?
    I checked the code in BoardCreatorInspector script and I have the code “If(GUI.changed) current.UpdateMarker();”

    Thank you so much again!

    1. If the Tile Indicator appears (just not in the correct place) then you probably configured the tile indicator prefab incorrectly. Make sure that the visuals on that game object are centered around the origin properly.

  37. Hi! I’ve been following this tutorial thorough though I’m a bit worried of its age. Are there any pitfalls you would have avoided now that you have the benefit of hindsight?

    1. Great question. I kept this project pretty simple, and really focused a lot more on just the systems than on the implementation. I think that approach allows the project as-is to be relevant for much longer than it might otherwise have been. If I were to make it again, I might do things a little differently here or there, but overall I think the project still has plenty of value to learn from. I don’t know that there are any particular pitfalls that are going to be a problem, though anything the project doesn’t cover, such as the ability to save and load a game, maybe challenging for beginners to implement.

  38. I’m having quite a lot of trouble with the map generator. Whenever I click the grow area button I create dirt tiles much shorter than the original prefab, is that suppose to happen? The tile selection prefab is also created as a separate game object but doesn’t update its position with the tile that I am moving.

    Thanks for the help.

    1. When most people get stuck, it is because they missed a setup step somewhere along the way of assigning a reference in the inspector. I would check that all of your references are assigned to the correct things. Next, I just want to point out that you aren’t intended to move tiles. You are just moving a highlight to indicate where on the board the editor buttons would be growing or shrinking. Each grow or shrink works in a step which is a quarter the size of a normal tile. This allows it to easily create some organic shapes especially when you combine it with the area options.

      1. I also extended the board creator slightly, I pair the camera at start to the marker for a simple camera follow. I’ll add a picture later.

  39. Hi! Really thanks for this. Help me so much! I would be a Game Dev and your work is so useful.
    I have a question. Why do you use “a” and “b” for “Point operator +”, “bool operator ==” and “bool operator !=” but “p1” and “p2” for “Point operator -“?.
    I know that is change nothing in programmation, but when i compare my code with yours, i was upset by this.
    I’m an amateur, so i ask, but it’s maybe nothing.

    Really thanks for your work, again.

    1. Hey Nathan, there are a lot of conventions used for short locally scoped variable names. Those are a few of them. The difference here isn’t a sign of something important that you should worry about, it was merely inconsistency in my own code.

  40. Hey Jon! I have been following your tutorials for some months now and I think they are really great. Thanks for making them and sharing the code. For me, it has helped me understand a lot, specially architectures, tweeners, and how to save and load data.

    I have modified some things around your tutorials to make a multiplayer tactics game where each turn you can spawn and move as many units as you can. So far I have been able to modify everything to achieve the results I have in mind. Nevertheless this week I encountered a problem I haven’t been able to solve.

    I wanted to have more control over my tiles and how I build level datas, so I changed the level data Scriptable Object to take in a list of “Tile Data”, instead of vector3’s. TileData is a class I made to hold the position of the tile in a vector3, an int for the terrain cost, and a string for the model name in the Resources file.

    I made all the modifications in the pertinent methods, to read and write the dataTiles when instantiating, loading, saving and everything else. I can create the board and each tile contains all the info requiered to make a “TileData” entry. But when I try to save, the game starts having some troubles and after some minutes it shows a lot of errors:

    StackOverflowException: The requested operation caused a stack overflow. x1
    ArgumentException: The specified path is not of a legal form (empty) x182

    The errors also inform that the problems have to do with a lot of scripts, and specially with a script that inherits from the AssetPostprocessor (a script I made following your bestiary management and unity factory tutorials, wich has always worked fine to create more units with a simple .txt). I think the problem might be related to the way AssetDatabase creates assets. but I am not sure

    Do you thing maybe its not possible, or optimal, to save the board as a List of DataTiles?

    Again, thanks for all the work you have put in these tutorials. I really aprecciate it.

    1. That’s awesome to hear, I am glad you have been enabled to customize the project to that extent!

      It shouldn’t be a problem to save a board as a List of “anything” assuming the target data type is Serializable. The approach you are taking sounds appropriate as well.

      A StackOverflowException may indicate that you have run into an infinite loop such as a while loop that doesn’t exit or a recursive method call that calls itself. The ArgumentException could be that somehow you forgot a file name.

      If you are familiar with breakpoints, I would recommend you use them to step through your code and make sure that your save code is working as intended. Good luck!

      1. Thanks for answering so fast! And thanks for the tip! I usually comment each line of code until I find track the error. I finally found the problem, which is the last line of the Save method:

        AssetDatabase.CreateAsset(board, fileName);

        To solve this, I had to deactivate the script that creates new UnitRecipes and “hears” my Resources file (the one with the OnProcessAllAssets method). With that script eliminated, the BoardCreator works just fine. I understand that maybe the problem comes from trying to create a new asset while this said script is listening and trying to make changes as well?

        Thanks again Jon!

        1. Good question – I only used the post process stuff to watch for external changes. It might help to look at the file paths of the assets and determine whether your additional code needs to run at that point. Maybe put external resource stuff into a specific folder so you can just check for that when running the auto-process magic.

  41. Howdy! Very impressed with this and had some questions, since it seems like you’re still answering πŸ™‚

    1) My team is finding themselves backtracking for a 4th time trying to create a similar terrain pattern to the original PS1 Final Fantasy Tactics. One of the key elements of this terrain style (unlike the NDS sequels) is that terrain can be *angled* at various angles, so the top of your tile could be slanted, for example, to represent a bridge or a hill.

    2) Another component that doesn’t seem to be covered already here would be having over-height tiles. For example, a cliffside. You want to populate the cliffside with a super tall tile that you can walk on the top of, but still want art for the 15 feet of space below it, not just empty space, and the next tile being completely disconnected from the top of the cliff.

    3) Would it be possible to extend this system to allow for “walking under a bridge”, where you have two tiles occupying the same XY, but at different Z locations? This is more of a nice-to-have, not a necessity, but it would be super cool to work out if it’s possible with this architecture!

    How could these things be accomplished? Thank you so much for what you’ve already done and for reading my post! I hope you have an excellent week πŸ™‚

    1. Glad you are enjoying it πŸ™‚

      1. There are some subtleties to this that may be tedious for things like determining jump height from/to sloped tiles, but it should be manageable. I would try something like defining an enum with a few supported types: flat, slope north, slope east, etc. Then instead of just height, I would also need a min and max height. That should be enough to describe your tiles. Then you’d need to add tools to support creating them, and make sure traversing slopes via walking didn’t trigger a jump.

      2. I think you can do that with my generator already. Not sure I understand what is missing. However, it’s worth noting that I imagined this generators purpose is only to prototype and build level data. In production, the actual art assets for the levels would probably be built in an external 3d package to make it look nice.

      3. It’s possible, but has several challenges to consider. First, the board’s tiles are stored in a dictionary where the key is a 2D point. The bridge tile would occupy the same 2D point as the tile beneath it, so you would be unable to store both as-is. This means you need a different structure to hold the tiles, perhaps just an array. Then, to help with pathfinding, I might just add references to each tile so that it knew what tiles, if any, were connected in each cardinal direction. I would have to do pathfinding based on those links instead of by coordinates, unless the unit was able to fly or teleport.

      Good luck!

  42. Hey, I made two buttons. I hope you don’t mind if I share the code.

    public void GenerateBoard()
    {
    Clear();
    for(int x = 0; x < width; x++)
    {
    for(int y = 0; y < depth; y++)
    {
    pos = new Point(x,y);
    GrowSingle(pos);
    }
    }
    }

    public void GenerateRandomBoard()
    {
    Clear();
    for(int x = 0; x < width; x++)
    {
    for(int y = 0; y < depth; y++)
    {
    int randomHeight = UnityEngine.Random.Range(0,depth);
    for(int z = 0; z < randomHeight; z++)
    {
    pos = new Point(x,y);
    GrowSingle(pos);
    }
    }
    }
    }

    and in the BoardCreatorInspector

    if (GUILayout.Button("Generate Board"))
    current.GenerateBoard();
    if (GUILayout.Button("Generate Random Board"))
    current.GenerateRandomBoard();

  43. Hey, just wanted to share what I expanded into from the base map generator.

    https://ibb.co/R090QwW

    Check it out, I can move and navigate with wasd keys, and then click to select the prefab for the tile to use. I can type the map name and save, and it updates in real time. Needs to be in play mode for this to all work correctly, but it is so fast to modify and change boards! The images are grabbed from the assets prefab preview, and it took a while to figure out all the parts. I originally made it an inspector only script, but found the play mode version to run much smoother.

    1. Hi, I’m really sorry to bother you, but would you be able to share your code or make a tutorial for us to learn how to use it this way

  44. Great tutorial! I’m a bit late to the party but I’ve been wanting to make a game just like this for a while.
    I wasn’t planning on commenting, but I was having an issue that someone else solved on a comment, so I wanted to leave a footnote to anyone that may come after reminding them to make sure to attach the Tile.cs script to the Tile prefab lol

  45. Hello i am fiddling with unity iso Tilemap z as y,im wondering how i would implement the movement system using only the grid,i can’t seems to find any information about using an iso 2d Tilemap around if someone can put me in the right direction i would owe that person a lot thanks. πŸ˜€

    1. I’ve just started playing with Tilemap myself, but you should be able to use Tilemap’s CellToWorld method to turn a tile grid position to a world position that you could then use to animate other sprites using the same animation libraries I have included with this project.

  46. I just want to start by thanking you for how generously you offer these amazing tutorials. It’s hard to believe exactly what I needed could exist and for free, no less.

    That said – there’s a little type on this page. You say:

    “randomly growning” and I think you mean “growing”

    Thank you so so much!

  47. There is a little problem in the BoardCreator.cs script, seems like `name` is never initialized. I changed the code like this:

    “`
    — a/Assets/Scripts/PreProduction/BoardCreator.cs
    +++ b/Assets/Scripts/PreProduction/BoardCreator.cs
    @@ -9,6 +9,7 @@ public class BoardCreator : MonoBehaviour
    #region Fields / Properties
    [SerializeField] GameObject tileViewPrefab;
    [SerializeField] GameObject tileSelectionIndicatorPrefab;
    + [SerializeField] string levelName;
    [SerializeField] int width = 10;
    [SerializeField] int depth = 10;
    [SerializeField] int height = 8;
    @@ -78,7 +79,7 @@ public class BoardCreator : MonoBehaviour
    foreach (Tile t in tiles.Values)
    board.tiles.Add( new Vector3(t.pos.x, t.height, t.pos.y) );

    – string fileName = string.Format(“Assets/Resources/Levels/{1}.asset”, filePath, name);
    + string fileName = string.Format(“Assets/Resources/Levels/{0}.asset”, levelName);
    AssetDatabase.CreateAsset(board, fileName);
    }
    “`

    1. The “name” is the name of the GameObject that the MonoBehavior is attached to, and is why you didn’t see it initialized. It is probably more intuitive to add the extra field like you’ve done here though, so thanks for sharing!

Leave a Reply to Malik Gray Cancel reply

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