Updating Manta-X

Old Code, New Code

The last 8 posts have focussed on the past, bringing an old dog back to life. Now it’s time to look to the future. We’re no longer stripping things away, we’re now in the process of refactoring, modernising and - Horus forbid - adding new features.

The process of iteratively refactoring something is a skill you seem to pick up with experience. I clearly didn’t have this skill 12 years ago, as I left the game in a broken state, with a bunch of things ripped out and not working. Refactoring isn’t about razing it to the ground and building it back up again, it’s about deliberately evolving a codebase over many many small steps whilst keeping the thing working. Sure, you may introduce some ugliness during the process, but you’re doing it knowingly, with the end goal in mind. Anyone who’s interested in this technique should read Martin Fowler’s Refactoring book.

Project Structure

I always find that dealing with C++ in Visual Studio is a royal pain in the ass. Adding new classes is never quite as fast as you’d like. It didn’t help that I had a whacky src and include split.

I decided to port the project over to Premake5 to allow me to better iterate on the build system. Premake is good because it’ll generate my VS files for me at any time and it’s often a lot easier to work in premake than trying to configure the build in Visual Studio’s property editor.

I have two build projects - the game itself and TinyXml. It took me about 30 minutes to get this working with Premake, ensuring I was linking to everything correctly and using the right runtime libs.

With premake in place, I can now rapidly add new files or change folder locations for things. So the very first thing I did was to merge all the code from include into src, tweak my premake file and regenerate. It all worked perfectly.

Devolving GameObject’s Hierarchy

The Entity/GameObject system in the codebase is based on inheritance. If you remember the last post, I’d removed a bunch of junk from the GameObject hierarchy (including Entity), but I want to make things more composable.

There’s only two GameObject types at the moment - the GameCamera and the GameShip (player).

Here’s a summary of the current state:

        GameObject
      /            \
     |              |
GameCamera      ModelEntity     <--- ICollidable
                    ^
                    |
                GameShip

Over the last few years, I’ve been using component based architectures for game objects. This concept isn’t new and I’m not going to talk about the benefits of them in length except to say that assembling lots of smaller components makes creating behaviours and working with objects a lot simpler than trying to wrangle an ever increasing inheritance graph.

ICollidable

The first thing to do here is to introduce a break and remove the ICollidable from ModelEntity. I do this by refactoring the code into a CollisionComponent over several small steps.

  1. Create CollisionComponent that inherits ICollidable
  2. Add member to GameObject which holds CollisionComponent
  3. Move all references over to the new CollisionComponent instead of assuming they’re inherited members
  4. Remove the inherited ICollidable from ModelEntity
  5. Collapse the ICollidable code into the CollisionComponent

With that process, we retained the exact same functionality throughout whilst migrating to something more structurally sound.

The inheritance graph now looks like:

        GameObject      <>--- CollisionComponent
      /            \
     |              |
GameCamera      ModelEntity
                    ^
                    |
                GameShip

The CollisionComponent is reusable in that it will let me attach it to any future game objects.

The GameObject class now looks like this:

class GameObject
{
public:
    Vertex3 position;
    Vertex3 rotation;

    std::unique_ptr<class CollisionComponent> collisionComponent;
};

ModelEntity

Next up is to take care of ModelEntity, collapsing it down into a ModelComponent. Its job will be to render the model.

The process is pretty much the same as it was for the CollisionComponent - take a series of small steps towards the goal.

  1. Created ModelComponent that inherits ModelEntity
  2. Add member to GameObject which holds ModelComponent
  3. Move all references over to the new ModelComponent
  4. Make GameShip inherit GameObject instead of ModelEntity
  5. Collapse the ModelEntity code into the ModelComponent

The inheritance graph now looks like:

        GameObject      <>--- ModelComponent
      /            \
     |              |
GameCamera      GameShip

And the GameObject class is now:

class GameObject
{
public:
    Vertex3 position;
    Vertex3 rotation;

    std::unique_ptr<class CollisionComponent> collisionComponent;
    std::unique_ptr<class ModelComponent> modelComponent;
};

GameShip & GameCamera

The next thing is to apply the same refactoring process to GameShip, creating the ShipComponent and to GameCamera, creating the CameraComponent.

I now have no specialized GameObjects, so I mark the class as final.

class GameObject final
{
public:
    Vertex3 position;
    Vertex3 rotation;

    std::unique_ptr<class CollisionComponent> collisionComponent;
    std::unique_ptr<class ModelComponent> modelComponent;
    std::unique_ptr<class ShipComponent> shipComponent;
    std::unique_ptr<class CameraComponent> cameraComponent;
};

Player Input

The final thing to do was to relocate the current logic that takes player input from the place it currently lives (the Game class) and create a new component, ControllerComponent which does exactly the same job. I’m not too keen on the name but can’t think of anything better yet. InputComponent perhaps?

The job of this component is to inspect the controller (in this case, keyboard) and create an action to poke at the ShipComponent. If I had an AI ship, I could create a similar component that would control the ship in the same way.

Here’s our GameObject:

class GameObject final
{
public:
    Vertex3 position;
    Vertex3 rotation;

    std::unique_ptr<class CollisionComponent> collisionComponent;
    std::unique_ptr<class ModelComponent> modelComponent;
    std::unique_ptr<class ShipComponent> shipComponent;
    std::unique_ptr<class CameraComponent> cameraComponent;
    std::unique_ptr<class ControllerComponent> controllerComponent;
};

You’ll notice that there’s a bunch of members on here that only apply to some objects. Obviously adding new components will mean bloating this class out further. I’ll take a look at this next time.

Until then!