Making a RTS game #9: Implementing character units and skills (Unity/C#)

On with our RTS project! Today, we’ll add the character and skill systems…

This article is also available on Medium.

Last week, we improved our selection feature and, in particular, we added an info panel for the currently selected unit. Now, it’s time to populate this panel with skills and use it to create even more units!

For now, we only have building units — but remember that we prepared our class hierarchy in previous tutorials so it is easy to add character units. Today, we’ll add these character units, plus we’ll start a basic skills system that allows us to produce one of those character units in our House building.

Implementing character units

General info about character units

As we discussed a few weeks ago, characters are one of the possible unit subtypes. So just like we used inheritance and polymorphism for buildings, we’re going to do the same for characters.

Both buildings and characters:

  • have a cost in game resources
  • may have a production of game resources
  • have healthpoints
  • are matched to a game object to display them in the 3D scene
  • will have levels and optionally skills

On the other hand, only character units can move (buildings are fixed on the ground) and buildings are built directly by the player while characters are “produced” by buildings. All of this means that data is going to be very similar between characters and buildings but behaviour (i.e. the managers) will vary.

Setting up the scripts

The recent article on “Polymorphism, take 2!” was a nice opportunity to recall the three important scripts associated with units:

  • the data script (e.g. UnitData): it’s a Scriptable Object that allows us to create assets – those assets are the possible types of buildings or characters in the game (pure data with no actual instantiation yet)
  • the instance script (e.g. Unit): the data associated with a particular instance of a unit type, with all the values that are specific to this instance
  • the manager script (e.g. UnitManager): this script is in charge of handling the lifecycle and behaviour of the unit after it has been created (how it responds to the selection mechanism, the evolution of the placement mode for buildings…)

We need to do the same for characters and create the three corresponding scripts: CharacterDataCharacter and CharacterManager. At the moment, all of them are really simple – it’s mainly about inheriting the proper class and calling some parent constructors or methods if need be!

Adding a character unit asset

We can now access the Create > Scriptable Objects > Character menu and create a new character asset. Let’s name it “Soldier” and edit some basic info:

We can also create a little prefab (a cube as usual) and assign it in the asset’s prefab field.

Tadaa – we’ve just added our first character unit! 🙂

Implementing skills

In most RTS games, your units have specific abilities, or skills, that the player can use actively, i.e. by clicking on a button somewhere. Sometimes, units can also have passive abilities – skills that run continuously in the background and are executed regularly at a given rate. All of those skills are usually a particular trait of each unit even though some might be shared between two or more unit types.

Skills are also going to rely on Scriptable Objects for data and a manager script, but we don’t need to “instantiate” them as we do with units. This means that we only need a SkillData Scriptable Object class (to create our skills assets) and a SkillManager class to handle the skill behaviour once in-game.

Preparing the data class and a skill asset

A skill has some basic info like a unique code, a display name and a description. It also has a cast time and a cooldown time: the “cast time” is the time it takes to actually trigger the ability (basically after clicking on it you’ll have to wait X seconds for the skill to actually be cast) and the “cooldown time” is the time it takes to recharge the ability and have it ready to be cast again (after it has been triggered, you have to wait X seconds for the skill to be ready again). It will also have a sprite to show in the UI (even though it’s empty for now).

Note: ultimately, our skills will also have a specific sound for when they are cast and perhaps some visual/particle effect. Because it’s always cool to have fireballs dancing around when you’re playing with your mage! 😉

The two most important fields of a skill asset are:

  • an optional reference to some unit data asset in case it is supposed to draw values from some set of assets (for example, when “producing a unit”, we need a reference to the unit we’ll produce in order to instantiate the right one in the end)
  • a skill type to determine what to do with all the skill data

To control the various skill types we have, we can use an enumeration (or enum). An enum is a finite list of options that one can draw items from without any risk of passing in an unrecognised value, and usually with better auto-completion from the IDE 😉

At the moment, we only have one type of skill: “INSTANTIATE_CHARACTER”. Later on, we could add an attack skill that lowers the enemy’s HP, or maybe some healing skills to restore a friend’s health… Perhaps you’ll even have fireballs, warps and teleports if you have wizards in your game?

The skill type is essential because in the Trigger() function of the skill, we need to know what action to perform; by switching on this type value, we’ll know how to interpret the data and execute the proper action. Here is the code for our SkillData class with the example of the INSTANTIATE_CHARACTER skill type:

You’ll notice that the Trigger() function also takes in references to two game objects:

  • the source is the game object of the unit that is triggering the skill: it necessarily exists (because otherwise the non-existent unit could not be running this ability!) and it’s mandatory because most skills require you to know at least the position the skill was triggered at, which is the position of the caster
  • the target is the game object the skill is triggered on, if any: it is optional because not all skills have a target

We’ll see very soon how we can create a new asset for our “unit production” skill based on this Scriptable Object template.

Adding skills to our units

To be able to associate skills with a particular unit type, we’ll need to add a new property to our units data class: the list of available skills, as a list of SkillData objects. It has to be at the UnitData level because all instances of a given unit type should share the same set of skills.

So let’s modify our UnitData class:

Now, whenever we create a new unit asset, we can add a list of skills to it… and those skills are simply other Scriptable Objects that you can drag in the inspector!

Coding up the manager class

A key feature of skills is that they take a certain amount of time to cast and they also have a cooldown time. In other words, we want to be able to wait for a while before actually applying the skill’s effect(s) and wait again before it is back to its “ready” state. In Unity, whenever you want to wait, you can use coroutines and IEnumerators.

IEnumerators are a C# variable type similar to Python’s generators: they are an iterable collection that support custom collection types and don’t require you to compute all elements at once. Instead, you can iterate through your collection and grab elements one by one. You “end” each iteration with a yield return.

Note: IEnumerators allow you to read through your collection but not modify it; the foreach statement relies on IEnumerators underneath, that’s why if you try to remove elements from a collection you’re iterating through with foreach you’ll get an error when debugging your program.

Unity makes use of those IEnumerators to handle parallel processing via coroutines: contrary to usual functions that run until completion in one go, coroutines can pause at one point, return control to Unity so it can manage the rest of the code and then start back from where they left off at the next frame. All state, variable values and other parts of the execution context is saved and restored when the coroutine resumes. Coroutines are particularly useful when:

  • you want to wait for a given amount of seconds before an action is run
  • you want to optimize your program by reducing the frequency of some functions calls – for example, checking for enemies within range can be called every tenth of a second to lighten the load on the CPU without it being noticeable for the player

In our case, it’s the first point that’s interesting. For now, we are not going to make a fancy UI and we will simply enable or disable the skill’s button depending on whether it is ready or not – but of course in the end it would be better to have some sort of cooldown bar to show in how much time the skill will be available again 😉

Now, on each Unit instance, we’ll want a list of skill managers so that we can easily cast any of these skills from any of our instantiated units – and we have a TriggerSkill() method to easily transfer the call:

Remember that in all of these initialisation and proxy methods, we have to make sure we transfer the current source and target properly 😉

A skill example: unit production

To better understand the skills system, we will jump in and implement a crucial skill in our game: the ability some units have of “producing” other units.

We now have a character asset, the Soldier, and a Scriptable Object for our skills assets. All of this lets us create very easily a new skill, “Produce a Soldier”:

Then, we can add this skill to our House building asset in its list of skills:

And now, we’ll update our UIManager class to display buttons for each skill in the selected unit panel we created last time:

Here, the unitSkillButtonPrefab I’m using is just a simple button with a text inside. You can of course go for something a bit prettier with an icon and all, but this is enough for now for this tutorial 😉

Finally, we need to make sure that clicking on this UI panel doesn’t deselect our unit; to do this, we’ll need to once again check the mouse isn’t currently hovering UI elements . We can just add this check in our UnitsSelection class, in the Update() method, and return from it immediately if it happens:

After all of this coding, here’s the result: our House building has a new skill to produce a Soldier and when we click it, after 1 second, a Soldier appears near the House! We then have to wait for 1 second before the skill is available again:

This skill isn’t perfect yet – in particular, if we produce multiple Soldier units, they’ll all be stacked on top of each other instead of being aligned next to each other. But it’s fine for now, just to introduce the skill mechanics 🙂

Conclusion

In this article, we’ve built upon our late class hierarchy improvements to integrate character units and a very basic skill system. Our buildings can now produce units and we can easily create lots and lots of skills to populate our assets and “stuff” our units!

This system is still very simple, though: for a real RTS game, we’d have to add more skill types, plus some visual and audio effects.

But, in the meantime, next time we’ll go back to an RTS-must-have: the flying camera that looks at the terrain from above, with its orthographic projection

9 thoughts on “Making a RTS game #9: Implementing character units and skills (Unity/C#)”

  1. Hi,

    inside your UIManager.cs, you are missing the reference for the unitSkillButtonPrefab.

    And for beginners like me, it would be nice to mention what parts are important inside our Skillbutton Prefab.
    Btw you probalby have forgotten to set the sprite to the image part of the button inside the UIManager.cs. You only set the Skillname to .text.

    Best wishes and as allways, keep up the good work. 🙂

    1. Hi, good catch! I’ve updated the tutorial to add a bit more details (and the reason I’m not setting a sprite is because for now I just have a text in my button, I haven’t taken the time to design an icon ^^ – see the new paragraph for more details), thanks! 😉

  2. Hi again,

    currently I’m having trouble with organizing my menus. I know you want to let other players develop their own GUI, but I’m a little bit stuck with how, what and where to arrange something… 🙁
    I allready downloaded your git project, but this is far more developped than what I have at the current state of the tutorial, so it doesn’t really help me in adjusting my canvas.

    Although I did everything in the tutorial it looks like the SkillButtons weren’t deleted , because clicking on a house several time will give one more create unit button each time I clicked it (starting with zero, so the first time I select the house, there is no skill button).

    And the whole unit selection panel looks messed up, once I started the game. So I think inside the _SetSelectedUnitMenu() in UIManager.cs you are entering those elements and rearrange them by code.
    But I can only see the .sizeDelta for _selectedUnitContentRecTransform and _selectedButtonsRectTransform. But both have nothing to do with _selectedUnitActionButtonsParent which is causing the problems, because with selecting a unit it gets changed to a format outside of my window. :-/

    1. Hey, sorry to hear you’re stuck :/
      The part of the code that “clears out” the UI and gives a blank slate to re-instantiate new buttons is indeed in the _SetSelectedUnitMenu() function, it’s this couple of lines:


      foreach (Transform child in _selectedUnitActionButtonsParent)
      Destroy(child.gameObject);

      Are you sure you added them? Other than that, you’ll have to make sure that you properly assigned all your variables (and that you assigned the right objects 😉 ). My advice if you’re not yet too familiar with Unity UI would be to have a smaller test project to try things out and look for beginner tutorials on the net that are specifically focused on this topic – my tutorial does assume some prior knowledge, I’ll admit I’m not diving into all the details 😉

      Hope you find a solution, cheers!

      1. Thanks for your answer. 😉 I have allready solved the issue with too much buttons.
        Simply forget the .gameObject part behind Destroy(child.gameObject). So Destroy(child) leads to a transform error but not to a critical error that crashes the game. 😉

        Yeah, looks like I have to grab another tutorial for implementing a UI and handling the canvas. :-/
        I’ll be back once I’ve done this, but for now the UI looks pretty horrible. 😀

  3. After this lesson, unfortunately, I have a problem. When I run the game and set a house, an error message comes up in the console. I can no longer select the house that has been set.

    The error message is this:
    NullReferenceException: Object reference not set to an instance of an object
    UnitManager.Awake () (at Assets/Scripts/Units/UnitManager.cs:24)
    UnityEngine.Object:Instantiate(GameObject)
    Unit:.ctor(UnitData, List`1) (at Assets/Scripts/Units/Unit.cs:27)
    Building:.ctor(BuildingData, List`1) (at Assets/Scripts/Units/Buildings/Building.cs:23)
    Building:.ctor(BuildingData) (at Assets/Scripts/Units/Buildings/Building.cs:18)
    BuildingPlacer:_PreparePlacedBuilding(Int32) (at Assets/Scripts/Units/Buildings/BuildingPlacer.cs:48)
    BuildingPlacer:_PlaceBuilding() (at Assets/Scripts/Units/Buildings/BuildingPlacer.cs:62)
    BuildingPlacer:Update() (at Assets/Scripts/Units/Buildings/BuildingPlacer.cs:41)

    The place where the error message comes is in the constructor of the unit.cs
    GameObject g = GameObject.Instantiate(data.prefab) as GameObject;

    Please can somebody help me with that? I can’t find any difference to the Code of this article…

    1. Hi there! Happy to know you’re interested in the tutorial, but sorry to hear you’re having some trouble 😉
      So – just to be sure: have you checked that you have properly assigned your House prefab to the House Scriptable Object in your Unity project assets? Otherwise, this would indeed cause a null reference on the data.prefab field in your Unit constructor…

      If that doesn’t help, feel free to post again with additional details, or check out the Github (both scripts and ScriptableObjects/prefabs configuration), or even send me your code via email! I’d happy to take a look and see if I can help 🙂

      Cheers!

      1. Thanks for your quick reply. I actually forgot to put the prefab in the properties at one point. Now it works again and I can continue.

        This is really a good tutorial. Thank you very much for all the work.

Leave a Reply to Dominik Cancel reply

Your email address will not be published.