Creating an RTS in Unity: Part XV

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

From what we have done so far we now have the broad framework for a real time strategy game in place. We can control units and buildings, create new units and buildings, and destroy enemy units and buildings. We can also collect resources to fund the creation of those new units and buildings. Sure, there are plenty of gaps that need filling. Pathfinding, collisions, an AI to control computer players, an AI to control individual units (and defensive buildings), the list goes on. At a later date I may come back and fill in some of those details, but for now we will leave that part of the game there.

However, there are more things that we need to put into place before we have a complete package. At the moment we are stuck with a single static map. Any time a player wants to play they must start at the same place. This might work well for arcade games, but it is not what we expect when we come to play a real time strategy game. Typically these have a much larger scope than the simple arcade game does.

The last few parts of this tutorial are therefore going to be about adding the things we need to flesh out the UI and overall user experience. This will involve adding in a menu system, the ability to save games and then to reload them, user accounts so that we can personalize the experience a little bit, and then at the end we will implement a basic single-player campaign.

Simple menu system

To get things started we will implement a basic pause menu that can be triggered from within our game. Initially this will not do much, but it will lay the platform for more complicated menu interactions later on. We want this to be triggered from user input, so we need to adjust the Update() method in UserInput.cs. Add

if(Input.GetKeyDown(KeyCode.Escape)) OpenPauseMenu();

immediately before the call to MoveCamera(). Here we are specifying that when the escape key is hit we want to open the pause menu. That method needs to be defined before we can continue.

private void OpenPauseMenu() {
	Time.timeScale = 0.0f;
	GetComponentInChildren< PauseMenu >().enabled = true;
	GetComponent< UserInput >().enabled = false;
	Screen.showCursor = true;
	ResourceManager.MenuOpen = true;
}

The first line tells Unity to basically stop time. If we do not do this then actions initiated before the menu was opened carry on – so Units keep moving, etc. In the second line we are enabling the script that will handle the pause menu itself. Then we disable the user input script, since we want the Player to be interacting with the menu, rather than the game. The fourth line re-enables the system cursor, so that we do not have to implement a custom cursor for use within menus (although you could do so if you wished). Finally we are letting our ResourceManager know that a menu is open at the moment. This gives us an easy way for code to check whether a menu is open or not. To be able to set this value we need to add it to ResourceManager.cs.

public static bool MenuOpen { get; set; }

Note: this is the CSharp way of setting up a public getter and setter on a private variable.

The next important step is to create the script that will handle the pause menu. We will create a new folder to hold all menu related things (since we will be building up quite a bit over the next few posts). Inside the main assets folder create a new folder called Menu. Inside this folder create a new folder call Scripts and then inside this folder create a new CSharp script called PauseMenu.cs. Set the initial code to the code that follows.

using UnityEngine;
using RTS;

public class PauseMenu : MonoBehaviour {

	public GUISkin mySkin;
	public Texture2D header;

	private Player player;
	private string[] buttons = {"Resume", "Exit Game"};

	void Start () {
		player = transform.root.GetComponent< Player >();
	}

	void Update () {
		if(Input.GetKeyDown(KeyCode.Escape)) Resume();
	}
}

We are defining a couple of public variables that we can set within Unity – a GUISkin that we can use to adjust the look and feel of the menu quickly and easily, and a header image that we want to display at the top of the menu. We then have a reference to the player this menu belongs to (initialised in the usual way in the Start() method), and a list of the buttons we want to display (represented by the strings we want to display on each button). In the Update() method we are also defining that we want to use the escape key as a shortcut to resume gameplay. Let us now define the Resume() method.

private void Resume() {
	Time.timeScale = 1.0f;
	GetComponent< PauseMenu >().enabled = false;
	if(player) player.GetComponent< UserInput >().enabled = true;
	Screen.showCursor = false;
	ResourceManager.MenuOpen = false;
}

This method is basically the inverse of the method that we used to open the pause menu in the first play. We restore the time scale back to 1, disable the pause menu, find and re-enable the UserInput for the correct player, hide the system cursor, and tell the ResourceManager that the menu has now been closed.

The actual drawing of the menu will be handled by the OnGUI() method provided by Unity, which we need to implement now.

void OnGUI() {
	GUI.skin = mySkin;

	float groupLeft = Screen.width / 2 - ResourceManager.MenuWidth / 2;
	float groupTop = Screen.height / 2 - ResourceManager.PauseMenuHeight / 2;
	GUI.BeginGroup(new Rect(groupLeft, groupTop, ResourceManager.MenuWidth, ResourceManager.PauseMenuHeight));

	//background box
	GUI.Box(new Rect(0, 0, ResourceManager.MenuWidth, ResourceManager.PauseMenuHeight), "");
	//header image
	GUI.DrawTexture(new Rect(ResourceManager.Padding, ResourceManager.Padding, ResourceManager.HeaderWidth, ResourceManager.HeaderHeight), header);

	//menu buttons
	float leftPos = ResourceManager.MenuWidth / 2 - ResourceManager.ButtonWidth / 2;
	float topPos = 2 * ResourceManager.Padding + header.height;
	for(int i=0; i<buttons.Length; i++) {
  		if(i > 0) topPos += ResourceManager.ButtonHeight + ResourceManager.Padding;
		if(GUI.Button(new Rect(leftPos, topPos, ResourceManager.ButtonWidth, ResourceManager.ButtonHeight), buttons[i])) {
			switch(buttons[i]) {
				case "Resume": Resume(); break;
				case "Exit Game": ExitGame(); break;
				default: break;
			}
		}
	}

	GUI.EndGroup();
}

There are several important sections here where work is being done. First off we want to set the GUISkin being used to be the one attached to the pause menu. Next we want to define the part of the screen that we will be draw in, which will be a rectangle in the centre of the screen defined by GUI.BeginGroup(). Then we draw a background rectangle for the menu followed by adding the header at the top of this box. Finally we want to draw a vertical list of buttons below the header. Note: Unity includes the click handler for a button in with the definition of where to draw the button. We can determine which button was clicked by evaluating the text of that button. Once we know this we can call the appropriate method to handle the click for that button.

There is an extra method that this code requires us to define now

private void ExitGame() {
	Application.Quit();
}

which simply tells the Unity application to quit.

You will notice that there are lots of references to values in ResourceManager being used in the OnGUI() method. This is so that we can separate the details of layout from the logic of the creation of the menu. In order for things to work we need to define all of these inside ResourceManager.cs.

private static float buttonHeight = 40;
private static float headerHeight = 32, headerWidth = 256;
private static float textHeight = 25, padding = 10;
public static float PauseMenuHeight { get { return headerHeight + 2 * buttonHeight + 4 * padding; } }
public static float MenuWidth { get { return headerWidth + 2 * padding; } }
public static float ButtonHeight { get { return buttonHeight; } }
public static float ButtonWidth { get { return (MenuWidth - 3 * padding) / 2; } }
public static float HeaderHeight { get { return headerHeight; } }
public static float HeaderWidth { get { return headerWidth; } }
public static float TextHeight { get { return textHeight; } }
public static float Padding { get { return padding; } }

Here we have defined some private variables that form the basis of our layout. We use these, in conjunction with some CSharp getters, to define the actual values that are to be returned.

There are just a couple of things to do before we can run our project and display the menu. Inside the Menu folder create two new folders called Images and Skins. Inside the skins folder create a new GUISkin called MenuSkin. Inside the Images folder we want to add a header image to display at the top of the menu. This could be a logo, something that provides a bit more theming for your game. I have gone for a simple placeholder like the image below.

Menu header image

Menu Header Image

We now have all of the components for opening our pause menu, but they need to be attached somewhere logical before we can use them. What we want to do is to add them to the HUD object belonging to each of our Players. This makes sense, since they are part of the display being presented to the Player. However, we do not want to have to attach one to each of the players in the game. The way to handle this is to add them to the player prefab that we created. In order to do this first add a new instance of that prefab into your game. We will use this instance to edit and update the prefab (since the other players already in the game already have a lot of other things added that we do not want to be part of a new player). Now attach the PauseMenu script to the HUD of the new Player. Add the header image and the MenuSkin to the appropriate fields of the PauseMenu you just attached. Once you have done this, make sure to disable the PauseMenu script, since the default state for our game is ‘playing’ rather than ‘paused’. You can do this by unchecking the checkbox next to the title of the script. To save these changes back to the prefab drag this new Player object onto the Player prefab in the Player folder. You should be prompted whether you want to do this. When this prompt shows up you need to choose the ‘save over’ option, since we want to replace the existing prefab with the updated prefab. These changes should now be present in all of the players already in our game.

The main customization I have done on MenuSkin is to change the background image that is used on the box element. I set the background image for a normal box to the image below.

Menu background

Menu Background

You should now be able to run your game and open the newly created pause menu by using the escape key. Clicking on resume, or hitting escape, should return you to your game. Once you have built your game clicking exit should close your game (this does not work for some reason when running your game from within Unity).

And that wraps things up for this week. Our game now contains a simple pause menu. As I mentioned earlier, we will build on this over the next couple of posts. All of the code for this post can be found on github under the commit for Part 15.

Advertisements

3 thoughts on “Creating an RTS in Unity: Part XV

  1. wendek says:

    Nice post, I like that you are doing more “fundations” posts rather than, say, different units and stuff which anyone can do rather easily.
    I got a question though : will you cover the topic of tooltips ? You know, temporary labels that appear when you mouse hover a button (to show a unit’s cost, for instance) and dissappear afterwards.

    Thanks again for this tutorial.

    • I haven’t thought about tooltips yet to be honest. I have the feeling that they would not be too hard to add though, since we are already getting a lot of detail through the mouse hover. It would simply be a matter of storing what we want from that in HUD and the setting a boolean flag as to whether to draw a tooltip or not. As part of the detail stored we would want to store the mouse position too. I would probably create a simple tooltip class that has a position and a WorldObject. When we detect a mouseover of something we would create a tooltip with the mouse position and the WorldObject we are over. We would then want to set the current tooltip for the HUD to this tooltip. If we gave a tooltip a ‘time to live’ that gets decremented each update then we could have tooltips that only show for a given period of time. Inside the draw method for the HUD we would then want a check something like if(currentTooltip) currentTooltip.draw() to make sure we actually display the tooltip. If we make this the last thing in the draw method for the HUD then we guarantee that it will be drawn on top of everything else.

      I hope that helps you get something working. At some point I may work this out properly and add it in as part of a post, but I can’t guarantee anything at the moment.

      • wendek says:

        Thanks for the answer. I guess using Update to count a finite number of frames to draw it could work and achieve a decent result. Anwyay right now I’m not too worried about it since I’m still implementing core mechanics of my game but I know I’ll have to do that at one point if I want the game to be playable. (especially since I’m using a ton more different resources than you do in the tutorial, so it’s not like the player will “lose more money than expected”, instead he will not be able to create the unit at all because for instance, he thought it only needed wood and stone, but iron was needed as well)

        Looking forward to the next post.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s