Let’s talk about refactoring! Early in development, I conceived of two types of “fighters” for Restless Ground: the protagonist and everyone else (“units”). This made a lot of sense… until it didn’t.
My original thinking: a lot of the code for units concerned their behaviour loop, and I’d probably want a different set of stats for the protagonist and units. I wasn’t even sure I wanted the protagonist to be able to move. However, they both had to be hittable; so I quickly implemented an interface called “IDamageable”…
CONVERGING CLASSES AND BADLY IMPLEMENTED INTERFACES
If you’re not familiar with C# interfaces, here’s the very basic practical use for them in this situation: you can have a class implement an interface (public class Protagonist: Monobehaviour, IDamageable). It now must have whatever public methods indicated in IDamageable — void TakeDamage(int number), int GetHP(). Now, my Attack() method doesn’t have to worry if it’s hitting a Unit or the Protagonist; it knows that anything that implements IDamageable can be… damaged. This is literally a textbook example.
So I tossed that in there and said “good enough.” A few months later, my Protagonist class and my Unit class began to look very similar. Both eventually implemented spells. Both needed to trigger animations. And both held similar state data, and interact with the world in similar ways.
IDamageable made sense for damage- and HP-related functions, but I had to implement an ICaster interface to handle spells. And what about something to get unit transforms? And then I started thinking about introducing player movement…
WELL, THAT GOT OUT OF HAND FAST
As it turns out, I want the protagonist and the units to share a LOT of functionality. After some design-thought, I decided that there are two fundamental differences between units and the protagonist. One is that when the protagonist dies, the game is over. Two, and more importantly, is that one is controlled by the player, and the other is controlled by AI. The fix?
Go from this:
IDamageable, ICaster, IWhateverable, Monobehaviour > Unit
IDamageable, ICaster, IWhateverable, Monobehaviour > Protagonist
Everything is a unit. Every unit has an AIController or PlayerController component. These take care of AI behaviour or player input commands.
After I refactored my code, I managed to implement controlled player movement in less than an hour. That was easy! But it also makes future improvements easier. My protagonist now has access to a huge suite of Unit commands that I had written for movement and similar functionality.
A FEW NOTES ON PLAYER MOVEMENT AND REFACTORING
- When I was first designing the game, I hadn’t thought too much about player movement. My test character class was a ranged caster type, but I intend to create more melee-oriented classes, too, but I wasn’t testing on one. This delayed the functionality.
- It makes the game FEEL so much better if you can move your character around responsively.
- The code reads so much better with the behaviour loops taken out of the unit class.
- I was daunted by the refactor at first. After all, I have maybe 100 classes that interact with IDamageable in some way. It took a bit of brute force, that’s for sure, and the better part of an afternoon. But after the refactor, everything seemed to work JUST FINE.