Oli's old stuff

Tinkering with retro and electronics

Jul 31, 2016 - 4 minute read - oldcode

Updating Manta-X (Part 2)

Components

Last time around we devolved the hierarchy into a component-based model. I wanted to clean that up a bit to make creating and accessing components a bit simpler.

At this point I’m not interested in exploring ‘pure’ entity-component models or arriving at a super-optmized solution, I’ll just implement something that makes working on the current system a bit more convenient whilst not closing off any future avenues.

Here’s the current GameObject class:

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;
};

My goal here is to remove the specialized std::unique_ptr and replace with a std::vector with some creation and accessor methods. In order to do this, I needed to create a lightweight baseclass for components - GameObjectComponent. At this point, it was just en empty struct.

GameObject then became:

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;

    std::vector<std::unique_ptr<GameObjectComponent>> components;
};

In true refactoring spirit, I tackled the problem one component at a time, adding things to the GameObject and GameObjectComponent as I went. The first thing that was required after adding the second component was that I needed to be able to determine the component type. I added this to the base component:

struct GameObjectComponent
{
    virtual ~GameObjectComponent() {}

    virtual int componentType() const = 0;
}; 

I added the getComponent method to GameObject:

GameObjectComponent* getComponent(int componentType) const;

I ended up adding a quick macro to implement this method and define a static const int ComponentType member; this allowed me to then retrieve a component by class type (eg: getComponent<ModelComponent>()).

template<class TComponent>
TComponent* getComponent() const
{
    return reinterpret_cast<TComponent*>(getComponent(TComponent::ComponentType));
}

Gradually refactoring things out, I discovered that some components needed to access others via their parent object - so I ended up putting a GameObject member on the base component. As well as accessor methods, I created some creation methods on GameObject to allow me to add components byt type:

template<class TComponent>
TComponent* addComponent()
{
    if (getComponent<TComponent>()) return nullptr;
    auto* cmpt = new TComponent(this);
    m_components.emplace_back(std::unique_ptr<TComponent>(cmpt));
    return cmpt;
}

template<class TComponent, typename TArg1>
TComponent* addComponent(TArg1&& arg1)
{
    if (getComponent<TComponent>()) return nullptr;
    auto* cmpt = new TComponent(this, arg1);
    m_components.emplace_back(std::unique_ptr<TComponent>(cmpt));
    return cmpt;
}

I’m still on VS2012 so I don’t have all the C++11 features available - as a result, I can’t use variadic templates.

At this point in time, I’m wondering whether this current component design is going to cut it. It’s not very data-oriented and is really a slightly more devolved version of the inheritance structure that was present. Having to put the back-pointer to the owning GameObject was telling that the design isn’t quite right - components still refer to each other directly whereby it should likely be a manager or system class that is dealing with these inter-dependencies. Additionally, sharing components between GameObjects may be something I want to do.

But right now it’s fine for my purposes so it’ll stick around until I need to change it.

Adding a Bad-guy

I wanted to test this out by adding a new feature - an enemy ship. The first new feature in 12 years!

This basically meant duplicating the code that creates the player and changing the rotation and position. When I start up the game, I see the enemy in the level, facing the player. Because they both have a PlayerControllerComponent they both move in response to my input. However, I realised that as I moved forwards, the enemy moved backwards. Essentially, the rotation of the enemy wasn’t being taken account of. In fixing this I’ve been wondering about whether to treat these objects as 3d space transforms, or screen-space 2d transforms. This is 2d game using 3d assets. There will be pseduo-3d in here in the forms of layering, so it’s more of a 2.5d game. Rotating an object will mean that the directions are relative to the object, not the screen. So I put in a simple “facing” concept to handle this. Now, the up/down is still in screenspace, but forwards/backwads is relative to the facing of the ship. Still not ideal, but it’ll do for now until I have something more concrete.

With all this fixed up, I removed the PlayerControllerComponent from the bad guy and now he just sits there like a duck waiting to be shot at.

At this point I’m really not happy with the code that initializes the game objects, so I wanted to make the system a little more data-driven (eg: loading data from file, not code).

First up though, there’s some cleanup work to tackle.

Until next time!