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 aUnitData
class - instance – the
Building
class should inherit from aUnit
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 toUnitData
, do the same for the files themselves (renameBuildingData.cs
toUnitData.cs
) and update the Scriptable Object associated menu:
- create a new
BuildingData.cs
C# file with a super-basic class that just inherits fromUnitData
- 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 aBuilding
variable): because we use some specific behaviour from theBuilding
class in theBuildingManager
, 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 theUnitManager
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.
Hi,
Line23 of Unit.cs should be foreach (ResourceValue resource in _data.Cost) instead of foreach (KeyValuePair pair in _data.Cost). 😉
Best wishes
Nice one! Fixed 🙂
This should happen less and less as you go through the tutorial… I think I got better at copy-pasting pieces of my code later on ^^
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. 😉
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.
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…
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. 😉
Chung-Tai, thx!
Great job! I love this tut 🙂
Hello, thanks a lot for your nice comment, I’m really happy you like it! 🙂
And it’s great that Chung-Tai’s answer could help you out, too 😀
Cheers!