Creating an RTS in Unity: Part IX

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

It is now almost time to begin allowing the player to produce more units and buildings. But before we can do that we need to add in resources for the player, along with a display of what they currently have into the HUD.

Resources for the Player

To keep things simple for this tutorial we will introduce two basic resource types: money and power. Money will be used to buy everything that the player will want to build or upgrade. Power will be used by buildings (although we will probably not introduce any penalties as part of this tutorial). The main reason for adding power is that we will want to handle power a little differently from resources like money. It provides things for the base, rather than acting as a consumable resource like money. Once we are handling power correctly it should not be hard to add consequences for running out of power.

The first thing we need to do for a player is to give them the ability to store resources. As part of this, we are going to make it so that the amount of resources that a player can have is limited by storage capacity. So to gain lots of money they are going to need somewhere sensible to store this. For a player we will determine a base storage capacity (they can always hold $1000 for example) that can then be expanded during the game. Add the following variables to the top of Player.cs.

public int startMoney, startMoneyLimit, startPower, startPowerLimit;
private Dictionary<ResourceType,int> resources, resourceLimits;

To allow the code to make use of Dictionary add

using System.Collections.Generic;

to the very top of the file. We will use this to map a resource type to a quantity. By doing things this way we can actually write most of our code to handle details independent of the particular resource in question. This allows us to easily add / remove resource types or change what those are. We will use this concept for both the amount of resources a player has and their capacity for that resource. The other 4 variables allow us to play around with how much money and power the player starts with, as well as how much of each of those they can store before needed dedicated storage facilities. If we wish to add more resource types later on we will also need to add more variables for the start values for that resource type.

Let us now add another entry to Enums.cs to define our resource types. We will use the following entry for now.

public enum ResourceType { Money, Power }

Remember that to access this from Player we also need to add

using RTS;

to the top of the file.

Now that we have somewhere to store resources, let’s add in the logic for initializing those values. We need to add the Unity method Awake() to our Player with the following code in it.

void Awake() {
	resources = InitResourceList();
	resourceLimits = InitResourceList();
}

We will use the method InitResourceList to provide the initialization for any collection of resources that needs to be handled – for now just our resources and the storage capacities for those resources. Let’s implement this method now.

private Dictionary<ResourceType, int> InitResourceList() {
	Dictionary<ResourceType, int> list = new Dictionary<ResourceType, int>();
	list.Add(ResourceType.Money, 0);
	list.Add(ResourceType.Power, 0);
	return list;
}

The last thing we need to do when setting up our Player is to add their start resources (and limits). We do this by adding

AddStartResourceLimits();
AddStartResources();

to the end of the Start method and then implementing each of these methods as follows.

private void AddStartResourceLimits() {
	IncrementResourceLimit(ResourceType.Money, startMoneyLimit);
	IncrementResourceLimit(ResourceType.Power, startPowerLimit);
}

private void AddStartResources() {
	AddResource(ResourceType.Money, startMoney);
	AddResource(ResourceType.Power, startPower);
}

Once again, each of these methods uses helper methods to add values for each resource type, which also need to be implemented.

public void AddResource(ResourceType type, int amount) {
	resources[type] += amount;
}

public void IncrementResourceLimit(ResourceType type, int amount) {
	resourceLimits[type] += amount;
}

Resource display in HUD

Our player now has a way to store resources and the limits for those, as well as start values for both of those. Now it is time to display this amount to the user, which is done through our HUD. We will start by adding

if(human) {
	hud.SetResourceValues(resources, resourceLimits);
}

to the Update method for our Player. Now, obviously, we need to provide an implementation of this within HUD.cs.

public void SetResourceValues(Dictionary<ResourceType, int> resourceValues, Dictionary<ResourceType, int> resourceLimits) {
	this.resourceValues = resourceValues;
	this.resourceLimits = resourceLimits;
}

This simply sets the values of the HUD version of resourceValues and resourceLimits to be those specified. This means that we need to declare those at the top of HUD

private Dictionary<ResourceType,int> resourceValues, resourceLimits;

and initialize them at the beginning of Start().

resourceValues = new Dictionary<ResourceType, int>();
resourceLimits = new Dictionary<ResourceType, int>();

Now that we have current values for the player being sent to the HUD we are ready to present that information to the player. This means updating our DrawResourceBar method for HUD. Add the following code between GUI.Box() and GUI.EndGroup()

int topPos = 4, iconLeft = 4, textLeft = 20;
DrawResourceIcon(ResourceType.Money, iconLeft, textLeft, topPos);
iconLeft += TEXT_WIDTH;
textLeft += TEXT_WIDTH;
DrawResourceIcon(ResourceType.Power, iconLeft, textLeft, topPos);

and then implement the method DrawResourceIcon.

private void DrawResourceIcon(ResourceType type, int iconLeft, int textLeft, int topPos) {
	Texture2D icon = resourceImages[type];
	string text = resourceValues[type].ToString() + "/" + resourceLimits[type].ToString();
	GUI.DrawTexture(new Rect(iconLeft, topPos, ICON_WIDTH, ICON_HEIGHT), icon);
	GUI.Label (new Rect(textLeft, topPos, TEXT_WIDTH, TEXT_HEIGHT), text);
}

This simply finds the appropriate image and text for the resource and then draws them in the specified location. If we ever want to change where a particular resources is displayed on screen all we need to change is the values being passed to DrawResourceIcon().

For both of these methods to work properly we need to declare some more constants at the top of HUD.

private const int ICON_WIDTH = 32, ICON_HEIGHT = 32, TEXT_WIDTH = 128, TEXT_HEIGHT = 32;

We also need to provide a way to drop in and then access textures for our resource icons. We will use an array of textures so that we can drop in any resources we like from within Unity

public Texture2D[] resources;

and then a dictionary to provide us easy access to those images at runtime.

private Dictionary<ResourceType,Texture2D> resourceImages;

Now we just need to add a final bit of initialization into Start() before we can do any drawing.

resourceImages = new Dictionary<ResourceType, Texture2D>();
for(int i=0; i<resources.Length; i++) {
	switch(resources[i].name) {
		case "Money":
			resourceImages.Add(ResourceType.Money, resources[i]);
			resourceValues.Add(ResourceType.Money, 0);
			resourceLimits.Add(ResourceType.Money, 0);
			break;
		case "Power":
			resourceImages.Add(ResourceType.Power, resources[i]);
			resourceValues.Add(ResourceType.Power, 0);
			resourceLimits.Add(ResourceType.Power, 0);
			break;
		default: break;
	}
}

Here we are making sure that the appropriate textures are associated with the appropriate resources. While we are at it we make sure that we have default values for the amount of resources and capacities to be displayed.

Now we need to provide the textures from inside Unity. First we should create somewhere to store these … Create a folder inside the HUD folder called Icons. Inside here we want to place two 32×32 textures (called Money and Power) to identify the resources to the player. In keeping with the theme of simplicity I have gone with the icons below (but you are more than welcome to construct your own).

  • Power IconPower
  • Money IconMoney

Once you have some images in Unity, drag them onto the resources field of the HUD. If you run your game now you will see the icons showing up, along with 0/0 for each resource (since we have not set starting values for the player yet), but the text is weirdly positioned. This is because we need to change the settings for Label inside our ResourceSkin. We want to set the border to 2, margin and padding to 0, and the alignment to MiddleCenter. Now when you run your game things will sit much nicer. There is quite a large gap between the text and the image since we need to allow space for when the player has lots of resources (although feel free to play around with the positioning yourself). Let’s start the player off with $1000, the ability to hold $2000, 500 units of power, and a power capacity of 500 units. If you run the game now you should see that the player has 1000/2000 for money and 500/500 for power.

I think that brings us neatly to then end of this part. For the final version of the code from this part check out the commit for Part 9 on github.

Advertisements

15 thoughts on “Creating an RTS in Unity: Part IX

  1. Zoe says:

    Again, great series. Do not get your hopes down with Unity just yet. If you have not checked out the Steam greenlight game Folk Tale, I would recommend you do. It is a Unity game and the features, complexity, and graphics look both unique and interesting. I will link the latest video on youtube:

    • I am not planning on just giving up on Unity. I want to push it to the limits and see what those are for myself. It’s not that I do not believe others when they say things are not possible, but I would like to prove it. And by the time that point is reached a lot of the conceptual design of the game (how things are laid out, how they talk to each other, etc) will be in place, making jumping to another platform less painful.

  2. Zoe says:

    Glad to hear it. I think your logic is sound, and your approach to the game design has been most educating. I hope my previous post is not offensive to anyone, only a testament to what the Unity Engine can achieve in a similar genre.

  3. eXtreme says:

    Hey, i have one question and I don’t know to directly ask you so I guess it’s best to comment in this most recent tutorial even tho it’s not related to this one, sorry.. feel free to delete it afterwards, but please answer me πŸ™‚

    I’m following your tutorial and everything is going great, however I want to make my objects/units be deselected with left click when user clicks on anything but the already selected object.. I tried fiddling with the code but couldn’t manage to make it work.. :/

    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”)
    {
    CoreObject coreObject = hitObject.transform.root.GetComponent();

    if (coreObject)
    {
    player.SelectedObject = coreObject;
    coreObject.SetSelection(true);
    }
    }
    }
    }
    }

    Basically, I need to add to this method that if user clicks on something, but it isn’t the already selected object to deselect it.. Oh and CoreObject is what you refer to WorldObject, everything apart that is the same..

    Hopefully you can help me on this one!
    Great tutorials btw =)

    • eXtreme says:

      To be perfect clear, i want to deselect with my left mouse click instead of right, but just to first check if the user left click on the already selected object, in that case i don’t want to deselect it..

    • Sorry for the late reply. A basic look at this tells me it will be difficult to do. We are handling all of the selection with the left mouse, so clicking on another object changes the selection to that. Are you wanting a system where you can only select certain units? The reason I have gone with deselect on the right mouse click is because it is a whole lot easier to code. If you could let me know a few more details on what you are trying to do I might be able to give you some more insight.

      • eXtreme says:

        Hey Elgarstorm, just seen this.. It’s really good that you’re still around and that you haven’t abandoned a project and/or site.. πŸ™‚ Your tutorials are really great..

        Basically, you know like in 90% of modern RTS games you do all your selecting (and deselecting) with left click, and right click is there for issuing commands like ‘move to”, “attack” and stuff like that.. Nothing wrong with your current system, I just feel like it would be more user friendly and more what users are already accustomed to if it was the other way around, that’s why I choose the other way around.. If you’ve played Generals, Cossaks, Red Alert, Startcraft, Warcraft 3 etc you should know what I’m talking about..

        Imagine the following scenario.. You start the game and nothing is selected.. You left click on a tank and it becomes selected, easy so far.. Now while tank is selected, there are two possible things that can happen.. 1) is you left click on that same exact tank again (that is already selected) and logically nothing should change, ie that tank is still selected..2) is you left click on ground/sky/other selectable thing and the previously selected tank should be deselected and the new object that you left clicked on (if it’s selectable) should be selected instead..

        Hopefully you do understand me now, and thanks again for replying.. =)

    • I too share this common goal, so here is what I did.

      My code for the 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”) {
      if(hitObject.name != “Ground”) {
      WorldObject worldObject = hitObject.transform.parent.GetComponent();
      if(worldObject) {
      //we already know the player has no selected object
      player.SelectedObject = worldObject;
      worldObject.SetSelection(true, player.hud.GetPlayingArea());
      }
      }
      }
      if (hitObject.name == “Ground”) {
      player.SelectedObject.SetSelection(false, player.hud.GetPlayingArea());
      player.SelectedObject = null;
      }
      }
      }

      My code for the RightMouseClick:

      private void RightMouseClick() {
      if(player.hud.MouseInBounds()) {
      GameObject hitObject = FindHitObject();
      Vector3 hitPoint = FindHitPoint();
      if(hitObject && hitPoint != ResourceManager.InvalidPosition) {
      if(player.SelectedObject) player.SelectedObject.MouseClick(hitObject, hitPoint, player);
      }
      }
      }

      This makes it so that left clicking on a unit selects it, while left clicking anywhere else deselects. Also making the movement work with right clicking too.

  4. eXtreme, I get where you are coming from. Although, from memory Red Alert used the system that I am using (I played it lots back in the day) and Age of Empires gave you the ability to choose. Personally I prefer this style of input when I am controlling things. As things stand in the existing code, clicking on another object while you have one selected does work. Later on this will not always be so straightforward, since there might be an interaction available based on which object you have selected and which object you wish to select (repair, attack, harvest, etc). In this case you would need to right-click to deselect before being able to select the new object. Unfortunately, to enable the user input you are wanting would require rewriting the existing logic basically from scratch. I might do this at a later date, but it is not high priority for now. (If I did it would be to add the two input styles as options for the user inside a settings menu)

  5. Concerning left vs right click, Westwood (the creators of Command and Conquer) used Left click as the action button. Blizzard used right click, and the only reason is because right click allows you to execute the command on mouse down, rather than waiting for mouse up ( a whopping handful of milliseconds…)

    For whatever reason, the rest of the industry copied Blizzard, despite the immense popularity of C&C games. I personally feel that left click action is more intuitive. You do everything on a computer with left click ( clicking links, opening files, etc….), so it’s odd that the primary action button for an RTS is right click (all because the developers wanted to shave a few milliseconds off the response time of issuing an order).

    FURTHER, Command and Conquer has a right click drag scroll, which, when you get used to it, is amazing. After playing 10’s of thousands of matches in various C&C games over the years, I became dependent on right click drag scroll. Playing other games where you have to navigate by edge scrolling, moving your cursor down to the minimap, or using the middle mouse button (taking your finger off an action button), makes the whole camera system feel REALLY clumsy, totally negating the already insignificant benefit of action on mouse down.

  6. anton says:

    Texture2D icon = resourceImages[type]; —- the highleted line
    + RT error message :

    KeyNotFoundException: The given key was not present in the dictionary.
    System.Collections.Generic.Dictionary`2[RTS.ResourceType,UnityEngine.Texture2D].get_Item (ResourceType key) (at /Applications/buildAgent/work/b59ae78cff80e584/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:150)
    HUD.DrawResourceIcon (ResourceType type, Int32 iconLeft, Int32 textLeft, Int32 topPos) (at Assets/Player/HUD/HUD.cs:194)
    HUD.DrawResourceBar () (at Assets/Player/HUD/HUD.cs:86)
    HUD.OnGUI () (at Assets/Player/HUD/HUD.cs:59)

    i can’t figure out why the key is not being assigned , i think it’s supposed to be assigned in the Start() method but seems it’s not getting assigned + this problem is taking away the cursor image, as since this error, the cursor images don’t activate, the game is just using the system default cursors :S

    any idea ?

    • anton says:

      solved it, the whole idea was within the naming of the pictures, as when i saved them, they were written with the first letter being a small one, the code took the capitalized one’s and of-course this gave an error of the key not being in the dictionary, as through the initialization process , the keys were named with CAPITAL letter starting the word.

      an another note people, take care of the naming πŸ™‚

  7. Bruce says:

    Hi again. I had an error I got fixed after a while of Digging, stepping through and lol’s

    After messing about for ages i realized the reason my hud was broken without, i mean everything worked except for the cursor and the damed resource icons., was that power and money should be named with a capital. eg Power / Money not power money. the physical name of the imeges that you saved.

    I hope this bit of digging has possibly helped some one out.

    Thanks once again for the great Tutorial. Hope you will be back soon.

    • kgrover3 says:

      Wow. I just spent the past 2 hours stepping through everything and couldn’t figure that out. I’m glad you posted this or who knows how long I would have continued to try to figure this one out. It’s always the simple things.

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