Refactoring Classes #1: The BaseCharacter Class pt.1

5 minute read

Published:

Lately I’ve been looking over my Character classes (mainly the Base Character and Main Character classes) and have been wanting to refactor them since their lengths are quite large. These classes contain lots of necessary functions, but idealy these specific functions should be split into components/classes of their own. This cleans things up and makes usability much easier. So in this small series I’m going to be discussing my process with refactoring them.

The Idea Behind What We’re Doing

Now I don’t want to go into a huge discussion on SOLID principles, encapsulation, design patterns, etc. However, I’ll discuss the idea of what I want to accomplish, and why it’s effective.

image info

When looking at what a Character class needs, there’s quite a lot of stuff that’s packed into it. This diagram doesn’t even list everything too, these came from needs off the top of my head, so there’s definitely more. Now imagine having to cram everything into this single class, it’s going to become very large quickly, making it tougher to read and modify as we add more stuff to it.

So, in order to remedy this, we should view our character as how a car is made up, each function of the car is split into multiple parts/components of their own.

image info

This is super nice since whenever we’re working on a car we can simply focus on that specific component and modify it. No need to change anything else inside. (At least from what I understand with cars anyway…)

The Refactoring

  • Moving An Inventory Management Function - for this case when I was figuring out my implementation for adding items to a character’s inventory I had accidentally put the entire function inside of the Base Character class. The fix for this was just simply moving this function to the Inventory Component of the Character, and fixing a couple of pointers in other places just so things now point to the Inventory Component whenever things call this function.

  • Moving Debug/General Event Actions - for this case I used to have all of the action events (keyboard/controller events) for debugging inside of the blueprint class for the main character. I also had actions such as opening the Inventory, Pausing, Action Prompts, etc. inside of the main character’s blueprint as well. This was obviously cluttering a lot of space inside of it, so I added these general actions to the Player Controller class. image info

  • Creating A Character Actor Component Handler Class - inside of the Base Character classes they used to contain a couple of functions which handled the ‘constructing/destructing’ of Actor Components that were attached to them.

    Actor Components are basically specific classes that can be attached to an actor and they each serve their own purpose that affects the actor. For example, a Character’s Inventory Component is an Actor Component because the Character has an inventory in it.

Example Diagram for Actor Components on an Actor
image info
What they look like in Unreal on an Actor Blueprint
image info

  • Now, I created a separate Actor Component which handles the construction/destruction of other Actor Components for Characters. This is fantastic for adding and removing status effects as they’re each a separate component, or adding items/weapons/abilities to a Character’s inventory due to them also being components. Having a handler like this just makes things easier to manage.
  • Creating A Blueprint Handler Class For Loading Components From Saves - lastly for this post, I used to have this huuuge function inside of the blueprint class of the main character which handled constructing Actor Components from a Character’s Inventory which were saved inside of the Game Instance object as strings.

    The Game Instance handled transferring save data between rooms, and also loading data from a file. I chose to handle this long method in blueprints since it was much easier to reference the ‘structs’ I had created which associated these saved strings to class references to load. For some reason it became pretty complicated to achieve in C++.

  • Now, I moved this huuuge function to a separate Blueprint Actor Component, I also split each piece of the method into multiple separate methods which made things much cleaner and clearer to read.
    image info

Some Small Troubles and Future Updates

All of these issues in which I needed to refactor them stemmed from me not having much experience with software development patterns in the past, but now I’m pretty sure I have a good understanding of them. It’s also tough to focus on mapping how I would design these structures while also implementing these features in the first place, but I think that would just get better as I practice more.

In a future post I plan to continue refactoring these two classes (Base Character and Main Character) in order to reduce their sizes by splitting functionalities they have into components. For next time I’m going to move the Hitbox Overlap Events inside of the Base Character since these functions are rather large. I then plan to work on the ‘Main Character class’ which has a bunch of functions with Abilities, Weapon Attack Movesets, etc. which needs huge refactoring just to make adding more easier.