On with our RTS game: today, we’ll make our buildings produce some resources!
This article is also available on Medium.
In the last tutorial, we’ve worked on our units and we’ve implemented a system of ownership. In this article, we’ll start from there and focus more on our buildings: we are going to see how to have them produce some resources based on their position in the world.
Here, what I call “resources” are the three in-game resources we talked about a long time ago, at the very beginning of this series: gold, wood and stone.
For this project, I wanted my resource production system to have three characteristics:
- the type of resources a building can produce depend on its type
- the amount of resources a building produces depends on where it’s placed in the world: the surroundings impact the “production efficiency” of the building
- resources are infinite: instead of having units travel to the nearest tree and gradually chop down the entire forest (which eventually forces you to move your woodcutter cabin), we’ll be able to continuously produce resources once the building is placed
In that sense, our resource production system resembles more management games like Industry Giant than our usual RTS references like Warcraft 3:
Setting up the resource production logic (using fake data)
Before actually diving into the computation of the building’s production capacity, let’s first use some mockup data to set up the production process and make sure it works. Only then will we implement the previous step of “producing more or less resources depending on the terrain the building is placed on”.
For this feature, we are going to proceed as follows:
- we’ll add a new list of unit to our
ownedProducingUnits: in this list, we’ll add units that we place and that have a non-null production (i.e. units for which the owner matches our player index and where the list of produced resources has at least one element)
- then, we’ll have a new coroutine in the
GameManagerthat runs every
_producingRateseconds and loops through all the
ownedProducingUnitsto call their
ProduceResources()method. At the end of the loop, it also sends an event for the
UIManagerthat asks it to redraw the UI elements displaying the resource amounts in the top bar.
- whenever we pause/resume the game, we’ll cancel or restart this coroutine (similarly to the coroutine in the day-and-night cycler)
- finally, for our tests, we’ll add some fake data in the
BuildingPlacerscript to mimic some non-null productions for our buildings
Overall, it’s pretty straight-forward. First, let’s modify our
GameManager with the new variable and the new coroutine:
Then, let’s check for the aforementioned conditions when we place a new unit so that we know if it should be added to the
ownedProducingUnits list or not (also: you might recall that we actually implemented the
ProduceResources() method a long time ago – I’ll copy it here so you remember what it looks like):
And let’s setup our mockup data:
We can now test it out – if we run the game, we see that after the initial building that is spawned in the
Start() method with our fake production data regularly increases the amount of gold, wood and stone in the UI displays at the top. Every 3 seconds, we get new resources!
Note: by the way, remember that you can click on the building to see its production capacity in the selected UI panel on the right 😉
Improving our game resources definition
At the moment, our in-game resources are referenced by a unique code (
stone) that is a string. While it’s valid, it’s not necessarily the best option because it makes it easier to make a mistake in-between scripts. Did I put “gold” or “Gold”? Was it “stone” or “stones”? All of those questions are a pain and shouldn’t even come to mind when we’re programming!
To have a more robust implementation, we are going to quickly refactor our game resources system to use an
enum. The main change happens in the
Globals.cs file, where we need to create this new enumeration and use it in the
GAME_RESOURCES dictionary (note that we change both the type and the initial data of this variable):
Now, Unity will complain about some compilation errors because the rest of the code still uses strings for in-game resources. We have to update:
UIManagerclass (both where we prepare our resource displays and where we use them):
- during our tests, the mockup data in the
You can restart the game: everything should work exactly as before – but the code is now way more structured under the hood! In particular, we have a fixed value for
InGameResource.Gold, for example, and it’s easily accessible via our IDE, which insures that we won’t make a mistake when using this key somewhere.
Computing the real unit production capacity
Well – that was nice, but it was the easy part! It’s time to really have fun and figure out how to get the actual units production capacity 🙂
The amount of resources a unit produces will be the combination of two things:
- the unit type: some units don’t produce anything (for example: character units aren’t producers a priori – even though you could easily switch it on in your game!), others only increase your gold, others produce multiple types of resources, etc.
- the unit’s position on the map: for the wood and stone resources, the amount of resources the unit can extract at each “turn” (i.e. every
_producingRateseconds) depends on how many trees or rocks there are around; for the gold, there can be a bonus from nearby buildings but it’s less subjected to the environment
So there are two things to do: one, we need to set in our
UnitData Scriptable Object assets the list of resources that this specific unit type can produce; two, we need to know in more details the computation we’ll do for the wood / stone / gold production.
Step 1: Adding the list of valid resources to a unit type
Now that we’ve refactored our resources system to use an enumeration, this part is pretty straight-forward. We just need to update our
UnitData script and add this new field:
And we’ll now have this new field in the Inspector of our assets that we can customise:
You’ll notice that Unity is even able to offer us a little dropdown with the proper options since this field uses a collection of
enum-typed objects, which is really handy 😉
This allows us to set what kind of resources a unit may produce – but we still need to figure out how much it actually creates, based on its current location and its surroundings.
Step 2: Introducing the production system
Note: of course, the system I code up here is just one idea – you should definitely see if it fits your game or else devise your own! 🙂
My resources production is going to work as follows:
- for gold, there is a
baseGoldProductionvalue; then, you get a
bonusGoldProductionPerBuildingbonus gold for every other building unit you own within a given range
- for wood and stone, we are going to get the nearby trees or rocks and attribute each a score: the closer the object, the higher the score – then we’ll sum all of those scores to get the final production score
There’s something important to note with this system: the gold production of a unit may change over time if you place more buildings around it so it won’t be simply computed once when we first place our unit. On the other hand, we won’t actually “consume” the trees and the rocks so the production of wood and stone will remain the same all throughout the life of the unit.
(Except if we decide to improve the unit’s production efficiency based on its level…)
To implement all of this, we first need to add some variables to our
GameGlobalParameters class. These will be the various factors and ranges relevant to computing this resource production.
Of course, remember to assign them in the Inspector of your global parameters asset – the numbers I chose here are just an example and you should definitely play around with your own game to pick the most appropriate ones!
We are also going to create specific functions that compute the “partial score” of our unit in terms of wood and stone production based on its distance to one given tree or rock. Again, I went for something really simple here but you could definitely investigate and find other nice ideas for those calculations. (Note: here, I based my computation on the inverse function, x : 1/x, because it’s something that decreases as the distance increases, which I think makes the most sense in this situation…)
Those functions leverage the concept of delegates that we talked about previously.
Now, let’s modify our
Unit class to better store the current unit’s production and to use those brand new variables. The first thing we need to do is convert our list of
ResourceValue-typed objects to an actual dictionary (for easier and faster lookup). Be careful: we will still receive a
List<ResourceValue> variable in the constructor – it’s just that we’ll automatically convert it to a dictionary!
Note: in this snippet of code and in the upcoming ones, I’ll use the Linq C# feature quite a bit. If you’re not familiar with it, I’ve recently wrote an article on this topic 😉
This implies a little change in our
UIManager, to avoid compilation errors:
We’re all set for computing the unit’s production based on its current surroundings!
Step 3: Computing the impact of the environment
In order to easily check for the building units, trees and rocks that are around our unit, we’ll rely on the Unity’s physics engine, and more precisely on the
Physics.OverlapSphere Unity’s built-in function.
This method allows you to easily get a list of all the colliders that can be detected in a sphere around your origin point. So I’m basically going to count how many building or trees or rocks I can find in my surroundings, apply some factors and sum the results to get my final production of gold, wood and stone. Of course, I’ll only do the calculation if my unit can actually produce this type of resource.
To optimise the search for the close objects, let’s create some additional layers in our project. We are going to put the units on one layer, the trees on another and the rocks on a third one. This way, our call to
Physics.OverlapSphere will specifically focus on finding the objects in the layer we give it and ignore the rest.
We did that a long time ago: to add layers, we need to go to our project settings, to the “Tags and Layers” submenu, and add the new layers at the end of the list:
Then, we have to update our prefabs to properly use those layers. Remember that the important thing is that the object or child object that has the physics collider has the proper layer. The rest of the objects don’t matter for this computation.
- for the units, we have to make sure that the prefab in itself is on the “Unit” layer but that the “FOV” child stays on the “FOV” layer
- for the trees, we just set the prefabs to use the “Tree” layer
- and for the rocks, we set the prefabs to use the “Rock” layer
Note that I found some nice free low-poly tree and rock models on the Unity Asset Store by Broken Vector I turned into prefabs – but you can of course continue using basic cubes if you want, it will work too! 😉
Globals class, we’ll add a reference to our new layers:
Now that all of that is done, here is finally the code to update the production of our unit resource per resource in a new method called
ComputeProduction() (in the
For the gold production, we make sure that we only count the building units that we own for the bonus… And we also returned the computed production in the end – this will be very useful very soon 😉
That method should be called whenever we place a building unit or we instantiate a character unit:
To test that everything works properly, try and place some trees and rocks around the spawning point of your initial House building (which should be in the middle of the screen given your current camera view). Then, be sure to remove any mockup data you wrote previously, and start the game. The House building that is placed initially should automatically compute its production capacity and start creating some value! (Remember to select the unit to get more info on its current production in the right panel)
Note: in the following images, I’ve materialised the zone the unit checks for nearby objects with a green disk – since the range is the same for all checks, we only have one area to consider 🙂
In this example, the House produces 5 gold per “turn” because it has no nearby building unit to give it a bonus. Then, we have one tree and one rock that contribute to the wood and stone production – since they are both almost at the limit of the zone, they only give a small contribution, barely balanced by the factors in our
You can play around with the assets on the terrain to see how it impacts the production…
And of course you can place other buildings near or far from natural resources; and you can have your unit types allow for more or less resource types:
Displaying the expected production amount before placing a building
At that point, we’ve successfully implemented our production system and we could call it a day. However, there is something really cool that we can do to help the player evaluate where he/she should place a new building: have a little floating panel that follows the “phantom” building and shows what the production of this unit will be if it is placed there.
The idea is similar to what is done in the business video game Industry Giant II, for example:
To implement this feature, we’ll rely on our event system. Basically, we want to tell the UI to update this floating panel (be it its position on the screen or the values inside it) whenever the “phantom” building is moved on the ground. We also want to use the “PlaceBuildingOn” and “PlaceBuildingOff” events we already created a while ago to toggle this floating panel on and off.
First things first: let’s create the UI element in our 3D scene! Now – as always with the UI parts of my tutorial, you should definitely take my ideas with a grain of salt and adapt them to the style of your own game 🙂
For this floating panel, I simply made a UI image and added some “Vertical Layout” component to it:
Important note: for our code to work properly and to place the panel in the right place, make sure that the “RectTransform” component as its anchor point in the bottom-left corner.
With this taken care of, we can update our
UIManager script to reference this new UI element, show and hide it whenever we enable or disable a building placement and update the production info whenever the “phantom” building is moved:
You’ll notice that the data we receive in our “UpdatePlacedBuildingProduction” event contains two objects: the new production values and the position of the “phantom” building. So let’s just add this event trigger in the
This event is triggered in the
Update() method, when we compute our raycast to re-position the “phantom” building and we check if the position was updated.
We now give the player some indication of what production the building unit will have if it’s placed at a given position – yay! 🙂
Today, we’ve gone back to some RTS basics and implemented a core system from scratch, like in the beginning of this series: the unit resource production! We’ve also added some new events for UI updates and we’ve seen how using enums can help sanitise our codebase.
Next time, we’ll discover a nice programming pattern, the “behaviour tree”, and see how it can help us design more complex behaviours for our units…