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 growning 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.

72 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. 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.

  18. 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.

  19. 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

  20. 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!

  21. 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;
    }
    }

  22. 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.

  23. 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.

  24. 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.

Leave a Reply

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