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 reorganise 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 realise 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 centralise 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 behaviour 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 behaviour 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. Because we’ve introduced this new hierarchy and the few additional fields, we’ve made 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.

9 thoughts on “Making a RTS game #7: Polymorphism, take 2! (Unity/C#)”

  1. Hi,

    Line23 of Unit.cs should be foreach (ResourceValue resource in _data.Cost) instead of foreach (KeyValuePair pair in _data.Cost). 😉

    Best wishes

      1. Don’t worry, I love your tutorial and getting the motivation the re-engineer a complete program to get a tutorial out of it is admirable!
        I just want to help you to get more people to the later stages of this tutorial. If they quit in the early sessions, because of some easy to fix errors would be a waste of your time. So just keep up the good work and consider me as a kind of your proofreader. 😀 I have allready learned a lot in the past few tutorials so it could only get more. 😉

  2. Hi there, me again =)

    We missed something in here…
    There is a Cast-Error in _PreparePlacedBuilding insinde BuildingPlacer.cs, because we have refactored the Globals.BUILDING_DATA to datatype UnitData. The Constructor of Building.cs however needs BuildingData.

    This leads to the mentioned Cast error in _PreparePlacedBuilding.

    1. Don’t worry… it is my fault. -.-

      I have renamed the .cs file to split between Unit.cs and Building.cs and Visual Studio took the job a little further than expected… I have to look up all class calls to see whether Visual Studio has changed BuildingData to UnitData…

  3. Damn it…
    It took my now round a day to find the issue with this topic. So if someone has the same problem, I will describe the problem and the solution here.

    =Problem=
    After renaming the BuildingData into UnitData VisualStudio updatet all the references to this class. Unfortunately I had to look through the complete code to revert all the changed UnitData’s back into BuildingData references.
    After starting the game I had one problem: My Globals.BUILDING_DATA.Length == 0. But there was no error displayed in the log and no hint what happened here.

    =Solution=
    By renaming the BuildingData to UnitData VS has also changed the ScriptableObjects from BuildingData to UnitData. For we are only loading BuildingData Objects into our display, the list was empty. So I had to recreate both ScriptableObjects (House & Tower) to get the game back working.

    Perhaps someone has stumbled over the same issue, then this might help. 😉

Leave a Reply to Pytlos Cancel reply

Your email address will not be published.