Skip to content

How to : Components

Julien Pires edited this page Aug 5, 2013 · 2 revisions

HomeHow to ▸ How to : Components

How game object and components are managed?

Game object management

GameObject(GO) are managed by a GameObjectManager instance. You have to create yourself an instance of this class. You can create one or more depending on your need. You can for example create one GameObjectManager instance per scene.

GameObjectManager myGOManager = new GameObjectManager();

GameObjectManager has methods to add or remove a GO and events when a GO or component are added or removed. GameObjectManager has an Update method and it's important to call it. With a GameObjectManager you can easily retrieve your game objects and easily use components system.

GameObjectManager myGOManager = new GameObjectManager();

// Game loop
while(isRunning)
{
    myGOManager.Update();
}

Component management

Components are only containers for game objects data. Components is what defines game objects's nature and behaviors. Components can be processed by a class which inherit ComponentHandler. ComponentHandler is like a brain. A component alone, without ComponentHandler, is nothing excepts pure data. A ComponentHandler processes these data, works with them. In many cases there will be a 1:1 relation between Component type and ComponentHandler.

// Base class for all ComponentHandler
public abstract class ComponentHandler : IDisposable

All ComponentHandler are grouped into an instance of ComponentHandlerSystem. GO are managed by GameObjectManager, ComponentHandler are managed by ComponentHandlerSystem. In order to dispatch Components to the corresponding ComponentHandler, ComponentHandlerSystem has to be linked to a GameObjectManager. This will ensure that when a GO is added all its current and future components will be dispatched and removed when the GO is removed. You can have one or more instance of ComponentHandlerSystem.

GameObjectManager myGOManager = new GameObjectManager();
ComponentHandlerSystem myCompoSystem = new ComponentHandlerSystem(myGOManager );

// Game loop
while(isRunning)
{
    myGOManager.Update();
    myCompoSystem.Update(elapsedTime);
}

Like for GameObjectManager, ComponentHandlerSystem has an update method. It's important to call it otherwise ComponentHandler will not be updated.


How to create/remove a game object?

It's simple to create a GameObject. You only need an ID, preferably unique if you plan to add it on a GameObjectManager.

ulong id = 1;
GameObject myObject = new GameObject(id);

Once a GameObject is created you can add it to a GameObjectManager.

GameObjectManager myGOManager = new GameObjectManager();
myGOManager.Add(myObject);

You have two solutions to remove a GO from a GameObjectManager. You can remove it immediately or add it to a pending list for removal. The pending list will be processed at the beginning of the next frame before any update of the components system while the immediate will not wait for the next frame.

// Remove the GO immediately
myGoManager.RemoveNow(1);

// Add the GO to a pending list for removal
myGOManager.Remove(1);
//or
myGOManager.Remove(myObject);

How to create/remove a component?

Components module provides only a base class for a component.

public abstract class Component

You need to inherit this class for all your components. Create one instance will depend on how you write it. But it will be a good idea to keep an empty constructor for a easier serialization.

Once you've created a component you have to add it to a GO.

GameObject myObject = new GameObject();
MyAwesomeComponent oneComponent = new MyAwesomeComponent();
myObject.Add(oneComponent);

If your GO is managed by a GameObjectManager the component will be dispatched to the corresponding ComponentHandler.

Removing a component from a GO is easy. You have two ways to do it. You can use RemoveNow methods to remove the component immediately or use Remove methods to add the component in a pending list for removal at the beginning of the next frame.

// Remove immediately
myObject.RemoveNow<MyAwesomeComponent>();
//or
myObject.RemoveNow(typeof(MyAwesomeComponent));
//or
myObject.RemoveNow(oneComponent);

//Add the component to a pending list for removal
myObject.Remove<MyAwesomeComponent>();
//or
myObject.Remove(typeof(MyAwesomeComponent));
//or
myObject.Remove(oneComponent);

How to work with components?

Component handler is the brain of a component. Components are only datas and component handler has the logic to work with. In many cases you will have a 1:1 relation between a type of Component and a component handler.

All ComponentHandler shoulds inherit the ComponentHandler base class.

public abstract class ComponentHandler : IDisposable

There is a parent class called ComponentHandlerSystem which is an aggregate of ComponentHandler. This class is responsible for managing them and dispatch components to the right ComponentHandler.

ComponentHandler class contains three abstract methods. This is important to implement them.

public abstract void Register(Component compo);

This method is called when a component is added to the GameObjectManager associated with the parent ComponentHandlerSystem.

public abstract bool Unregister(Component compo);

This method is called when a component is removed from the GameObjectManager associated with the parent ComponentHandlerSystem.

public abstract void Tick(GameTime time);

This method is called every frame. The logic should be added in this method.


How to link a ComponentHandler with a type of component?

A ComponentHandlerSystem instance is able to dispatch components to appropriate component handler. ComponentHandler class has a property to detect which component it is listening for.

public Type[] ComponentTypes { get; }

This property return an array of Type instance. Internally ComponentHandler class has a protected array and classes that inherits from ComponentHandler should populate this array to target specific component.

public abstract class ComponentHandler : IDisposable
{
    protected Type[] componentTypes;
}

The array can contains zero, one or more type instance. It's acceptable to listen for different components. Type instance should be at least of type ComponentHandler otherwise an exception will be thrown by ComponentHandlerSystem when you will try to add the component handler to it. An array with zero element means that your not listening. A null value is valid and means that your listening to all components.

public class AwesomeComponentHandler : ComponentHandler
{
    public AwesomeComponentHandler()
    {
        // Listen to all components
        this.componentTypes = null;

        // Listen to no component
        this.componentTypes = new Type[0];

        // Listen to specifics component
        this.componentTypes = new Type[]{ typeof(MyFirstComponent), typeof(MySecondComponent) };
    }
}

How to communicate between ComponentHandler?

The Core namespace has a Mediator class that can be used to send and receive message. You can create as many instance of Mediator as you want.

Here is an example of two component handler sharing a Mediator instance to communicate.

public class FirstAwesomeHandler : ComponentHandler
{
    private Mediator mediator;
    private EventType playerMove;

    public FirstAwesomeHandler(Mediator media)
    {
        this.playerMove = EventType.CreateEvent("Player_Move");
        this.mediator = media;
    }
}

public class SecondAwesomeHandler : ComponentHandler, IEventHandler
{
    private Mediator mediator;
    private EventType playerMove;

    public SecondAwesomeHandler(Mediator media)
    {
        this.playerMove = EventType.CreateEvent("Player_Move");
        this.mediator = media;
        this.mediator.Register(this.playerMove, this);
    }
}

// Somewhere in a loading
Mediator media = new Mediator();
FirstAwesomeHandler firstHandler = new FirstAwesomeHandler(media);
SecondAwesomeHandler secondHandler = new SecondAwesomeHandler(media);

The first component handler called FirstAwesomeHandler registers a new type of event : Player_move. The second component handler does the same and registers itself to the mediator instance. The register method of the mediator class is used to link a listener with a specific event.

The second component handler implements the IEventHandler interface. This interface is necessary for registering to the mediator. This interface has one method which is called by the Mediator class each time an event for which the listener is registered is triggered.

Now the first component handler will send a message and the second one will receive it.

public class FirstAwesomeHandler : ComponentHandler
{
    / ......... /

    public override void Tick(GameTime time)
    {
        Message msg = new Message(this.playerMove, time, this);
        this.mediator.QueueEvent(msg);
    }

    / ......... /
}

public class SecondAwesomeHandler : ComponentHandler, IEventHandler
{
    / ......... /

    public void HandleEvent(Message msg)
    {
        if(msg.eventHash == this.playerMove.eventHash)
        {
            // Move the player
        }
    }

    / ......... /
}

Since you can create yourself Mediator instance you have to call the Tick method. The Mediator acts as a stack of message and the Tick method sends the messages from this stack. This method should be called at each update of the game loop.