Making a RTS game #7: Polymorphism, take 2! (Unity/C#)

Let’s continue our RTS game and focus on the hierarchy of our classes!

This post is also available on Medium.

In this tutorial, we are going to reorganize some of our classes to better prepare what’s to come: we will create a clear logical hierarchy for our units that can be either buildings or characters. We started that process in a previous episode of this series, but we’re not done yet.

At that point, you might realize that we could – and should! – re-use the notions of inheritance and polymorphism we saw in the previous tutorial for our data and unit classes. What we want is to replicate the kind of class hierarchy we have between UnitManager and BuildingManager at two levels:

  • data – the BuildingData class should inherit from a UnitData class
  • instance – the Building class should inherit from a Unit class

Proxying for the data classes (UnitData/BuildingData)

We need to have a UnitData class that our BuildingData class can inherit from. This parent class will need to centralize all the fields that are generic to all units like, say: the unique code/name, the description, the number of initial health points, and the cost in game resources.

Wait, isn’t that our entire BuildingData class? Yes it is! That’ s because so far we haven’t introduced any particularity that makes it a building rather than a character…

Let’s make the best of this situation and tackle the problem while it’s a quick-win. ‘Cause it’s going to be quick:

  • rename the BuildingData class to UnitData, do the same for the files themselves (rename BuildingData.cs to UnitData.cs) and update the Scriptable Object associated menu:
  • create a new BuildingData.cs C# file with a super-basic class that just inherits from UnitData
  • change the building Scriptable Object associated menu

And that’s enough for the data classes! 🙂

What we did here is a “direct transfer”, a proxy that sets the scene for future coding but does not inherently add functionality.

An important note: we do not want to make a global list of building and character types – it is better to keep a separate list of building types in our Globals class. So we don’t need to change our data loading mechanism at the moment.

Also, we should update two small pieces of code in our current scripts: firstly, in our CustomEventData, we can replace the BuildingData-typed field by a UnitData-typed one:

This will allow us to pass in data on any (sub)type of unit in our events. Similarly in the UIManager, in our _SetInfoPanel() function, we can use our new parent data class and replace the field accesses:

Separating the instance classes (Unit/Building)

Creating the same sort of hierarchy between the Unit and Building classes is going to be a bit more complicated because we have to extract the proper fields and methods but keep some behavior that is specific to the Building in the derived class. Basically: the placement mode, the materials switching and the DataIndex getter are only useful for buildings so we can take them out. The rest should remain in the base Unit class:

Re-updating the unit managers (UnitManager/BuildingManager)

Finally, we need to change a few things in our manager classes to properly use the class hierarchy we just created:

  • since it is used by all types of units, we are going to move the box collider requirement and assignment to the UnitManager – so it needs to become protected instead of private
  • we need a reference to our unit instance (a Unit or a Building variable): because we use some specific behavior from the Building class in the BuildingManager, we can’t benefit from polymorphism and wait for run-time “mutation” this time; instead, we are going to override a getter to convert the value to the right type
  • we’ll move the Initialize() method to the UnitManager so it is common to all unit types

This gives us the following bits of code:

A good opportunity to prepare some future features

This little refactor is also a nice opportunity to think ahead and prepare data that will be useful in the future. There are three things I want to add to my instance classes: a unique instance uid, a unit level and a unit resources production Dictionary.

  • Having a unique uid (or “uid” for short) is always interesting because it’s a sure way of differentiating between different instances of the same thing; C# has a built-in Guid that directly gives us the tool for that
  • Units have levels because I will put in place a system of upgrades: the player will be able to level up buildings and characters in order to improve their stats (and optionally produce new units in buildings).
  • Resource production is very similar to resource cost, that’s why we’ll use a List of ResourceValue in both cases; the resource production Dictionary simply maps an in-game resource unique code to the amount that’s produced by that unit each “turn” – I haven’t decided how often the resources are collected yet, so I’m voluntarily vague on this topic… 😉

Both those properties are linked to an instance and not a data class, because two instances of a Tower could have different levels, and two instances of a House could have different productions, for example. And they are possibly valid for buildings and characters alike, so I’ll put them in the Unit base class. They are pretty straightforward to implement:

You’ll notice that I’ve added another constructor so that if no production is provided when creating a new Unit(...), the program is able to automatically define an empty production Dictionary on its own. For good measure, it’s better to do the same on the derived Building class:

The point here is not to dive into the whole resource collection system – this will be a topic for another tutorial. But at least at this point, everything is ready for us to implement it!

Conclusion

This tutorial was shorter than usual but it was important to take a step back, look at our classes and plan what is to come. Having introduced this new hierarchy and the few additional fields, we’re making our lives easier for the upcoming features.

Our refactor will already pay off greatly in our next episode: we will have a second go at our units selection system and code up selection groups, integrate a “selected unit” panel and list the currently selected units.

Leave a Reply

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