Making a RTS game #29: Improving our players system (Unity/C#)

Let’s continue working on our RTS – today, we’ll improve the logic for player resource production!

This article is also available on Medium.

In the previous episode, we talked about something quite “meta”: shortcuts for the players to make some actions quicker to perform. Today, let’s go back to an in-game feature and talk a bit about our system of players.

So far, we’ve successfully implemented a list of players along with our own player ID and some game resource production. But it’s still a bit fuzzy at the moment when it comes to really producing resources per player. Let’s see how to do that!


A little note, not directly related to today’s topic: you’ll see in the demos that I have some new building models! Those are some (basic) 3D objects that I made in Blender for fun and that I’m sharing for free in the Github repository along with the code 🚀

You can check them out in the “Assets/Imports/Units/Buildings” folder of the project – and if you want to use them, make sure that the associated prefabs use the proper material slot for the player colour (you can see mine in the “Assets/Resources/Prefabs/Units/Buildings”) 😉

Adding a UI indicator for our current player colour

To make it easier to identify which player we are in the rest of the tutorial, let’s start by adding a little indicator in our UI that shows our current player colour.

I’m going to offset our minimap a little bit and place a small flag next to it that will be tinted by our player colour, like this:

But we are not just going to put an image sprite! We are going to have an animated sprite, so that this banner flutters in the wind 🙂

Creating an animated sprite

First things first: go ahead and get the sequence of PNG images from the Github repository 🚀, or make your own! It has to be a looping animation for a really nice effect, and I personally prepared those images using a cloth simulation in Blender – but of course, you can do it whichever way you want.

Also, notice that I made a “gray” banner: that’s because we will tint this image with our player colour. This way, we will handle any colour that the player chooses automatically and without any redundancy in our assets 😉

The idea is that we are going to create a Unity animation for our image that uses each of these images as keyframes for the “Sprite” property of a UI image.

To make this, let’s start by adding a new UI image to our canvas; I created a new panel called “PlayerIndicator” in my scene and placed the “Banner” image inside it – I also added a little “Pin” wood bar above it to make it look like the banner is hooked to something:

For more details, you can take a look at my scene in the Github repository 🚀

At that point, if we pick an image from the sequence of PNGs for the “Sprite” property of the Image component, we’ll get a nice image of a flag… but it’s not moving!

To have it animate, we need to create an animation asset in our Unity project and link it to this object by adding it an Animator component. The quickest way to do both at the same time is to select our object, so here the “Banner” UI element, and open the Animation window:

If you’ve properly selected your object, you’ll get to directly create a new animation clip for your object by clicking the “Create” button in the middle of the window. Click it and Unity will have you create a new Animation clip asset:

Once you’ve created your clip, Unity will also automatically create an Animator on the object that you selected, so your “Banner” UI element will now be able to use animations – and by default it will use the one you just created:

Ok – now, it’s time to actually add our frames to make the animation! To do this, you can simply select all the images in your project folder and drag them to this timeline area. This will automatically place them all next to each other and “rebuild” the animation properly:

You can actually try it out live by clicking the “Play” button in the bottom-left corner 🙂

Note: by default the animation clips are set to loop – this is what we want here but if you happen to have a single-shot animation, go to your new animation clip asset and untick the “Loop Time” property 😉

You might think, like I do, that the animation is a bit fast. To change this, we can manipulate the speed of the animation in the Animator component window. To access it, open the Window > Animation > Animator window.

Here, you find the Animator “state machine”: this is all the states that the Animator associated with this object can be in. Our object has only one possible animation, one state to pick from. It’s the default entry state (shown as an orange block) and so it plays automatically when we start the game.

If we click on this state block, we get a few additional properties, like the speed of the animation:

Note: for a more detailed on state machines, you can check out a video/text tutorial I made a few weeks ago about finite state machines in Unity 😉

At that point, we have a cool gray flag floating about!

Tinting the sprite with our player colour

The next step if to tint this image with our player colour to have it match.

We’ll do this in our UIManager. First, let’s add a reference to our new UI image. Then, we’ll have to set it up when the game starts – but we can’t actually do it in the Awake() function!

When we start the game, there is a quick early “warm-up” phase during which scripts are running their Awake() functions somewhat in parallel, then they get down to the Start() and so on, following Unity’s lifecycle for events execution order. But remember that we are only defining the GameManager Singleton instance in its Start() method – so it’s not yet available in the Awake() phase.

The solution is simply to put our “player indicator tinting logic” in the Start() function of the UIManager. And the logic in itself is pretty straight-forward – we access the game player parameters in our GameManager instance to get our player index and the associated colour in the array of players:

If you feel like the colours are a bit dark, you can actually add a small amount to this colour to make it brighter; but you might get values out of the nominal 0-1 range, so it’s better to re-clamp the components afterwards (I’ve extracted this colour-lightening function to my Utils.cs file):

Also, remember to drag the “Banner” UI element to the new variable slot in the UIManager component 😉

Fixing the minimap!

The last thing we need to take care of is… the minimap! Because we’ve moved it to the right, it’s currently a bit off! If you try to click inside it, you’ll see that there is an offset in the coordinates and the right part of the minimap doesn’t do anything.

That’s because, for now, our Minimap class doesn’t take into account the position of the UI element. Since it was 0 before, it was ok – but now we have to update our script to balance out this offset.

Be careful, though: the offset is not on the element with Minimap script itself but its parent container:

And voilà: the minimap is fixed! 🙂

Handling game resources for each player

Now that we have a nice visualisation of our current player ID, via its colour and our little banner, let’s get to the meat of this tutorial and have our system produce resources per player!

Transforming our GAME_RESOURCES dictionary into an array of dictionaries

The first thing that we need to change is the dictionary that currently holds our game resources, in the Globals class: the GAME_RESOURCES dictionary. It’s currently only capable of managing the resources for one player.

So, to have it handle multiple players, we’ll just replace it by an array of dictionaries: there will be one dictionary per player, as indexed in our game player settings Scriptable Object instance. We’ll often access the one at the myPlayerId spot, but sometimes we’ll also need to populate stuff in the other ones.

So – let’s go to our Globals.cs file and start by replacing the GAME_RESOURCES variable and creating a new method to prepare all of these dicts; it will simply take in the number of players in the game and create one dict for each:

Note that these dictionaries are currently initialised with non-null resource amounts to make it easier for debugging but, of course, it’s just for testing purposes! In a final build of the game, we would most probably have 0 of each resource at the beginning of a new game 😉

Of course, because we’ve changed the type of our GAME_RESOURCES variable, we’ll now have various compilation errors all throughout the project. Time to fix this!

In the Globals class

The first problem to solve is actually right here, in the Globals.cs file. For now, our CanBuy() function expects the GAME_RESOURCES variable to be a dictionary. What we’ll do is first add another input parameter to this function to pass in the player that we’re checking the available resources for, and then create a second prototype for this function so that, by default, we check our own resources:

This will avoid any compilation errors coming from this function because the new prototype replaces the previous one in all current calls.

But! We have to be careful because we don’t always want to call CanBuy() with our own player id! More specifically, the units should rather check for the current resources of their owner…

In the UnitData and Unit classes

This means that in our UnitData class, we have to modify the CanBuy() function so it expects the id of the owner of the building:

And so, in the Unit class, we’ll pass it in when we proxy the call to the associated UnitData instance:

While we’re at it, there are also 3 other modifications we have to do in this script: wherever we refer to our resources, we need to make sure that we take the sub-element at the right index. Namely, we want to get the dictionary at the _owner index in our Place(), LevelUp() and ProduceResources() functions:

In the GameManager class

The next small thing we have to do is, in our GameManager, actually call the new InitializeGameResources method so the dictionaries are ready to be read from or written to in the rest of our code:

In the UIManager class

And this brings us to the last and largest set of modifications: the refactor of the UIManager!

The nice thing is that, by definition, this manager shows the UI of one player in particular – us, the player at index myPlayerId. So we can actually store this index in the script and then refer to it in various places to insure we’re accessing the proper sub-element.

Remember that the tricky part, though, is that the GAME_RESOURCES variable isn’t initialised at the very beginning of the scene. Because of the lifecycle ordering, we once again have to use the Start() method instead of the Awake(). (If we tried to use the Awake(), during that tiny moment, if our UIManager tries to access stuff from the GAME_RESOURCES variable, it could get a null reference or an index error.)

So we will simply move all that is game resource-related in the Start() method of the UIManager; we’ll also set a _myPlayerId that is global to the entire class:

And so now, we just have to finish up fixing the compilation errors using the new _myPlayerId variable:

Preparing a basic test setup

To be able to check that we indeed have different resource amounts for each player, let’s add a bit of test/debugging UI to our screen. We want to have a row of buttons to easily switch from one player to another.

Note that we will not be updating the FOV and the minimap: this feature is just for quick debugging and it shouldn’t pollute the rest of the code too much 😉

This will not be real “proper” UI: instead, we’ll use Unity’s IMGUI (Immediate Mode GUI) to quickly create a bunch of buttons with callbacks. IMGUI is usually better for debugging because it is not as pretty/customisable as the Unity UI package but it is faster to code 🙂

Note: by the way – the IMGUI mode can also be used in the Unity editor itself (so, not in play mode) to create custom panels and windows, or additional objects for visual tooling and editing. If you want to learn more, you can check out either this article I wrote about Unity custom UI and async processes, or this video/text tutorial about creating gizmos in Unity! 😉

We’ll do this in our GameManager. To add IMGUI to your game, you just need to declare another function in your MonoBehaviour class: the OnGUI() method. This is called regularly by Unity to “repaint” the GUI on the screen.

Inside of this function, you can use various scripting utilities (directly available through our import of the UnityEngine package) to create basic UI. For example, here, let’s create an offset area with a row of linked buttons – the SelectionGrid() tool allows you to make a sort of radio input-like set of buttons, where one is active at a time and you get the updated index as return whenever you click a button in the grid:

Now if we run the game, we have 2 buttons in the top-left corner of the screen:

To have them actually update our logic, though, we need to compare the return value to our current player index and, if they’re different, perform some actions.

More precisely, we’ll want to set our player index to this new value and emit an event so that our UIManager can catch it and reset the UI properly:

On the UIManager side, we can simply say that:

If I run my game, I can now very easily switch between the two players – I see that the player indicator colour changes in the bottom-left corner, the game resource texts update at the top and when I select my units, I get more or less info depending on the player index that I currently have!

An important note, however: we are directly modifying the game player parameters Scriptable Object instance, so the changes will remain even after you’ve stopped running the game!

To make sure that we don’t inadvertently export it in a build of our game, we can even wrap this GUI logic in a little preprocessor check, like we saw in a previous tutorial:

Fixing the resource production logic

You might remember that, when we worked on our unit behaviour trees, and more precisely on our building behaviour tree, we added a little node called CheckUnitIsMine. This was here to insure that we would only produce resources for the current player.

Of course, now, we can get rid of this! Whereas before all the units would have contributed to the same resource pool, they will now automatically produce resources for the right player because the ProduceResources() function has been refactored properly 😉

So we can just remove the node in the behaviour tree, and all will work smoothly!

If you run the game, you’ll see that when you make two House buildings, one for each player, then each player will gather 7 gold coins every 3 seconds:

Conclusion

Today, we’ve improved our players system and we’ve made sure that we produce game resources for everyone. We’ve also taken this opportunity to learn about Unity animations and we’ve added a nice animated sprite to our UI!

Next time, we’ll refactor our save/load system – we’ll see how to replace our JSON serialisation with a binary serialisation to better protect our data and avoid it being directly readable and rewritable!

Leave a Reply

Your email address will not be published. Required fields are marked *