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.

24 thoughts on “Creating an RTS in Unity: Part VIII

  1. Greetings. My name is Binakot. I’m a programmer from the team of independent game developers “Bayonet Studio”.
    Our main project is a real-time strategy. We have been chosen engine for the project. Unity was on the list. Unfortunately for the global and complex strategies Unity is not very suitable. But to make it simple strategy is quite possible. I read your blog from the beginning. It’s always interesting to see what the other was thinking programmer on the same task as you.
    Good luck. And keep up the good work.

    • Binakot, what sort of project are you working on (if you can give me any details, feel free to email me privately)? And what sorts of problems did you run into with Unity?

      • Our main project is a real-time strategy “Red March”. Red March is a direct descendant of the Red Alter modification for C&C: Tiberium Wars (http://www.moddb.com/games/red-march).
        But this project we are doing in our spare time. Put it in our time and money. Unity engine was chosen as the time engine for the establishment of technical demo. In our game plan 5000+ units on a battlefield. Unity can not cope with this, because engine close and optimize of internal algorithms are not possible.
        I was trying to get rid of unnecessary events, and other treatments to improve the performance of the engine, but it did not really help. Now the project is selected for another open engine written in C++.
        I still work with unity as a game engine for another project. But this is not a hobby, its my job. I work as programmers on the project “After Reset” (http://www.afterreset.com/). For this project Unity went fairly well. But it’s an RPG and of the 5000 + units out of the question = p

    • It’s not even the number of units. Unity will not be able to handle the strategy, even with 200+ units such as Starcraft and Warcraft. Minimal graphics, low poly models, and the FPS is still less than 60. Rts in a lot of calculations at a time, such as pathfinding, collisions between units, animation, deals and receiving damage.
      I believe its possible to make AAA-game on Unity, but it’s not real-time strategy =)

      • That is a little disappointing to hear. Did you find that work done on the project up until that point was still useful though? Would you have another engine that you would recommend for high end performance in a strategy title?

  2. Engines which could have been written strategy to one person is no more) There are two most popular open engine for RTS: http://springrts.com/ and http://play0ad.com/. C++ and full sopen source, but for one person it is not possible. Continue to work with Unity. It’s not a dead end. You also will not be uber graphics and 1000 + units. There are many examples of developments rts on Unity. People just do not bring it to the end.
    http://www.youtube.com/user/ashenkster69
    http://seriousgames-zheroth.blogspot.ru/2011/07/rts-example-project-in-unity.html

  3. anton says:

    oooops an another question, this time i’ve checked the code, it is marked under part 11 i guess and it has a namespace of “Resources” which is until now not covered 🙂

    so my problem is whenever i select a unit and click somewhere i get this error message during RT of the Engine:

    NullReferenceException
    UnityEngine.Component.GetComponent[WorldObject] () (at C:/BuildAgent/work/cac08d8a5e25d4cb/Runtime/ExportGenerated/Editor/UnityEngineComponent.cs:206)
    UserInput.LeftMouseClick () (at Assets/Player/UserInput.cs:111)
    UserInput.MouseActivity () (at Assets/Player/UserInput.cs:99)
    UserInput.Update () (at Assets/Player/UserInput.cs:20)

    and this line of code get’s highlighted
    WorldObject worldObject = hitObject.transform.parent.GetComponent();

    and i am unable to move the unit , it simply stands still. what am i missing in here :S

  4. Could you post the entirety of UserInput.LeftMouseClick() as well as the entirety of WorkManager.FindHitObject() here so that I can see what is going on a little more clearly?

    Also, what is the nature of the ground object? Have you set it’s name to “Ground” (so the name of the object as seen in the hierarchy view)?

    • anton says:

      fixed it, i had a problem with one of the Floor objects not being inherited from the building, seems like a mouse drag mistake, and for the Ground, yes it’s name was Groind heheheh
      fixed them, as well as forgot to sit the movement speed and the rotation speed for the object which actually didn’t allow my unit to move as the speeds are sit to 0 😀

      i hope my problems in here help others and you to make the process clearer.
      and that people would learn from the mistakes that i’m facing so that if they face them they can find the solution within the comments 😀

      • Glad to hear that you managed to fix things 🙂 I have also been caught out by the forgetting to set movement and rotation speeds and wondering why nothing was moving. I wonder if we could set a default in the code – specify a value for speed when we declare the variable in Unit.cs? That way at least all units will be able to move. Then we can use setting values inside Unity to make each Unit unique.

  5. Kelley says:

    if(owner.username = player.username && (unit || building)) player.hud.SetCursorState(CursorState.Select);

    Please help. In this part I’m getting an error, that I can’t use && for operands of type ‘string’ and ‘bool’.

    Thanks for help.

    • The problem is that in that statement you are setting owner.username to be player.username, rather than checking to see if they are the same. An equality check requires == not =.

      • Kelley says:

        Thanks. That help. But now when I select unit or anything else nothin change in the cursor. I’ve tried to experiment with code, bud it won’t work. Now I have it in exact state as it is in this tutorial before the “Movement input” headline.

        Thanks

        • I’m not sure sorry. Are you sure that things are meant to change at that point? I do know that there is a default behaviour which is to use the select cursor. The thing you should check is which code is actually being called and what values are being used at that time.

  6. CoreyB says:

    quick question – in the first block of code, how does the Game Object “hoverObject” get evaluated in the if-statement (fourth line)? I’ve always understood if-statements to be for booleans only

      • Yes, that is how it is being evaluated. Some languages allow checks like this – although I think this might be the framework Unity is using, rather than C# itself which is allowing it. Anyway, within Unity that is how this check works. And it is so much easier to write.

  7. CoreyB says:

    Hiya,

    Movement doesn’t seem to be working for me – everything to this point has been gravy, but when I click on the unit, I don’t get any move functionality and the cursor remains in the “Select” state.

    Tried copying all the code from GitHUB and re-attaching scripts to all the objects, all to no avail. Any idea what I should look at to fix this?

Leave a reply to CoreyB Cancel reply