Creating an RTS in Unity: Part VIII

Update: This can now also be found at stormtek.geek.nz/rts_tutorial/part8.php, where the entire tutorial is now being hosted.

This time we will look at implementing something a little more interactive – we will add some basic movement to our units.

Movement Cursor

First up, let’s modify the cursor state to indicate a movement option for when we have a Unit selected. Before we can implement something specific for our Unit, we need to add in some general handling of a mouse hover. We start by adding this method to UserInput

private void MouseHover() {
	if(player.hud.MouseInBounds()) {
		GameObject hoverObject = FindHitObject();
		if(hoverObject) {
			if(player.SelectedObject) player.SelectedObject.SetHoverState(hoverObject);
			else if(hoverObject.name != "Ground") {
				Player owner = hoverObject.transform.root.GetComponent< Player >();
				if(owner) {
					Unit unit = hoverObject.transform.parent.GetComponent< Unit >();
					Building building = hoverObject.transform.parent.GetComponent< Building >();
					if(owner.username == player.username && (unit || building)) player.hud.SetCursorState(CursorState.Select);
				}
			}
		}
	}
}

and call it from the end of MouseActivity() in UserInput. Now we need to implement the default behaviour for SetHoverState in WorldObject.

public virtual void SetHoverState(GameObject hoverObject) {
	//only handle input if owned by a human player and currently selected
	if(player && player.human && currentlySelected) {
		if(hoverObject.name != "Ground") player.hud.SetCursorState(CursorState.Select);
	}
}

With the default behaviour in place we can now add some specific behaviour for our Unit with the following.

public override void SetHoverState(GameObject hoverObject) {
	base.SetHoverState(hoverObject);
	//only handle input if owned by a human player and currently selected
	if(player && player.human && currentlySelected) {
		if(hoverObject.name == "Ground") player.hud.SetCursorState(CursorState.Move);
	}
}

If you run this from Unity now you will notice that no move cursor shows up if the unit is selected. This is because the Unit does not actually belong to a player at the moment. To fix this, drag your unit onto your player, making it a child of the player. Unfortunately, you will now no longer be able to select the Unit, since we accidentally introduced a bug in an earlier post. Thankfully this is an easy bug to fix. The problem is that when we are determining which object was selected we are using

hitObject.transform.root.GetComponent< WorldObject >()

to gain a reference to the WorldObject script (or a subclass of that). Unfortunately, when we add the object to a player this reference no longer works. We actually need to use

hitObject.transform.parent.GetComponent< WorldObject >()

instead. To fix our code we need to make this change in MouseClick() in WorldObject and LeftMouseClick() in UserInput. Now if you run from within Unity things should behave as expected. We can still select both MyBuilding and MyUnit. If we have the building selected we are only ever shown the select cursor. But if we have the unit selected we are shown the select cursor if we are over an object and the move cursor if we are hovering over the ground – complete with animation.

Movement Input

Now it is time to handle user input for movement. To initiate movement for a Unit we need to extend the functionality of our mouse click logic for a world object. Add the following code to MouseClick inside Unit.cs.

public override void MouseClick(GameObject hitObject, Vector3 hitPoint, Player controller) {
	base.MouseClick(hitObject, hitPoint, controller);
	//only handle input if owned by a human player and currently selected
	if(player && player.human && currentlySelected) {
		if(hitObject.name == "Ground" && hitPoint != ResourceManager.InvalidPosition) {
			float x = hitPoint.x;
			//makes sure that the unit stays on top of the surface it is on
			float y = hitPoint.y + player.SelectedObject.transform.position.y;
			float z = hitPoint.z;
			Vector3 destination = new Vector3(x, y, z);
			StartMove(destination);
		}
	}
}

We start off here by making sure that the default implementation for handling a mouse click is handled. Then, if the player clicked on the ground, we want to start a move towards that position. To handle that we need to implement StartMove.

public void StartMove(Vector3 destination) {
	this.destination = destination;
	targetRotation = Quaternion.LookRotation (destination - transform.position);
	rotating = true;
	moving = false;
}

This methods sets the state for our object in preparation for moving. We need the destination that our Unit is to move towards and the rotation that Unit needs to rotate towards the destination. This rotation is given to us by the Unity method Quaternion.LookRotation. It also appears that Unity is using the z-axis as the forward direction (which is why we constructed our Unit the way we did earlier). We then specify that the Unit is ready to rotate in preparation for moving. To allow our code to compile we need to add these global variables to the top of Unit.

protected bool moving, rotating;

private Vector3 destination;
private Quaternion targetRotation;

Unit Movement

With the ability for players to give destinations to units, it is time to implement some basic movement for a Unit. This will be telling the Unit how to get from it’s current location to the destination that has been set. For now we will go with a very simple algorithm.

  • Rotate so that the “front” of the Unit is pointing directly at the destination
  • Move in a straight line towards the destination, ignoring everything in it’s path
  • Stop when the destination is reached

At a later date you will want to implement some form of pathfinding and collision avoidance so that your Unit does not run into anything along the way. But for now it is enough to be able to move around. Remember that at the end of StartMove() we introduced two state variables – rotating and moving. We will use these to determine what the Unit should currently be doing. Modify the Update method for Unit to be the following.

protected override void Update () {
	base.Update();
	if(rotating) TurnToTarget();
	else if(moving) MakeMove();
}

This implements all of the logic for our algorithm above, leaving it up to the individual methods to fill in the blanks for how each part is to be implemented. Let’s handle turning towards our destination first.

private void TurnToTarget() {
	transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, rotateSpeed);
	//sometimes it gets stuck exactly 180 degrees out in the calculation and does nothing, this check fixes that
	Quaternion inverseTargetRotation = new Quaternion(-targetRotation.x, -targetRotation.y, -targetRotation.z, -targetRotation.w);
	if(transform.rotation == targetRotation || transform.rotation == inverseTargetRotation) {
		rotating = false;
		moving = true;
	}
}

The Unity method Quaternion.RotateTowards provides a smooth rotation between the current rotation of the Unit and the desired rotation. The variable rotateSpeed is used to determine how quickly we want to get between those two rotation values. A numerical issue can cause the Unit to be stuck facing the wrong way, which we fix with the check against inverseTargetRotation. Once the current rotation of the Unit matches the desired (or inverse desired) rotation we stop rotating and declare that the Unit is now ready to move. Before we go any further let’s declare a movement speed and a rotation speed at the top of Unit.cs, making them public so that we can tweak their values inside Unity.

public float moveSpeed, rotateSpeed;

Now we need to provide the basic movement for our Unit.

private void MakeMove() {
	transform.position = Vector3.MoveTowards(transform.position, destination, Time.deltaTime * moveSpeed);
	if(transform.position == destination) moving = false;
}

Once again we make use of a Unity method to give us smooth movement to our destination, this time by using Vector3.MoveTowards. And once again we have moveSpeed to help us determine how quickly that will happen. If we now set moveSpeed and rotateSpeed for the Unit to 1 you should be able to direct it around the map (taking care not to run into any other objects). Feel free to play around with these values until they match a speed that you are comfortable with.

You will notice an annoying fact when you move your unit: the selection box remains where the Unit was first located, which is no use at all. What we want is for the selection box to remain around the Unit at all times. The selection box is based on the Bounds for the Unit – these are never being updated, which is why the selection box does not move. We may want these bounds to remain up-to-date for other checks against the Unit, so let’s make sure they stay current by adding

CalculateBounds();

to the end of TurnToTarget() and MakeMove(). This guarantees that whenever the position of the Unit has changed (whether through movement or rotation) it’s Bounds remain correct.

And that brings us to the end of this part. We are now informing the user that they can select a position to move the currently selected Unit to and allowing them to move that Unit there in a very basic fashion. We have also structured the code in such a way that we should be able to update the movement process reasonably easily without breaking the rest of our code. As usual, the code can be found on github under the post for Part 8.

Creating an RTS in Unity: Part VII

Update: This can now also be found at stormtek.geek.nz/rts_tutorial/part7.php, where the entire tutorial is now being hosted.

Now that we have a basic Unit and Building in our world, along with the ability to select them and display that fact to the user, let’s improve our HUD a little further. One of the things that is useful in games (actually, on a computer in general) is having the mouse cursor change when what the player can do changes. For example, hovering over a building you can select would show a different cursor than hovering over the ground when a unit that can move is selected. This time we will add in the ability to change the cursor based on a sense of cursor state. The HUD itself will be responsible for actually drawing the correct cursor. But it will be up to a number of things in our game to determine what the actual state of the cursor needs to be.

Custom Cursor

The first thing we want to be able to do is to draw a custom cursor. As part of this we will introduce the concept of cursor state – so that drawing is based on that state and so that changing that state does not need to worry about the draw code. Before we begin drawing we should create a place to store all the cursors that we are going to use. Inside the HUD folder create a new folder called Cursors. We may as well put all of our cursors into folders now, so that when we want to use them we can just grab the ones we want. Inside the new Cursors folder create folders with the following names: Attack, Harvest, Move, Pan, Select. As you can see, we are going to have a number of different states that we will display to the user with our cursor. Many of these actions will not be added until later, but it is still useful to have the cursors here.

  1. Attack Cursors
    • Attack Cursor 1Attack Cursor 1
    • Attack Cursor 2Attack Cursor 2
  2. Harvset Cursors
    • Harvest Cursor 1Harvest Cursor 1
    • Harvest Cursor 2Harvset Cursor 2
  3. Move Cursors
    • Move Cursor 1Move Cursor 1
    • Move Cursor 2Move Cursor 2
  4. Pan Cursors
    • Pan Up CursorPan Up Cursor
    • Pan Down CursorPan Down Cursor
    • Pan Left CursorPan Left Cursor
    • Pan Right CursorPan Right Cursor
  5. Select Cursor
    • Select Cursor 1Select Cursor

The images above are the cursors that I am using for this project. Feel free to use them as well. Each of these needs to go into the appropriate folder. (Apologies for slightly weird formatting or display there, it is WordPress being annoying … If you click on each image they should open in a new tab, you can save them from there.)

Now that we have some images to work with, we need a way to be able to access these within our HUD. Add the following variables to the top of HUD.cs

public Texture2D activeCursor;
public Texture2D selectCursor, leftCursor, rightCursor, UpCursor, downCursor;
public Texture2D[] moveCursors, attackCursors, harvestCursors;

and then drag the appropriate images onto them in Unity. Just make sure that when you are adding images to the array variables that you add them in the right order (1, 2, 3, …) otherwise the animation sequence that we end up using will look really weird. Now it is time to get our custom cursor drawing. Add the following method to HUD.cs.

private void DrawMouseCursor() {
	if(!MouseInBounds()) {
		Screen.showCursor = true;
	} else {<
		Screen.showCursor = false;
		GUI.skin = mouseCursorSkin;
		GUI.BeginGroup(new Rect(0,0,Screen.width,Screen.height));
		UpdateCursorAnimation();
		Rect cursorPosition = GetCursorDrawPosition();
		GUI.Label(cursorPosition, activeCursor);
		GUI.EndGroup();
	}
}

If the mouse is over the HUD area of the screen we will use the system mouse cursor (although we could easily change this at a later date). If not we need to cancel the system cursor and draw our own in its place. To make sure that things always behave we will define a skin for our cursor (a new default skin will do fine) and define the group drawing area to be the entire screen. We then update any animation our cursor might have to make sure we have the correct frame set in activeCursor, find where it needs to be drawn on screen, and then draw it there using a Label.

As usual, there are some steps we need to take to make this method work. First, create a new skin (leave all the settings as default) and call it MouseCursorSkin. Now create a reference to another GUISkin at the top of HUD called mouseCursorSkin and attach the new skin to your HUD in Unity. Finally, we need to create the two methods which do most of the work for us. Let’s start with UpdateCursorAnimation.

private void UpdateCursorAnimation() {
	//sequence animation for cursor (based on more than one image for the cursor)
	//change once per second, loops through array of images
	if(activeCursorState == CursorState.Move) {
		currentFrame = (int)Time.time % moveCursors.Length;
		activeCursor = moveCursors[currentFrame];
	} else if(activeCursorState == CursorState.Attack) {
		currentFrame = (int)Time.time % attackCursors.Length;
		activeCursor = attackCursors[currentFrame];
	} else if(activeCursorState == CursorState.Harvest) {
		currentFrame = (int)Time.time % harvestCursors.Length;
		activeCursor = harvestCursors[currentFrame];
	}
}

This method is relying on two global variables, so we need to declare them at the top of HUD.

private CursorState activeCursorState;
private int currentFrame = 0;

CursorState is actually an Enum. These are useful for declaring things like a collection of states (as opposed to using an array of strings that we then have to access in annoying ways). As you can see above, checking to see whether we are in a certain state is quick and easy. Inside the RTS folder create a new C# script called Enums and replace the entire file with the code below.

namespace RTS {
	public enum CursorState { Select, Move, Attack, PanLeft, PanRight, PanUp, PanDown, Harvest }
}

If we ever want to handle more cursor states in our game all we need to do is add extra entries to this enum and then we can perform the relevant checks elsewhere in our code. Remember, if we want to access this Enum from a class we need to add

using RTS;

above the class definition.

Now to evaluate that method and see what it is actually doing. Each branch of the if statement is handling a different cursor which has more than one image for it. If there is only one image we do not need to handle an animation. The logic for each cursor is the same, all that is changing is the array of images being referenced. The first line determines the current frame. This does so by making sneaky use of some math theory, which I will go into very briefly here. The % operator in C# performs a division on two integers (whole numbers) and then returns the remainder. For example: 7 / 2 gives us 3 lots of two with one left over (3 * 2 + 1 = 7). So 7 % 2 will return the one left over. This is very useful when we want to access a particular entry in an array based on some changing element – in this case time. Unity provides us the current elapsed game time (in seconds) through Time.time. By casting this to an int we round the current time down to the nearest second (so 116.23 seconds becomes 116 seconds). We then divide this value by the number of images we have for our cursor (given by the length of the array for that cursor’s images) and retrieve the remainder. This is guaranteed to be a valid index in our array. The index position will advance by one every second, and it will automatically wrap back to the start of the array (since a match with the length of the array gives a remainder of 0). This one simple line of code is extremely powerful, and we will use this underlying concept in other places as we progress. Once we have the index value we can set the active cursor to the appropriate image for the current cursor state. (For those interested in learning more look up Modular arithmetic)

There is still one more method for us to create: GetCursorDrawPosition.

private Rect GetCursorDrawPosition() {
	//set base position for custom cursor image
	float leftPos = Input.mousePosition.x;
	float topPos = Screen.height - Input.mousePosition.y; //screen draw coordinates are inverted
	//adjust position base on the type of cursor being shown
	if(activeCursorState == CursorState.PanRight) leftPos = Screen.width - activeCursor.width;
	else if(activeCursorState == CursorState.PanDown) topPos = Screen.height - activeCursor.height;
	else if(activeCursorState == CursorState.Move || activeCursorState == CursorState.Select || activeCursorState == CursorState.Harvest) {
		topPos -= activeCursor.height / 2;
		leftPos -= activeCursor.width / 2;
	}
	return new Rect(leftPos, topPos, activeCursor.width, activeCursor.height);
}

The basic position we want to start with for any cursor is the position on screen where the mouse cursor would be drawn. Remember that Unity has the draw coordinate starting in the bottom left corner, so we need to take the screen coordinates for the mouse (which start from the top left corner) and invert these to make sure that the cursor will be drawn in the correct position.

We now want to tweak the position of the mouse based on its current state. There are a number of reasons why we might want to do this. The most common reason is that a large number of the cursors we will use actually have the point where we think the cursor is as the centre of the image, not the top left corner (which is where we will be starting the drawing of our image from). In all of these cases we need to shift the draw position up and to the left by half the width and height of our image. The other special cases here (at the moment) are for when we are panning right or down. Remember that this happens when the mouse is positioned on the far right or bottom of the screen. If we were to draw the cursor from the top left of our image in either of those situations, we would be drawing the cursor off-screen, which is no use at all. So we need to make sure that the draw position is shifted back on screen appropriately in theses scenarios.

It turns out that there is one more thing we need to do before we can even show a custom cursor on screen at the moment. We need to set what the default cursor will be for our HUD. But before we do that, let’s create a method that will allow us to easily change our cursor state.

public void SetCursorState(CursorState newState) {
	activeCursorState = newState;
	switch(newState) {
	case CursorState.Select:
		activeCursor = selectCursor;
		break;
	case CursorState.Attack:
		currentFrame = (int)Time.time % attackCursors.Length;
		activeCursor = attackCursors[currentFrame];
		break;
	case CursorState.Harvest:
		currentFrame = (int)Time.time % harvestCursors.Length;
		activeCursor = harvestCursors[currentFrame];
		break;
	case CursorState.Move:
		currentFrame = (int)Time.time % moveCursors.Length;
		activeCursor = moveCursors[currentFrame];
		break;
	case CursorState.PanLeft:
		activeCursor = leftCursor;
	break;
	case CursorState.PanRight:
		activeCursor = rightCursor;
		break;
	case CursorState.PanUp:
		activeCursor = upCursor;
		break;
	case CursorState.PanDown:
		activeCursor = downCursor;
		break;
	default: break;
	}
}

We set the active cursor state to the new state specified and then we update the cursor accordingly. You will notice that if a cursor has multiple images we are using the same method as before for selecting which frame of the animation to show. We do it this way so that next update the animation will flow seamlessly, rather than jumping from the first frame to whatever UpdateCursorAnimation() decides the frame should be.

Okay, all of our framing is in place, now we just need to call it from the right places. In HUD, add the following line to the end of Start()

SetCursorState(CursorState.Select);

and this line to the end of OnGUI() (inside the check for whether the player is human).

DrawMouseCursor();

If you run this from inside Unity you should see that our Select cursor is now being drawn while the mouse is inside the playing area. (Sometimes in this demo mode Unity does not hide the system mouse cursor when it should, selecting an object should fix this. Either way, you should still be able to see the Select cursor being drawn)

Changing Cursor State

It turns out that most of the framework for changing our cursor state is in place already. All that is left is to implement the logic of when this should change throughout our codebase. For now, let’s change the cursor when we are panning the map around. This is a good simple way to inform the user what is happening. To make this happen we need to add some code to MoveCamera() inside our UserInput script. Update the code which handles vertical and horizontal movement to the following

bool mouseScroll = false;

//horizontal camera movement
if(xpos >= 0 && xpos < ResourceManager.ScrollWidth) {
	movement.x -= ResourceManager.ScrollSpeed;
	player.hud.SetCursorState(CursorState.PanLeft);
	mouseScroll = true;
} else if(xpos <= Screen.width && xpos > Screen.width - ResourceManager.ScrollWidth) {
	movement.x += ResourceManager.ScrollSpeed;
	player.hud.SetCursorState(CursorState.PanRight);
	mouseScroll = true;
}

//vertical camera movement
if(ypos >= 0 && ypos < ResourceManager.ScrollWidth) {
	movement.z -= ResourceManager.ScrollSpeed;
	player.hud.SetCursorState(CursorState.PanDown);
	mouseScroll = true;
} else if(ypos <= Screen.height && ypos > Screen.height - ResourceManager.ScrollWidth) {
	movement.z += ResourceManager.ScrollSpeed;
	player.hud.SetCursorState(CursorState.PanUp);
	mouseScroll = true;
}

and then add the following check at the bottom of the method

if(!mouseScroll) {
	player.hud.SetCursorState(CursorState.Select);
}

to make sure that we do not get stuck with a Pan cursor. If you run this now you will see that the cursor is changing, but we cannot see it when we are panning up or right since our mouse is over the HUD. Let’s fix that, since it will leave players highly confused as to what is going on. At the top of DrawMouseCursor() we need a better check to see if the mouse is in the HUD or not. This should do, so add it to the start of the method

bool mouseOverHud = !MouseInBounds() && activeCursorState != CursorState.PanRight && activeCursorState != CursorState.PanUp;

and then replace the

if(!MouseInBounds())

check with

if(mouseOverHud)

to determine whether to use the system cursor or not.

I think that about does it for today. We now have custom cursors at our disposal and an easy way to change those at will. As our units and buildings gain more abilities (or we add more specialized versions of them into our game), we will be able to easily change the cursor whenever we want to. The full code for the end of this stage is up on github under the commit for Part 7.

Creating an RTS in Unity: Part VI

Update: This can now also be found at stormtek.geek.nz/rts_tutorial/part6.php, where the entire tutorial is now being hosted.

Last time we created a building and made sure that we could select it and display the name of the selected building to the user. This time we will create our first unit. Then we will present more information to the user on what they have selected by displaying a selection box around the selected unit / building.

Basic Unit

Our approach for creating a basic unit will follow a very similar format to what we did last time with our basic building. Create a new C# script inside your Unit folder (rather than the Building folder used last time) and call it Unit. Once again we want to have this class inherit from WorldObject and to provide an override for those Unity methods. The resulting class should look the same as I have below.

public class Unit : WorldObject {

	/*** Game Engine methods, all can be overridden by subclass ***/

	protected override void Awake() {
		base.Awake();
	}

	protected override void Start () {
		base.Start();
	}

	protected override void Update () {
		base.Update();
	}

	protected override void OnGUI() {
		base.OnGUI();
	}
}

Now let us create a basic unit, modeled very loosely on a tank. Remember, this is just placeholder to see that things are working – a perfectly acceptable way of doing things for early production. Create a new empty object, call it Unit, and set its transform properties as follows: position = (0, 0, 0), rotation = (0, 0, 0), scale = (1, 1, 1). Once again our building is in the way, so set it’s position to (-20, 0, 0). Our unit will be made up of two cubes, two capsules, and a cylinder, so create these and drag them into the Unit object, making them children of the Unit. Rename the capsules to be LeftTread and RightTread, rename the cubes to be Body and Turret, and rename the cylinder to be Muzzle. Now that we have names to identify all of the elements by, it is time to set the positioning and scale of each of them. The following settings will work well enough:

  • LeftTread: position = (-0.75, 0.25, 0), rotation = (0, 90, 90), scale = (0.5, 1.85, 0.5)
  • RightTread: position = (0.75, 0.25, 0), rotation = (0, 90, 90), scale = (0.5, 1.85, 0.5)
  • Body: position = (0, 0.65, 0), rotation = (0, 90, 0), scale = (3, 0.5, 1.5)
  • Turret: position = (0, 1.35, 0), rotation = (0, 0, 0), scale = (1, 1, 1)
  • Muzzle: position = (0, 1.4, 1), rotation = (0, 90, 90), scale = (0.5, 0.75, 0.5)

Now attach the script Unit.cs to the object Unit, give it the Object Name of MyUnit, and run your game from inside Unity. You should see the original cube that we made, the building from last time, and your newly created unit. Clicking on the unit should display the name MyUnit in the HUD, and then selecting the building should display MyBuilding. And just like that we have a unit which can be selected. Now we begin to see just how useful the inheritance can be (when done correctly of course).

Selection Box

So we now have a building and a unit in our world, along with a random object (which is useful to show that we can only select objects which we have defined as WorldObjects). The user can tell the name of what was selected, but currently has no way of knowing which of the objects in the world that is – which will quickly become annoying as the number of objects in the world increases. Let’s fix that little problem for the user by drawing a selection box around the object the user currently has selected. Since it is a world object that is selected, we will let WorldObject handle the drawing of a selection box when required. To begin, let us add the following code to the OnGUI method of WorldObject.

if(currentlySelected) DrawSelection();

Note that we only want to draw a selection box if the world object has been selected. Obviously to do anything we are going to need to create the appropriate method. Do so now, making sure that it is a private method, and add this code to it.

private void DrawSelection() {
	GUI.skin = ResourceManager.SelectBoxSkin;
	Rect selectBox = WorkManager.CalculateSelectionBox(selectionBounds, playingArea);
	//Draw the selection box around the currently selected object, within the bounds of the playing area
	GUI.BeginGroup(playingArea);
	DrawSelectionBox(selectBox);
	GUI.EndGroup();
}

We will store the skin to be used for drawing in our resource manager, since we only wish to have one reference to it (rather than one for every world object). We will add that one reference to our HUD and then use the HUD to set that value in our ResourceManager. The other advantage that this gives us is the ability (if we wanted) to customise the selection box for each player. We will not look into that here, but it is an option that our project will support quite easily. To create a reference to our skin add the following code to ResourceManager.

private static GUISkin selectBoxSkin;
public static GUISkin SelectBoxSkin { get { return selectBoxSkin; } }

public static void StoreSelectBoxItems(GUISkin skin) {
	selectBoxSkin = skin;
}

This provides a public accessor for the GUISkin and a public method for storing any items to be used with a selection box. At the moment this is just a skin, but that will change later on. Now we need to add another public skin to our HUD giving us

public GUISkin resourceSkin, ordersSkin, selectBoxSkin;

at the top of HUD.cs now. To access our resource manager we need to add

using RTS;

to HUD above the class definition (along with the other using statements there). Now we can add this line

ResourceManager.StoreSelectBoxItems(selectBoxSkin);

to the end of the Start method to make sure that the ResourceManager has a reference to the skin, which our WorldObject can now access when it needs to draw the selection box. Before we go any further with this code, let’s create the skin (inside the skins folder in our HUD directory) and call it SelectBoxSkin. Create a new 128×128 image called selectionBox.png and store it in the Images folder for the HUD. Make this image completely transparent apart from a 1 pixel thick line which wraps around each corner. It should look similar to my image below.Selection Box

Set the background for box in our SelectBoxSkin to be this image. Then change the following setting for box:

  • Border: (3, 3, 3, 3)
  • Margin: (0, 0, 0, 0)
  • Padding: (0, 0, 0, 0)

Leave all of the other settings as their default values. Now set the SelectBoxSkin for the HUD to be that newly created skin.

If we now look back at the code we added to WorldObject we can see that there is a reference to selectionBounds. We need to define that by adding this code to the top of WorldObject:

protected Bounds selectionBounds;

Before we look at calculating the rectangle to draw we should make sure that the bounds of our object are being calculated correctly. Create a public method called CalculateBounds() with the following code in it

public void CalculateBounds() {
	selectionBounds = new Bounds(transform.position, Vector3.zero);
	foreach(Renderer r in GetComponentsInChildren< Renderer >()) {
		selectionBounds.Encapsulate(r.bounds);
	}
}

and then call that from the Awake method of our WorldObject.

selectionBounds = ResourceManager.InvalidBounds;
CalculateBounds();

Note that the first thing we are doing inside Awake() is to set the bounds to an invalid selection, so we better add that to our ResourceManager like so

private static Bounds invalidBounds = new Bounds(new Vector3(-99999, -99999, -99999), new Vector3(0, 0, 0));
public static Bounds InvalidBounds { get { return invalidBounds; } }

and then make sure that we add

using RTS;

to the top of WorldObject so that we can access it. The actual method CalculateBounds is making use of the fact that Unity is keeping track of the bounds of each primitive object in our world. So we set the bounds to be centred on the position of our object and extending out exactly zero units in all directions. We then find all the child objects which have bounds defined and add their bounds to ours. By calling this from the Awake method we can guarantee that each object knows the bounds it was created with. If we move on object we will need to call CalculateBounds again to recalculate that.

We will get to finding the rectangle in a moment. There is one other variable that we need to add first though: playingArea. This is a variable that our WorldObject needs to know about, and which we will set whenever we select the object (since it is actually the area of the screen that is not covered by our HUD). To get this set up will take a number of little steps. First, declare the following variable at the top of WorldObject.

protected Rect playingArea = new Rect(0.0f, 0.0f, 0.0f, 0.0f);

Second, update the SetSelection method to the following

public void SetSelection(bool selected, Rect playingArea) {
	currentlySelected = selected;
	if(selected) this.playingArea = playingArea;
}

to make sure that it is being set whenever the the object is selected. Now we need to go and change every reference to SetSelection to make sure that our code will compile and run again. Most of these happen in the ChangeSelection of WorldObject, so update it to have the following code.

private void ChangeSelection(WorldObject worldObject, Player controller) {
	//this should be called by the following line, but there is an outside chance it will not
	SetSelection(false, playingArea);
	if(controller.SelectedObject) controller.SelectedObject.SetSelection(false, playingArea);
	controller.SelectedObject = worldObject;
	worldObject.SetSelection(true, controller.hud.GetPlayingArea());
}

Note: if we are setting the selection to true then we find out what the playing area is from the HUD. There are two other places where this code is being called, both in UserInput – once in LeftMouseClick and once in RightMouseClick. In both cases add the parameter

player.hud.GetPlayingArea()

to the method call. The last thing to do for this is to create the method GetPlayingArea inside HUD.

public Rect GetPlayingArea() {
	return new Rect(0, RESOURCE_BAR_HEIGHT, Screen.width - ORDERS_BAR_WIDTH, Screen.height - RESOURCE_BAR_HEIGHT);
}

Okay. That was a little fiddly, but it is a good process to go through at the same time. Because often when you add changes to your code you realize that a number of other places need to be changed to make sure that your code will still run.

Now it is time (at last) to calculate the rectangle on screen in which we wish to draw our selection box. First up, we are going to create another static helper class called WorkManager. We will use this to collect a number of useful methods that can then be accessed anywhere in our code. Inside the RTS folder create a new C# script called WorkManager. We want to set this up similar to ResourceManager – so wrapped in the namespace RTS and not inheriting from anything. Your new class should look like this.

using UnityEngine;
using System.Collections.Generic;

namespace RTS {
	public static class WorkManager {

	}
}

With this in place we can add a static method CalculateSelectionBox to our new WorkManager which will calculate the rectangle we want.

public static Rect CalculateSelectionBox(Bounds selectionBounds, Rect playingArea) {
	//shorthand for the coordinates of the centre of the selection bounds
	float cx = selectionBounds.center.x;
	float cy = selectionBounds.center.y;
	float cz = selectionBounds.center.z;
	//shorthand for the coordinates of the extents of the selection bounds
	float ex = selectionBounds.extents.x;
	float ey = selectionBounds.extents.y;
	float ez = selectionBounds.extents.z;

	//Determine the screen coordinates for the corners of the selection bounds
	List<Vector3> corners = new List<Vector3>();
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx+ex, cy+ey, cz+ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx+ex, cy+ey, cz-ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx+ex, cy-ey, cz+ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx-ex, cy+ey, cz+ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx+ex, cy-ey, cz-ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx-ex, cy-ey, cz+ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx-ex, cy+ey, cz-ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx-ex, cy-ey, cz-ez)));

	//Determine the bounds on screen for the selection bounds
	Bounds screenBounds = new Bounds(corners[0], Vector3.zero);
	for(int i=1; i<corners.Count; i++) {
		screenBounds.Encapsulate(corners[i]);
	}

	//Screen coordinates start in the bottom left corner, rather than the top left corner
	//this correction is needed to make sure the selection box is drawn in the correct place
	float selectBoxTop = playingArea.height - (screenBounds.center.y + screenBounds.extents.y);
	float selectBoxLeft = screenBounds.center.x - screenBounds.extents.x;
	float selectBoxWidth = 2 * screenBounds.extents.x;
	float selectBoxHeight = 2 * screenBounds.extents.y;

	return new Rect(selectBoxLeft, selectBoxTop, selectBoxWidth, selectBoxHeight);
}

This is easily the biggest single piece of code I have posted so far … However, most of it breaks down into the commented sections. First up, some local variables to reduce the amount of typing needed as well as to reduce the number of references to object variables. We then use Unity methods to find the screen coordinates of each corner of the rectangular bounds which surround our object. Once we have those we can create a bounding object for those corners (in screen coordinates). We can then use this bounding object to create and return the rectangle that we are after.

The final thing to do for our selection box is to implement the method DrawSelectionBox in WorldObject. We are splitting this off into a separate method so that child objects can easily draw any extra elements they want on top of the basic selection box (for example a health bar).

protected virtual void DrawSelectionBox(Rect selectBox) {
	GUI.Box(selectBox, "");
}

This simply draws a box (with no text inside it) within the specified rectangle. Since we have set the background image for box in our skin, this will display the image that we want.

If you save all of your changes and then run your project from inside Unity you should be able to select your building and your unit and see the selection box appearing around those. Notice that as you move the camera around it maintains this rectangle around your object correctly too.

Well, I think that brings us nicely to the end of another post. We now have a unit in place. And we have just succeeded in adding a selection box to display around our selected objects. If you had any problems the full code from this part can be found on github under the commit for Part 6.

Creating an RTS in Unity: Part V

Update: This can now also be found at stormtek.geek.nz/rts_tutorial/part5.php, where the entire tutorial is now being hosted.

Now that we have created a framework for WorldObjects, let’s begin an implementation for a Building. We will create the basic framework for a building, attach this to a simple object, and then enable selection of that building – along with a basic display in the HUD of what has been selected.

Basic Building

First off, let’s create a new script in the Building folder that we created last time, we will call it Building.cs. The first thing we need to do is to make sure that our building inherits from WorldObject. To do so, change the class definition to the following.

public class Building : WorldObject {

This is the C# way of saying that we inherit from another class. Once we have done this, we need to override the Unity methods that we declared virtual in our WorldObject. For now we will simply get them to call their base implementation. You should have the following inside your Building class now.

protected override void Awake() {
	base.Awake();
}

protected override void Start () {
	base.Start();
}

protected override void Update () {
	base.Update();
}

protected override void OnGUI() {
	base.OnGUI();
}

This forms the most basic framework that we need for our building, but we will be adding to this over time. Now create a new empty object in Unity and rename it Building. (GameObject->Create Empty) To this new object, let us add two cubes – we will call them Floor1 and Floor2. (GameObject->Create Other->Cube) Make sure that the Transform properties for the Building object have the following settings: Position = (0,0,0), Rotation = (0,0,0), Scale = (1,1,1). For Floor1 set them to: Position = (0,2,0), Rotation = (0,0,0), Scale = (10,4,10). And for Floor2 set them to: Position = (0,6,0), Rotation = (0,0,0), Scale = (5,4,5). You will notice that the cube which we created in the first part is now in the way. To move it, set the Position property for it’s Transform to (0,2,-20). This moves the cube 20 units down the z-axis and out of the way of our new building. The final thing we need to do with our building is to attach the Building script that we created just before to our Building object.

Building Selection

The first thing we need to add when we want to look at implementing selection of world objects is a method for handling that input. You will recall that we attached a script called UserInput to our player in an earlier part. We will make use of this script to handle responding to mouse input for our player, beyond simple control of our camera. In the Update method, after the call to CameraRotation(), add a call to MouseActivity(). Now create a private method called MouseActivity and add the following implementation to it.

private void MouseActivity() {
	if(Input.GetMouseButtonDown(0)) LeftMouseClick();
	else if(Input.GetMouseButtonDown(1)) RightMouseClick();
}

This method is simply detecting whether the left or right mouse button has been clicked and is passing on handling of that event to the appropriate method. Now we must implement both of these methods within UserInput, to complete our handling of mouse input from the user. We will use the left mouse button for selecting objects and performing actions for a selected object and then we will use the right mouse button for cancelling the selection of an object. Let us look at the left mouse click first. Add the following code into a private method LeftMouseClick.

private void LeftMouseClick() {
	if(player.hud.MouseInBounds()) {
		GameObject hitObject = FindHitObject();
		Vector3 hitPoint = FindHitPoint();
		if(hitObject && hitPoint != ResourceManager.InvalidPosition) {
			if(player.SelectedObject) player.SelectedObject.MouseClick(hitObject, hitPoint, player);
			else if(hitObject.name!="Ground") {
				WorldObject worldObject = hitObject.transform.root.GetComponent< WorldObject >();
				if(worldObject) {
					//we already know the player has no selected object
					player.SelectedObject = worldObject;
					worldObject.SetSelection(true);
				}
			}
		}
	}
}

At the moment, most of this code will fail since we have not implemented the appropriate properties and methods. But before we get onto that, I want to spend just a moment going over the logic of this piece of code. The first thing to take note of here is that we only wish to handle mouse clicks which happen inside our playing area. If the mouse is somewhere inside our HUD we will let the HUD handle mouse input. Now, since our left click is being used for selection and action, we need to determine what the user hit. There are two distinct possibilities here – either the user clicked on a world object, or they clicked on the ground (at the moment this is simply a flat plane, but this could potentially become actual terrain with hills, etc.) somewhere. We will create a pair of helper methods to determine what the point in the world was that the player clicked on, and which object (if any) they clicked on. By adding the field InvalidPosition to our ResourceManager we are able to handle clicks in unidentifiable places (for example the sky), which can then be ignored. So if we have a valid object that we clicked on (even if it was only the ground), and the point which we clicked on was valid, we want to do something with that mouse click. It is important to note that what happens with the mouse click will change depending on whether or not there is already an object selected. If there is, we need to let that object handle the mouse click. If not, we need to determine whether an object needs to be selected or not. If the object clicked on was not the ground, and it was a world object, we can select it. If not we can ignore the mouse click altogether. It turns out that this small piece of code is actually handling a lot of different scenarios.

Now it is time to add in all the pieces of code that this method requires. The first check we have here is to see whether the mouse is inside the playing area or not. Add a reference to the HUD for a player to the top of the Player class like so

public HUD hud;

and then initialize it in the Start method for the Player.

hud = GetComponentInChildren< HUD >();

This finds the HUD script that we added to our player. Now we need to add the method to the HUD which actually determines whether the mouse is inside the playing area or not. The following code should do it.

public bool MouseInBounds() {
	//Screen coordinates start in the lower-left corner of the screen
	//not the top-left of the screen like the drawing coordinates do
	Vector3 mousePos = Input.mousePosition;
	bool insideWidth = mousePos.x >= 0 && mousePos.x <= Screen.width - ORDERS_BAR_WIDTH;  	bool insideHeight = mousePos.y >= 0 && mousePos.y <= Screen.height - RESOURCE_BAR_HEIGHT;
	return insideWidth && insideHeight;
}

Note here that Unity is a little weird in that the origin for the screen coordinates is in a different place than the origin for the drawing coordinates is. This is an annoyance, but it can be worked around as long as you are aware of it. This method simply finds the current position of the mouse and determines whether it is inside the playing area or if it is over part of the HUD.

Let us now find out which object, if any, the user clicked on by adding the following method to UserInput.

private GameObject FindHitObject() {
	Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
	RaycastHit hit;
	if(Physics.Raycast(ray, out hit)) return hit.collider.gameObject;
	return null;
}

By default this method will return a null object. To find which object was hit we make use of some Unity methods. The ray is a line running from the point on screen where the user clicked into the world, from the perspective of our main camera (the only camera that we have in our world). Physics.Raycast() then traces this line and finds the first object in the world to be hit. If it finds it this is stored in the variable hit, if not the method call returns false. If we do find an object we return the parent of that object. Slightly complicated, but all of the dirty work is actually being handled by Unity for us, which is nice.

We do a very similar thing to find the point in the world on which the player clicked, as can be seen in the following method (which also needs to be added to UserInput).

private Vector3 FindHitPoint() {
	Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
	RaycastHit hit;
	if(Physics.Raycast(ray, out hit)) return hit.point;
	return ResourceManager.InvalidPosition;
}

There are a couple of places now where we are referencing the value InvalidPosition, so let’s add it to our ResourceManager now with the following code.

private static Vector3 invalidPosition = new Vector3(-99999, -99999, -99999);
public static Vector3 InvalidPosition { get { return invalidPosition; } }

By creating the private variable invalidPosition we are saving ourselves from needing to create a new Vector3 every time InvalidPosition is referenced. Remember, every little bit can add up very quickly to lots of unnecessary work which in turn slows our game down. Now it is time to add a reference to Player for the current selection. Add the following to the public variables to the top of our Player class.

public WorldObject SelectedObject { get; set; }

This is CSharp’s way of creating a hidden private variable exposed through a getter and setter method. Our player is now ready to store a world object that it has selected. With this in place, let us now create the MouseClick method for WorldObject.

public virtual void MouseClick(GameObject hitObject, Vector3 hitPoint, Player controller) {
	//only handle input if currently selected
	if(currentlySelected && hitObject && hitObject.name != "Ground") {
		WorldObject worldObject = hitObject.transform.root.GetComponent< WorldObject >();
		//clicked on another selectable object
		if(worldObject) ChangeSelection(worldObject, controller);
	}
}

This method will define the basic handling for a mouse click for a world object. Any special details are left up to any subclasses to define. You will note that we are passing in a player to this method. That player is the one who is controlling input at the moment. It may not be the player which owns this world object, which is why we need a reference to them. The basic idea here is to determine whether the object clicked on was a world object and to select it. For now this will allow us to change what has been selected, although that will not be obvious until next time (although feel free to play around with adding more buildings and trying it out). Later on we will add some more logic in here, but for now it is time to implement the ChangeSelection method that is being called here.

private void ChangeSelection(WorldObject worldObject, Player controller) {
	//this should be called by the following line, but there is an outside chance it will not
	SetSelection(false);
	if(controller.SelectedObject) controller.SelectedObject.SetSelection(false);
	controller.SelectedObject = worldObject;
	worldObject.SetSelection(true);
}

We need to make sure that the object the controlling player has selected is deselected (which should be this object, but there is a chance that it might not be). Once we have done so we can set the selection for the controlling player to be the object which was clicked on, which we then make sure is told that it has been selected.

Now that we have code in place to select a world object, it would be good to add some basic visual feedback to the user. For now we will make this as simple as displaying the name of the selected object at the top of the orders bar in our HUD. To do so, add the following code to our HUD script inside the DrawOrdersBar method. This should go between the call to GUI.Box() and GUI.EndGroup().

string selectionName = "";
if(player.SelectedObject) {
	selectionName = player.SelectedObject.objectName;
}
if(!selectionName.Equals("")) {
	GUI.Label(new Rect(0,10,ORDERS_BAR_WIDTH,SELECTION_NAME_HEIGHT), selectionName);
}

If the player has nothing selected then we do not wish to draw anything. However, if they do we wish to get the name of that object and draw that in a label at the top of our orders bar. You will need to create the constant variable for selection height at the top of HUD as follows.

private const int SELECTION_NAME_HEIGHT = 15;

The other thing you need to do is to define the settings for a Label under the skin OrdersSkin. This will allow you to set up whatever styling you want. I have used the settings shown in the screenshot below to get some nicely centred text.

Settings for Label

Settings for Label

There is just one more thing to do before running your code to test the new building selection. As you will have noticed, we are going to display the name of the building that we select. But before we can do this, we actually need to set the name for our building. To do so, click on the Building object that you created in Unity back near the start of this post. In the inspector you will see a number of variables for which you can set values (the public variables declared at the top of our Building script and our WorldObject script). Enter a value, any value, in the Object name field. Now when you run the project in Unity and select your building this name should appear at the top of your orders bar area.

The last thing left to do this week is to implement our right click method for handling input from the mouse. Remember, we are going to use this to cancel whatever selection the player currently has. The snippet of code below should do just nicely (it belongs inside UserInput remember?).

private void RightMouseClick() {
	if(player.hud.MouseInBounds() && !Input.GetKey(KeyCode.LeftAlt) && player.SelectedObject) {
		player.SelectedObject.SetSelection(false);
		player.SelectedObject = null;
	}
}

Once again we make sure that the mouse is inside the playing area. Also, remember that if we are holding down the left alt button and clicking with the right mouse button we are rotating our camera? Therefore, if that is the case we need to ignore the right mouse click. If not, we need to deselect whatever the player had selected and then set their selection to null (which indicates nothing selected).

Right, another longer post out of the way, but we have achieved lots today. We are now in a position to define specific types of buildings. And we can now select objects in our world – whether they belong to our player or not – and display the name of the currently selected object to the player. As always, the complete code from the end of this post can be found on github under the commit for Part 5.

Creating an RTS in Unity: Part IV

Update: This can now also be found at stormtek.geek.nz/rts_tutorial/part4.php, where the entire tutorial is now being hosted.

The time has now come for us to lay the foundations for interacting with objects in our world. There are 3 main types of objects with which we will want to interact with in various ways. These can broadly be classified as Unit, Buildings, and Resources. Units will be running round collecting things and attacking things. Buildings will form the majority of our bases. And Resources exist to be collected so that we can build up our base and army, enabling us to take over the world.

However, all 3 of these types of objects will share a number of things in common.

  • They all exist somewhere inside our world
  • They can all be selected
  • They all have a name
  • They can all take damage

With this in mind, let us make use of inheritance to cut down on the amount of code we need to think about and maintain.

To get us started, let’s create a new folder in our Assets directory called WorldObject. There should now be 3 folders sitting in this directory – Player, RTS, and the newly created WorldObject. Inside the WorldObject folder create another 3 folders called Building, Resource, and Unit. We will not be doing anything in these today, but we will make use of them much later in the project. Finally, add a new C# script into the WorldObject folder and call it WorldObject. This script is where we shall define our basic world object and it’s core behaviour.

World Object

First up let’s create the following public variables at the top of our class. Remember, by declaring them public we can assign values directly within Unity, which is useful for testing and for easily changing details if we need to.

public string objectName;
public Texture2D buildImage;
public int cost, sellValue, hitPoints, maxHitPoints;

We will not any of these variables right now, but it is useful to set them up anyway. Also, some of these details will not make as much sense for a Resource, but those details can simply be ignored at run time. And it may be that we actually want to implement a resource that the user must construct first anyway (which would actually blur the lines between building and resource even further).

Next we shall add a couple of protected methods. Since these are protected, rather than private, they can be accessed by any subclasses of WorldObject. However, since they are not public they are not able to be reached by any random external classes.

protected Player player;
protected string[] actions = {};
protected bool currentlySelected = false;

As with our HUD last time, we want to be able to access the player that might own a given WorldObject. We also make use of a boolean variable to determine whether someone has currently selected this object. And finally, we will allow an object to define a list of actions that it can perform. Exactly what the results of those actions are will be left up to a subclass to determine. This could be, for example, building other objects, repairing objects, researching upgrades – it all depends on what the object is and what it is able to do in the game.

In order to make sure that a subclass can have it’s own implementation of Unity methods, while still being able to run the default implementations of those methods, we need to declare them protected virtual. This is one of those ‘This is how C# / Unity require things to be’ situations. Make sure the following methods are declared after your variables, replacing any code that Unity provided for you.

protected virtual void Awake() {

}

protected virtual void Start () {
    player = transform.root.GetComponentInChildren< Player >();
}

protected virtual void Update () {

}

protected virtual void OnGUI() {
}

I can’t remember the exact difference between Awake and Start, but I do know that Start happens later after a bunch of other things have been initialized. Notice that once again we are getting the reference to the player very early on.

There are two other things that we wish to define now for our WorldObject. The first is the ability to tell it whether it has been selected or not. We will do this by adding the method SetSelection as outlined below.

public void SetSelection(bool selected) {
    currentlySelected = selected;
}

For now all this does is change the selection state to whatever was specified. The other thing to add is a pair of methods to look after actions. We will provide an accessor for all the methods that we have defined as well as a virtual method for performing an action. By declaring a method virtual we are telling C# that any subclass must provide an implementation for that method. The methods should look as below.

public string[] GetActions() {
    return actions;
}

public virtual void PerformAction(string actionToPerform) {
    //it is up to children with specific actions to determine what to do with each of those actions
}

And that wraps up the framework for our WorldObject. We have nothing to add this to just yet, but we will get onto that in the next couple of parts as we begin to define buildings and units. A shorter entry this time, but it provides us a good platform to build upon. Once again, the complete code for this is up on github under Part 4.

Creating an RTS in Unity: Part III

Update: This can now also be found at stormtek.geek.nz/rts_tutorial/part3.php, where the entire tutorial is now being hosted.

First up, I apologize for the long delay between posts for anyone who has been following. Life got a little bit crazy and then I got distracted. I hope to keep things regular from now until the end.

We now have the basic framework up for accepting input from our players, and we are using this to move the camera around our world. Now it is time to develop the framework we will use for presenting useful information back to the user – through an HUD (heads-up-display). This will be a very simple looking affair, although you are free to develop your own pretty graphics and make it look a lot more professional. For the moment, I am more interested in the functionality than the precise look anyway.

You may be wondering why we are focusing on this so early in our development … Well, the reality is that an easy way to notice some of our changes working is by integrating the visual feedback for the user straightaway. So lets get our framing up now, and then when we want to show the user things happening, we already have a nice clean way to do so.

The core of our HUD is going to be a top bar, where we will display current resources, and a side bar, which we will use to display the currently selected unit / building along with any options it has. At a later date we will also integrate things like a selection box for that selected unit / building, a rally point for buildings, etc.

Graphics

First up we will need to create some graphics to use in our HUD. These can be made in any image editor you like. Personally, I use Paint.NET – it is free, easy to use, and I find it quick and easy to whip up the simple graphics we will be using during this tutorial. All of the graphics I am using are up on github, but I encourage you to make your own. I will be using the png format, mainly for it’s support for transparency – also Unity has no problems importing these that I have encountered.

One thing to note about Unity with textures: by default it only supports sizes that are powers of 2 (16×16, 128×32, etc). If you try to import a texture that is not, it will scale it to fit – resulting in weird distortions. I have heard rumours that you might be able to change this, but have not had (or taken) the time to track this down. Feel free to do so in your own time. For this tutorial we will use the default, but some judicious use of transparency will allow us to cheat at times and achieve varied texture sizes where we want them anyway.

Before we create any images, let’s make a folder to store them in. Inside the Player folder we created last time add a new folder called HUD. We will use this folder to store all of the elements that end up going into our HUD (and this will grow quite large over time). Inside this folder create a folder called Images, which is where we will store the images we are about to make.

For now, we need to images to serve as backgrounds for our top resource bar and our side orders bar. I am just going for a flat background colour on each of these for now – nothing fancy, simply a good way to define various regions on the screen. This means that we can actually make them a mere 16×16 pixels, scale in Unity, and get the same effect as if they were sized to fit our screen. This saves on our final memory footprint. One image like this won’t make much difference, but if we make a habit of doing so wherever possible we reduce the chances of having issues later on. Create the two images now, calling them resourceBar and ordersBar. Make them different colours so we can easily distinguish the two, but try and get two colours which will show up both black and white text nice and clearly.

When saving your images, you have two options for getting them into Unity. The easiest is to save the files directly into the folder you created within Unity, which means navigating to that folder from the SaveAs menu in your image editor. The other option is to save them somewhere else and then drag them into Unity, making sure to drop them into the appropriate folder there. Either option works, it is your choice as to which is easier for you. From now on, I will mention where to put the files and leave it up to you to get them there in whatever way you see fit.

GUI Skins

Now that we have a couple of graphics, it is time to figure out how to put them onto the screen. Unity presents us with GUI skins, which are an easy way to customise the look and feel of a number of common graphical elements used to build interfaces. We will make use of a number of these in our HUD to customize certain areas. Before we make any of those, though, lets first create a new folder to store them in. I’m sure that this routine is starting to get a little familiar by now. Inside your HUD folder create a new folder called Skins.

Add two new GUI Skins to your newly created folder. Call them ResourceSkin and OrdersSkin for consistency. Now that we have some skins, it is time to begin to customize them. Select ResourceSkin first. In the Inspector you should see a whole list of GUI elements that you have the ability to customize. Click on Box, since this is the element we will use to provide the background for our resource bar. You should now see a collection of settings that you can fiddle with, including those for various states the box will find itself in. The only state we are interested in changing at the moment is Normal, so click on that. As you can see, we can customize both the background image and the text colour for our box. The only thing we want to change is the background, to do so drag the texture resourceBar onto the background field. The settings for your ResourceSkin should look similar to the image of mine below.

ResourceSkin Settings

ResourceSkin Settings

Now do the same thing for OrdersSkin, remembering to use the texture ordersBar rather than resourceBar for the background. These skins will receive further modifications later on as we add more things, but that will do us for now.

HUD Code

Nice work getting to this point. To recap, we have some images to display and some skins to display them with. What we need now is a way to attach these to our player. To do this we need to create an HUD object and attach a script so that we can interact with it. Go ahead and create an empty object and rename it to HUD. Then, in your HUD folder, create a new C# script, also called HUD, and attach this script to you HUD object. We are now ready to begin writing some code to do things with our HUD.

The first thing we need to add is a reference to the skins we have created so that we can reference them when it comes time to draw elements on the screen. Add the following line to the top of HUD.cs.

public GUISkin resourceSkin, ordersSkin;

In Unity we can now attach our two skins to the appropriate values in our HUD object. The other thing we want to add to the top of our class are some constant values for things like width and height which we will use to perform calculations etc. later on. Add the following constants for now (more will be added later on when we flesh out our HUD a bit more).

private const int ORDERS_BAR_WIDTH = 150, RESOURCE_BAR_HEIGHT = 40;

Note the use of all capitals to indicate that these are constants. This is not required, it is just a style I have adopted to be able to quickly identify when a constant value is being used in my code. I never have to think about whether it is a constant or not, that is obvious from the way the variable name has been constructed.

The other thing we want to have is a reference to the player that this HUD will belong to. Before we do this, let’s first take our HUD object and attach it to our player. We can do this by dragging our HUD object onto our Player object (in the Hierarchy view). This will make the Player object the parent of the HUD. In the Hierarchy view in Unity you should now see a small arrow to the left of the Player object which, when expanded, should reveal the HUD object. Now we can add a reference to a player at the top of HUD.cs like so:

private Player player;

Finally, we need to initialize our reference to the player when the HUD is created. Add the following code to the Start() method that was added for you by Unity when you created the script.

player = transform.root.GetComponent<Player>();

This tells Unity that we want the root object for the HUD, in this case our Player object, and that we then want a reference to the Player.cs script belonging to that root object. This now allows our HUD to talk to the Player that owns it whenever it wants. By creating this reference now we are saving computation time later on (which will actually be at least once every update, so multiple times a second).

We don’t actually need to use the method Update() which Unity provided for us, but we do need to make use of the OnGUI() method that they provide – this is the one called each frame to handle any drawing our script is responsible for. Therefore, let’s rename Update() to OnGUI() so that we can add some code to it.

We only wish to actually draw anything if the controlling player is a human, since it makes no sense to display pretty pictures to any computer players we might have present. We will also split our draw logic into separate methods to give us cleaner code that is easier to both read and maintain. Add the following code into the newly named OnGUI() method

if(player.human) {
	DrawOrdersBar();
	DrawResourceBar();
}

Now we need to create these methods, otherwise nothing will actually run. First let us look at drawing the orders bar. Add the following code to DrawOrdersBar()

private void DrawOrdersBar() {
	GUI.skin = ordersSkin;
	GUI.BeginGroup(new Rect(Screen.width-ORDERS_BAR_WIDTH,RESOURCE_BAR_HEIGHT,ORDERS_BAR_WIDTH,Screen.height-RESOURCE_BAR_HEIGHT));
	GUI.Box(new Rect(0,0,ORDERS_BAR_WIDTH,Screen.height-RESOURCE_BAR_HEIGHT),"");
	GUI.EndGroup();
}

There are a couple of important things to note here. The Unity GUI object will always use the last GUISkin that was activated for it. This allows us to easily swap between different skins when drawing different parts of our application, which is great. It does mean, however, that if we are expecting to use a particular skin for some drawing that we need to deliberately change to that skin (unless we can GUARANTEE that it will be set). GUI.BeginGroup(rectangle) defines a rectangular area of the screen in which we will be drawing. This is declared in Screen co-ordinates, so the actual position (in pixels) on the screen where that rectangle will sit. Here we are defining a rectangle on the far right of the screen that starts at the bottom of where our resource bar will sit. If you begin a group, you must remember to end it or things will break. Finally, we actually draw our coloured background with the call to GUI.Box(rectangle, string). Once again this defines a rectangular area to draw (in this case to fill with the background image that we set in our skin). The important thing to note here is that the co-ordinates are for within the group that we are in. So (0,0) here is the top corner of the group that we began, NOT the top left corner of our screen. The empty string at the end of the method call simply means we do not wish to display any text within the box we have just drawn.

Finally, lets look at drawing our basic resource bar. Add the following code to DrawResourceBar()

private void DrawResourcesBar() {
	GUI.skin = resourceSkin;
	GUI.BeginGroup(new Rect(0,0,Screen.width,RESOURCE_BAR_HEIGHT));
	GUI.Box(new Rect(0,0,Screen.width,RESOURCE_BAR_HEIGHT),"");
	GUI.EndGroup();
}

As you can see, this code is almost identical. All that we have changed is the position and dimensions of our rectangles and the skin we are using while doing any drawing. If you save all your changes and go and play your scene in Unity you should see something similar to the image I got below.

Basic HUD in action

Basic HUD in action

Right, I think this post has gone on long enough for today. We may not have done a lot of coding this time round, but we have actually put in place quite a lot of important architecture for a fully-functional HUD. We will build on this in later posts as we add more abilities to the player, and to our game in general.

A full copy of the code for the end of this post is up on github under the commit for Part 3.