Unity RTS Tutorial Part 2 - Fully Working Resource System


Adding a new resource

So first up, I have decided to include money in my game. So will update the scripts to include this resource. We need a sprite to include for display in the HUD. Open the Player prefab select the HUD and add a new sprite to the "Resources" List. If you edit this in the Hierarchy be sure to apply the changes to the prefab. Next we need to look at the code. Several modifications are required to add a new resource. Add the following at the end of the switch statement in the "Start()" method of "HUD.cs";

                case "Money":
                    resourceImages.Add(ResourceType.Money, resources[i]);
                    resourceValues.Add(ResourceType.Money, 0);
                    resourceLimits.Add(ResourceType.Money, 0);
This creates the HUD buttons and text for money as the script starts. For the "DrawResourceBar()" method add the following just below the similar code for the other resources.
        iconLeft += TEXT_WIDTH;
        textLeft += TEXT_WIDTH;
        DrawResourceIcon(ResourceType.Money, iconLeft, textLeft, topPos);
In "Player.cs", at the top of the class definition, update this line;
    public int startStone, startStoneLimit, startMetal, startMetalLimit, startFood, startFoodLimit;
to be;
    public int startStone, startStoneLimit, startMetal, startMetalLimit, startFood, startFoodLimit, startMoney, startMoneyLimit;
This sets the resource amount and limits for mone.;
In "Player.cs" update the Dictionary "InitResourceList()" to include our money code;
        list.Add(ResourceType.Money, 0);
"AddStartResourceLimits()" gets this line;
        IncrementResourceLimit(ResourceType.Money, startMoneyLimit);
With a similar line in "AddStartResources()";
        AddResource(ResourceType.Money, startMoney);
To be able to load the new resources we need to add 2 lines to the "LoadResources()" method;
                        case "Money": startMetal = (int)(System.Int64)reader.Value; break;
                        case "Money_Limit": startMetalLimit = (int)(System.Int64)reader.Value; break;
The Save routine will already save additional resources. Finally in "Enums.cs" I have updated;
    public enum ResourceType { Stone, Metal, Food, Rock, Unknown }
To be;
    public enum ResourceType { Stone, Rock, Metal, Ore, Food, Vegetables, Money, Gold, Unknown }
As well as adding the new "Money" resource I have expanded this the fully develop the other resources. For my "Stone", "Rock" will be the resource while it is being collected. "Ore" is "Metal" during the collection. "Vegetables" are "Food" during the collection. "Gold" is "Money" during the collection. Running the game now we should see the new resource available in the resource bar.

Removing Resources

To start with my game already has the ability to remove stone as units are created. This is handles in the "CreateUnit()" method in "Building.cs". The follwoing line controls this.

        if (player && unitObject) player.RemoveResource(ResourceType.Stone, unitObject.cost);

This statement needs to be expanded to handle multiple resources and the similar code in the "Player.cs" script to do the same when buildings are created. To start with "WorldObject.cs" needs to be updated to account for the new resources and the sale values of them. The following line setting variables needs to be updated;

    public int cost, sellValue, hitPoints, maxHitPoints;

Is updated as follows;

    public int stoneCost, stoneSellValue, foodCost, foodSellValue, metalCost, metalSellValue, moneyCost, moneySellValue, hitPoints, maxHitPoints;

This will actually break a few sections of code. but we can fix them as we add the ability to handle the new resources.

First off, lets update the "StartConstruction()" method in "Player.cs". The following line needs updating;

        RemoveResource(ResourceType.Stone, tempBuilding.cost);

To the following;

        RemoveResource(ResourceType.Stone, tempBuilding.stoneCost);
        RemoveResource(ResourceType.Metal, tempBuilding.metalCost);
        RemoveResource(ResourceType.Food, tempBuilding.foodCost);
        RemoveResource(ResourceType.Money, tempBuilding.moneyCost);

The next change is in the "Building.cs" script. Change the following line in the "CreateUnit()" method from;

        if (player && unitObject) player.RemoveResource(ResourceType.Stone, unitObject.cost);


        if (player && unitObject)
            player.RemoveResource(ResourceType.Stone, unitObject.stoneCost);
            player.RemoveResource(ResourceType.Food, unitObject.foodCost);
            player.RemoveResource(ResourceType.Metal, unitObject.metalCost);
            player.RemoveResource(ResourceType.Money, unitObject.moneyCost);

Next update the "Sell()" method. Change the following line from;

        if (player) player.AddResource(ResourceType.Stone, SellValue);


        if (player)
            player.AddResource(ResourceType.Stone, stoneSellValue);
            player.AddResource(ResourceType.Food, foodSellValue);
            player.AddResource(ResourceType.Metal, metalSellValue);
            player.AddResource(ResourceType.Money, moneySellValue);


Checking for available resources

We need to add some checks here to see if we have enough resources avaialble before starting creating an object. For units we need to prevent the unit form being added to the build queue. It would also be good here to prevent the user from clicking the button in the first place and it would also be nice to be able to see which buttons cant be clicked. Fortunately Unity can already handle this for us. We just need to disable the button. The "DrawActions()" method is called by "DrawOrdersBar()", which is itself called by "OnGUI()". "OnGUI()" can actually be called a number of times per frame so the code we add to the called methods will constantly be rerun to setup our buttons again if our available resources change. Unity can already handle this kind of UI. When the GUI buttons are set to disabled they are greyed out and cannot be clicked. We are not currently setting them to enabled or disabled, so they default to enabled. We need to set this just before the button is created. Add this line;

GUI.enabled = EnoughResource(actions[i]);

Just before the following line in the "DrawActions()" method;

if (GUI.Button(pos, action))

The new line of code calls the "EnoughResource()" method, which we will get to in a moment. It returns true or false. Setting the state of the button to true of false. Before we end the group we need to include a line to make sure it is always true at the end of the method so any later GUI components are not set to false. The end of the method should now be as follows;

        GUI.enabled = true; //Add this line above the existing one.

Now we need to add the following method;

    private bool EnoughResource(string name)
        List<int> list = ResourceManager.GetBuildCost(name);
        int storedStone = resourceValues[ResourceType.Stone];
        int storedMetal = resourceValues[ResourceType.Metal];
        int storedFood = resourceValues[ResourceType.Food];
        int storedMoney = resourceValues[ResourceType.Money];
        int stoneCost = list[0];
        int metalCost = list[1];
        int foodCost = list[2];
        int moneyCost = list[3];

        if (stoneCost > storedStone || metalCost > storedMetal || foodCost > storedFood || moneyCost > storedMoney)
            return false;
            return true;

This method checks our available resources and returns false if any of them are below the requirements. This calls the following which needs to be added to "ResourceManager.cs"

        public static List<int> GetBuildCost(string name)
            return gameObjectList.GetBuildCost(name);

This itself calls a method in "GameObjectList.cs". Add the following to "GameObjectList.cs";

public List<int> GetBuildCost(string name)
                List<int> list = new List<int>();
        for (int i = 0; i < buildings.Length; i++)
            Building building = buildings[i].GetComponent<Building>();
            if (building && building.name == name)
                return list;
        for (int i = 0; i < units.Length; i++)
            Unit unit = units[i].GetComponent<Unit>();
            if (unit && unit.name == name)
                return list;
        return null;

This method checks through the list of buildings and units to find the costs for the relevant button. It returns a list of the integer values. These values can then be checked against available resources in "EnoughResource()". At this point you should be able to run the game and test the new resource system.

The current code can be found on GitHub: