Creating an RTS in Unity: Part II

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

Last time we installed Unity and got a basic scene up and running. Now it is time to begin to craft our game. Today we will look at creating a player to interact with and using that player to move the camera around. This will also involve our first brush with coding using C# scripting.

Players

We are creating a strategy game that centres around players building up bases and armies in the hopes of map-wide domination. This means that almost everything we do will be through players, whether human or computer controlled. For the purposes of this tutorial we will assume that there will be one human player and one or more computer players. All user input, as well as display back to the user, will be handled by the human player.

Therefore, before we can do anything useful, we must create a player that we can begin to interact with. Create an empty object that will serve as a wrapper for everything related to our player and rename it Player. (GameObject -> Create Empty)

Since we wish to interact with a player, we are going to need some scripts to define what the player can and cannot do. We will use two scripts to do this – one for the player, and one for handling user input for the player. Firstly, to keep things organized as the project grows, lets create a folder inside our assets directory and call it Player. Anything to do with a player will be put inside this folder. Now create two C# scripts inside this folder – call them Player and UserInput.

Drag both of these newly created scripts onto the empty object we created just before to add them to our player object. This will allow us to begin defining behaviour etc. for our player. Well done, you now have the framework for interacting with a player in your game.

Player Details

For now, we don’t need to define much for our new player. In fact, all we want is a way to identify different players, and to determine whether a player is human or not. To do this, we simply need to add two variables to our class.

Open Player.cs in your editor of choice. The only thing that really matters here is the ability to edit the file. Add the following two variables to the top of the class.

public string username;
public bool human;

Once done your Player.cs should like the following:

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour {

	public string username;
	public bool human;

	// Use this for initialization
	void Start () {

	}

	// Update is called once per frame
	void Update () {

	}
}

We will leave Start() and Update() empty for now, but as our player gets becomes complex later on some extra things will be added. Now return to Unity, select Player, you should see in the Inspector that you can enter a name for your player and define whether it is a human player or not. Enter your name as the username and set your player to be human. The Inspector for your player should look similar to the image below.

Initial Player Settings

Initial player settings

Resource Manager

There is one more thing to add before we get started on the camera. In a number of places we are going to encounter the desire to use some constant values – for things like camera pan, height of the camera off the ground, width of buttons etc. We are also going to want to be able to have easy access to some global variables (there is a time and a place for these) and we will want all parts of the game to be able to access them – eg current map name. Therefore, we are going to create a static class to handle all of this. It shall become our resource manager.

Now, to handle things that are not part of Unity, we are going to create our own namespace – RTS. This will allow us to give any class access to our code by simply adding

using RTS;

to the top of the file. We will store all of our namespaced files in a folder called RTS. Create this folder inside your assests directory now. Inside this folder create a new C# script, rename it ResourceManager, and open it for editing.

There are a number of things we need to do straight away to make this script useful to us. First off, we can get rid of the automatically generated Unity methods, since we will never be using them. Also, make sure that the class no longer inherits from MonoBehaviour, since none of that functionality makes sense, and change it to a static class. Finally, we need to add the namespace declaration around the class definition. The resulting file should look like this:

using UnityEngine;
using System.Collections;

namespace RTS {
	public static class ResourceManager {

	}
}

Note that we still want to use UnityEngine, since that gives us access to all of the Unity framework. Remember, also, that any variables or methods we add to ResourceManager will also need to be declared as static. We will add some details further on as we need them, but for now the basic frame is there for us to build on.

Camera Input

Now it is time to do our first interesting thing for this tutorial – handle user input for the camera. Once this has been sorted, we should not need to touch the camera again, so let’s get it right first time. Open UserInput.cs for editing. As a start lets reference our ResourceManager by adding

using RTS;

to the top of the file. Next up we want a reference to the player for which we are actually handling the input for. Add

private Player player;

to the top of your class. This is a private variable, since we only want this class to be able to interact with it. We will initialize this player when the class is created, since it will never be changing. To do so, add the following line to the Start() method:

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

This code basically tells Unity to go to the root of the object that this script belongs to (in this case the empty object we created and called Player) and then find the Player component that we added (referenced by the Player.cs script we created just before). Now we can interact with our player directly whenever we want to.

Inside the Update() method add

if(player.human) {
	MoveCamera();
	RotateCamera();
}

Note here that we are only going to handle input for a human player. This means that our update method will do nothing for all computer players, which will help reduce the amount of work that we are doing each update – by keeping this low to begin with we can help to keep our framerate down, making sure that our game runs as smoothly as possible.

We now need to define the two methods that we are calling from inside our Update() method. MoveCamera() will handle moving the camera around our world. Since we are working in 3 dimensions, it will also be useful to be able to rotate the camera to look behind us, which is what RotateCamera() will handle. Create these now, leaving them empty, and making sure that they are both private to make sure that only this class can interact with them.

private void MoveCamera() {

}

private void RotateCamera() {

}

Before we fill in either of those methods, lets just add some speed modifiers to our ResourceManager. For now we will set them to a reasonable speed for us. At a later date these could be updated via an options menu to allow players to customize the environment to best suit their tast. The following values should do it …

public static float ScrollSpeed { get { return 25; } }
public static float RotateSpeed { get { return 100; } }

Camera Movement

Now, let’s make the camera move about the map. We will initiate this whenever the mouse moves to the edges of the screen. To make it a little more forgiving, let’s define an area a few pixels in from the edge of the screen where scrolling will happen. We will do this by adding the following

public static int ScrollWidth { get { return 15; } }

to our ResourceManager. This will allow us to get the width of our scrollable area, but not to change it – which is just how we want it.

The first thing to do inside MoveCamera() is to define the current mouse position and a new vector for our movement, like so:

float xpos = Input.mousePosition.x;
float ypos = Input.mousePosition.y;
Vector3 movement = new Vector3(0,0,0);

Now it is time to handle the panning of the camera around the map. We are going to use the y-axis for height, so the x-axis and the z-axis are to be used for direction in our world. In terms of user input we will use the x-axis for the horizontal screen movement and the z-axis for the vertical screen movement. We use this

//horizontal camera movement
if(xpos >= 0 && xpos < ResourceManager.ScrollWidth) {
	movement.x -= ResourceManager.ScrollSpeed;
} else if(xpos <= Screen.width && xpos > Screen.width - ResourceManager.ScrollWidth) {
	movement.x += ResourceManager.ScrollSpeed;
}

//vertical camera movement
if(ypos >= 0 && ypos < ResourceManager.ScrollWidth) {
	movement.z -= ResourceManager.ScrollSpeed;
} else if(ypos <= Screen.height && ypos > Screen.height - ResourceManager.ScrollWidth) {
	movement.z += ResourceManager.ScrollSpeed;
}

to add movement in the appropriate axis when the mouse is inside the region that we defined for movement – in this case the first 15 pixels in from each edge of the screen.

Now, as I mentioned before, our camera is sitting inside a 3-dimensional world. This means that it is more than likely that our camera has been rotated in some interesting fashion. So if we simply add our movement vector to the position of the camera it will appear to do weird things … (feel free to comment this next bit out at some point and see what I mean). What we want is for our camera to move forwards in the direction that it is pointing. Thankfully, Unity has a method attached to the camera that will do this for us.

//make sure movement is in the direction the camera is pointing
//but ignore the vertical tilt of the camera to get sensible scrolling
movement = Camera.mainCamera.transform.TransformDirection(movement);
movement.y = 0;

That does the trick quite nicely. Note that we are changing the vertical movement back to 0 to ensure that the camera pans around nicely but does not have weird up and down movement at the same time. To add vertical movement we use the scroll wheel on the mouse like so

//away from ground movement
movement.y -= ResourceManager.ScrollSpeed * Input.GetAxis("Mouse ScrollWheel");

and now we are ready to add our calculated movement to the camera. In order to do so, we must first calculate the destination of our camera, which is easy enough to do.

//calculate desired camera position based on received input
Vector3 origin = Camera.mainCamera.transform.position;
Vector3 destination = origin;
destination.x += movement.x;
destination.y += movement.y;
destination.z += movement.z;

Now that we know where we want the camera to go, there is one final check we wish to make. To keep things sensible we should make sure that the user cannot move the camera down through the ground, and also that they cannot move it so far up that nothing can be seen clearly anymore. Add the following limits to our ResourceManager

public static float MinCameraHeight { get { return 10; } }
public static float MaxCameraHeight { get { return 40; } }

for the minimum and maximum heights allowed for our camera. This will work fine for our world, since the ground will always be flat. However, if you add terrain at a later date you will need to rethink how this is being calculated. Now we can add the following check to MoveCamera() to keep the camera inside the limits that we have set.

//limit away from ground movement to be between a minimum and maximum distance
if(destination.y > ResourceManager.MaxCameraHeight) {
	destination.y = ResourceManager.MaxCameraHeight;
} else if(destination.y < ResourceManager.MinCameraHeight) {
	destination.y = ResourceManager.MinCameraHeight;
}

At last we are ready to add the desired movement to the camera itself, which we will only do so if the camera has moved. Unity nicely provides us with a static reference to the main camera which we will make use of.

//if a change in position is detected perform the necessary update
if(destination != origin) {
	Camera.mainCamera.transform.position = Vector3.MoveTowards(origin, destination, Time.deltaTime * ResourceManager.ScrollSpeed);
}

If you now go into Unity and hit play, you should be able to move your camera around the map with no trouble at all.

Camera Rotation

The final thing to cover in this post is rotating the camera. We will do this when the user holds down the right mouse button. However, we will also be using the right mouse click to deselect items later on, so we need to throw a modifier key into the mix to prevent confusion. We will allow the player to rotate the camera, but only if they are pressing the ALT key while holding down the right mouse button. This will prevent any accidental movement of the camera. Add the following code to the RotateCamera() method.

Vector3 origin = Camera.mainCamera.transform.eulerAngles;
Vector3 destination = origin;

//detect rotation amount if ALT is being held and the Right mouse button is down
if((Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) && Input.GetMouseButton(1)) {
	destination.x -= Input.GetAxis("Mouse Y") * ResourceManager.RotateAmount;
	destination.y += Input.GetAxis("Mouse X") * ResourceManager.RotateAmount;
}

//if a change in position is detected perform the necessary update
if(destination != origin) {
	Camera.mainCamera.transform.eulerAngles = Vector3.MoveTowards(origin, destination, Time.deltaTime * ResourceManager.RotateSpeed);
}

Take note that we are allowing rotation around the camera’s vertical axis as well as tilting up and down to change the angle with which we are viewing the ground. It turns out that it is a lot easier to rotate the camera than it is to move it around sensibly.

Congratulations! You now have a working real-time strategy style camera that can comfortably move around your world, handling 3 dimensions comfortably. Feel free to take it for a spin and get a feel for how we will be navigating the world we are building up.

For completion this week, here is the completed code for our UserInput and for our ResourceManager. Remember, all of the code can be found on my github account. Changing to the commit for the part you are working through will give the state of the code at the end of that part (in this case it is part 2). From now on I will generally not post full code here – just the lines we are adding (plus maybe a little extra for context to make sure you stay on track).

UserInput
using UnityEngine;
using System.Collections;
using RTS;

public class UserInput : MonoBehaviour {

	private Player player;

	// Use this for initialization
	void Start () {
		player = transform.root.GetComponent< Player >();
	}

	// Update is called once per frame
	void Update () {
		if(player.human) {
			MoveCamera();
			RotateCamera();
		}
	}

	private void MoveCamera() {
		float xpos = Input.mousePosition.x;
		float ypos = Input.mousePosition.y;
		Vector3 movement = new Vector3(0,0,0);

		//horizontal camera movement
		if(xpos >= 0 && xpos < ResourceManager.ScrollWidth) {
			movement.x -= ResourceManager.ScrollSpeed;
		} else if(xpos <= Screen.width && xpos > Screen.width - ResourceManager.ScrollWidth) {
			movement.x += ResourceManager.ScrollSpeed;
		}

		//vertical camera movement
		if(ypos >= 0 && ypos < ResourceManager.ScrollWidth) {
			movement.z -= ResourceManager.ScrollSpeed;
		} else if(ypos <= Screen.height && ypos > Screen.height - ResourceManager.ScrollWidth) {
			movement.z += ResourceManager.ScrollSpeed;
		}

		//make sure movement is in the direction the camera is pointing
		//but ignore the vertical tilt of the camera to get sensible scrolling
		movement = Camera.mainCamera.transform.TransformDirection(movement);
		movement.y = 0;

		//away from ground movement
		movement.y -= ResourceManager.ScrollSpeed * Input.GetAxis("Mouse ScrollWheel");

		//calculate desired camera position based on received input
		Vector3 origin = Camera.mainCamera.transform.position;
		Vector3 destination = origin;
		destination.x += movement.x;
		destination.y += movement.y;
		destination.z += movement.z;

		//limit away from ground movement to be between a minimum and maximum distance
		if(destination.y > ResourceManager.MaxCameraHeight) {
			destination.y = ResourceManager.MaxCameraHeight;
		} else if(destination.y < ResourceManager.MinCameraHeight) {
			destination.y = ResourceManager.MinCameraHeight;
		}

		//if a change in position is detected perform the necessary update
		if(destination != origin) {
			Camera.mainCamera.transform.position = Vector3.MoveTowards(origin, destination, Time.deltaTime * ResourceManager.ScrollSpeed);
		}
	}

	private void RotateCamera() {
		Vector3 origin = Camera.mainCamera.transform.eulerAngles;
		Vector3 destination = origin;

		//detect rotation amount if ALT is being held and the Right mouse button is down
		if((Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) && Input.GetMouseButton(1)) {
			destination.x -= Input.GetAxis("Mouse Y") * ResourceManager.RotateAmount;
			destination.y += Input.GetAxis("Mouse X") * ResourceManager.RotateAmount;
		}

		//if a change in position is detected perform the necessary update
		if(destination != origin) {
			Camera.mainCamera.transform.eulerAngles = Vector3.MoveTowards(origin, destination, Time.deltaTime * ResourceManager.RotateSpeed);
		}
	}
}
ResourceManager
using UnityEngine;
using System.Collections;

namespace RTS {
	public static class ResourceManager {
		public static int ScrollWidth { get { return 15; } }
		public static float ScrollSpeed { get { return 25; } }
		public static float RotateAmount { get { return 10; } }
		public static float RotateSpeed { get { return 100; } }
		public static float MinCameraHeight { get { return 10; } }
		public static float MaxCameraHeight { get { return 40; } }
	}
}
Advertisements

Creating an RTS in Unity: Part I

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

This forms the beginning of a series of tutorials on building an RTS in Unity 3D. I will be using the free version of Unity 4.0 for Windows with C# scripting. There will be a lot of programming, so it is assumed that you have an understanding of basic programming principles and at least a basic familiarity with either C# or a similar language (e.g. Java, C++). I will be covering why we are doing things a certain way, but I won’t be stopping to discuss any language stuff in detail. It is also assumed that you have a basic knowledge of Unity – as much as is covered in the beginners tutorials anyway. Earlier posts will include a bit more detail on Unity (including some screenshots), but as we progress this will diminish.

All of the source code will be freely available on my github account. I will make a commit for each post, so you will be able to step through the revision history alongside each post. This will include the Unity project as well as the source code.

Objectives

The goal is to produce a  game that includes all of the core mechanics needed to play a modern real-time strategy game. The core features we will be aiming for are:

  • Multiple players, one of which will be controlled by the player
  • Selection of game objects – whether units, buildings, or resources
  • The ability to issue commands to any units / buildings that the player controls
  • The ability to create new units from specified buildings
  • The ability for specified units to be able to construct new buildings
  • The ability to collect resources, thus enabling a simple economy
  • An interactive display that shows the player
    • What resources they currently have
    • What game object they have selected
    • What options a selected game object has
  • A functional menu system
  • The ability to save and load games

Obviously this is no light undertaking. Just looking at that list is daunting … but it is entirely possible. I will attempt to keep things as short as possible, but I do not wish to skimp on any of the necessary details either. As we progress I will also attempt to lay out my reasoning for certain design decisions. So, it is time to begin …

Setup

The first thing to do is download and install Unity 3D, if you have not already done so, which can be found here. I do not believe it matters which platform you use, but I am using Windows 7. If you have any trouble using another platform, I apologize in advance. Once you have installed Unity, tweak the layout of the interface to fit your preferences. The areas that are important to be able to see are

  • Project view
  • Hierarchy
  • Inspector
  • Scene view
  • Game view

It can also be useful to see the console output as well, though we will not be using it in these tutorials. But when developing it is useful to be able to print out debugging information to the console while you are play-testing.

Now that you have installed Unity, it is time to create a new project for our game. (File -> New Project …) I will call it RTS Tutorial, but you are free to give it any name you choose. By default (on Windows, at least) Unity will put this under MyDocuments, but it is easy enough to change this when you are creating the project. Do not worry about adding any extra packages at this stage.

When we create a new project Unity presents us with an empty scene that contains a camera. The first thing we want to do is to save this scene. (File -> Save Scene) Let’s call this scene Map since we will be using it to play around with a map for most of the project. Once you have hit save you should see the scene file called map now located in the Assets folder for your project.

First Objects

The last thing I want to cover this time is actually being able to see something happening, otherwise most of this post is just boring overview. Let’s add some ground for things to sit on and a cube to give us a point of reference.

For the ground we will create a plane and rename it Ground. (Game Object -> Create Other -> Plane) Let’s set it’s position to (0,0,0) and it’s scale to (100,1,100). This will make the centre of our ground to be the origin and it will extend 100 units in either direction. Note that we are using the y-axis for height off the ground. This will be the case for everything in our world.

Ground Settings

Ground Settings

Now create a cube (Game Object -> Create Other -> Cube), position it at (0,2,0), and set the scale to (10,4,10). The centre of all objects is in the middle of the volume, so to sit an object on the ground we must move it up (in they-axis) by half of it’s height (the y-value of it’s scale).

Cube Settings

Cube Settings

At the moment our scene only contains ambient light. This means that our rendered scene will not be black, but we will also not be able to distinguish things like the edges of objects. We can fix that by adding a point light to the scene.  (Game Object -> Create Other -> Point Light) Let’s rename it Sun, since we want it to simulate a sun somewhere at a distance. Position the newly created sun at (100,400,100) so it is off to the side of the centre of our little world and up quite high. Now set the range for the light to be 1000 so that it spreads light across most of the ground.

Sun Settings

Sun Settings

The last thing to do is to position our camera so that we can see our cube. Select the camera and set it’s position to (20,10,-20) and it’s rotation to (15,-45,0).

Camera Settings

Camera Settings

And that is all for part 1. We now have ground that can be extended when we want to, a light to simulate the sun, a cube for reference, and a camera to see things. If you hit play now you should see a scene very similar to the one below.

Final Scene for Part I

Final Scene for Part I

Next time we will look at adding some framework details to build our game on and then we will make our camera interactive.