Creating custom components

So far we have talked about using built-in components like Camera and Renderable, but another major way you'll be using components is to create your own. Components serve as the main place to put your gameplay logic in, and this is where you'll be adding a majority of your custom code when creating a game.

Creation

To create your own component simply implement the Component interface by deriving from it. Component's constructor must always accept a handle to a SceneObject, which represents the scene object the component is part of.

// A simple component that does nothing
class CCameraFlyer : public Component
{
public:
CCameraFlyer(const HSceneObject& parent)
:Component(parent)
{}
};

Logic

Each component implementation can override any of the three primary methods for introducing gameplay logic:

  • Component::onInitialized - Called once when the component is first instantiated. You should use this instead of the constructor for initialization.
  • Component::update - Called every frame while the game is running and the component is enabled.
  • Component::onDestroyed - Called just before the component is destroyed. Use this instead of the destructor for cleanup.

Here is a simple implementation of a component using a few of these methods to implement basic camera movement.

// A simple component that moves a Camera component attached to the same SceneObject
class CCameraFlyer : public Component
{
public:
// Basic constructor same as above
CCameraFlyer(const HSceneObject& parent)
:Component(parent)
{}
private:
// Called once when component is first added to a scene object
void onInitialized() override
{
// Find the camera component we'll be influencing (assumed to be on the same scene object)
mCamera = SO()->getComponent<CCamera>();
// Create virtual buttons we'll be using for movement (assuming we registered them previously)
mMoveForward = VirtualButton("Forward");
mMoveBack = VirtualButton("Back");
mMoveLeft = VirtualButton("Left");
mMoveRight = VirtualButton("Right");
}
// Called every frame while the component is active
void update() override
{
// Check if any movement or rotation keys are being held
bool goingForward = gVirtualInput().isButtonHeld(mMoveForward);
bool goingBack = gVirtualInput().isButtonHeld(mMoveBack);
bool goingLeft = gVirtualInput().isButtonHeld(mMoveLeft);
bool goingRight = gVirtualInput().isButtonHeld(mMoveRight);
// If the movement button is pressed, determine direction to move in
Vector3 direction = Vector3::ZERO;
if (goingForward) direction += SO()->getForward();
if (goingBack) direction -= SO()->getForward();
if (goingRight) direction += SO()->getRight();
if (goingLeft) direction -= SO()->getRight();
// Multiply direction with speed and move in the direction
float frameDelta = gTime().getFrameDelta();
float speed = 10.0f;
Vector3 velocity = direction * speed;
SO()->move(velocity * frameDelta);
}
HCamera mCamera; // Camera component that is influenced by this component.
// Virtual keys we will use for movement
VirtualButton mMoveForward;
VirtualButton mMoveBack;
VirtualButton mMoveLeft;
VirtualButton mMoveRight;
};

Use Component::SO() to access the scene object the component is attached to.

Component handle

You will also likely want to declare a handle you can use to easily access the component, same as HCamera or HRenderable. This is done by simply creating a typedef on the GameObjectHandle<T> object.

typedef GameObjectHandle<CCameraFlyer> HCameraFlyer;

Using the component

We now have everything ready to use the component. You can create the component as any other by adding it to the scene object.

// Create a scene object to add our component to
HSceneObject cameraSO = SceneObject::create("Camera");
// We create a Camera component since our component relies on it
HCamera camera = cameraSO->addComponent<CCamera>(primaryWindow);
// And finally we add our component
HCameraFlyer = cameraSO->addComponent<CCameraFlyer>();

Advanced

Now that we have the basics covered, there are a few more things to know that can be useful when building more advanced components.

Activating/deactivating a scene object

Any scene object can be temporarily de-activated and reactivated by calling SceneObject::setActive(). When a scene object is deactivated its components will not have Component::update() called.

Your component can also be notified at the exact moment when activation/deactivation happens. This way you can perform additional functionality if needed. Override Component::onEnabled and Component::onDisabled to get notified every time a component is activated and deactivated, respectively.

// We're just extending the component we defined above
class CCameraFlyer : public Component
{
...
void onDisabled() override
{
gDebug().log("Component disabled.");
}
void onEnabled() override
{
gDebug().log("Component enabled.");
}
...
};

Getting notified on scene object change

Sometimes you want to get notified when the scene object the component is attached to moves or changes parents. You can do this by overriding the Component::onTransformChanged() method.

// We're just extending the component we defined above
class CCameraFlyer : public Component
{
...
void onTransformChanged(TransformChangedFlags flags) override
{
if((flags & TCF_Transform) != 0)
gDebug().logDebug("Parent SO moved.");
if((flags & TCF_Parent) != 0)
gDebug().logDebug("Scene object parent changed.");
}
...
};

TransformChangedFlags parameter will notify you whether the scene object moved, has changed parents, or both.

Note that Component::onTransformChanged will never trigger by default. You must first enable it by calling Component::setNotifyFlags. It accepts the same TransformChangedFlags parameter which tells the system in which cases should it trigger Component::onTransformChanged.

// We're just extending the component we defined above
class CCameraFlyer : public Component
{
...
void onInitialized() override
{
// Get notified when the scene object moves
setNotifyFlags(TCF_Transform);
}
...
};