Components

The Component interface helps you extend your Entity objects with additional features using composition.

A few examples of things you can do with components:

  • A component that can rotate any visual.
  • A component that provides advanced click interaction to any visual.
  • A component that allows to attach game-specific data and logic to an entity.

What's interesting about components is how flexible they are: instead of using strict inheritance to extend your objects, which can make your app too rigid and difficult to modify when new situations arise, you can attach your components to your entities at runtime. If done properly, a component can be compatible with many different entities, which encourages code reusability while still keeping the project as flexible as possible: you don't need to make all your object inherit from the same subclass to make them share the same feature. Instead, just attach a component!

Your first component

Let's get right to it and create our first component:

MyComponent.hx
import ceramic.Entity;
import ceramic.Component;

class MyComponent extends Entity implements Component {

    function bindAsComponent() {

        trace('Hello from my component!');

    }

}

This component is not doing much except logging a message, but here's what to say about it:

  • To be a valid component, the class implements the Component interface and the bindAsComponent() method.

  • The bindAsComponent() method is called when the component is attached to an entity.

  • The class must be a subclass of Entity, that is: every component is also an entity. That said, it does not need to be a direct subclass of Entity. You could turn a Visual into a component as well because Visual also inherits from Entity.

Attaching the component to an entity

Your component won't do anything until it is attached to an entity. Here's how you do it:

Attaching a component to an entity
// Create an entity
var someEntity = new Entity();

// Create a component
var myComponent = new MyComponent();

// Attach the component to the entity
// under the key: 'myComponent'
someEntity.component('myComponent', myComponent);

Because we attached the component to our entity, it's bindAsComponent() method is called, and you should see this log in the console:

Hello from my component!

You can also get your component instance back at any time with the key you used to attach it:

var myComponent:MyComponent;

// Get the component instance attached to the entity
myComponent = cast someEntity.component('myComponent');

Good to know: when an entity is destroyed, any component attached to it will be destroyed as well.

Also: you can attach as many components as you want to an entity, but a component can only be attached to one entity.

A concrete example

This is nice and all, but that first component is not very useful right? Let's create a more meaningful component, one that can rotate any visual!

Here is the component that makes this visual rotate:

RotateVisual.hx
import ceramic.Visual;
import ceramic.Entity;
import ceramic.Component;
import ceramic.Timer;

/**
 * An example of component that creates a rotating
 * animation with the visual attached to it
 */
class RotateVisual extends Entity implements Component {

    /**
     * This will be the visual we attached to
     */
     var visual:Visual;

    /**
     * The duration of each rotation
     */
    public var duration:Float;

    /**
     * The pause between each rotation
     */
    public var pause:Float;

    public function new(
        duration:Float = 2.0, pause:Float = 1.0) {

        super();

        this.duration = duration;
        this.pause = pause;

    }

    function bindAsComponent() {

        // Start rotating when the component
        // is attached to a visual
        rotateOnce();

    }

    function rotateOnce() {

        // Animate rotation
        visual.tween(
            ELASTIC_EASE_IN_OUT, duration, 0, 360,
            (value, time) -> {
                visual.rotation = value;
        })
        // And repeat
        .onceComplete(this, () -> {
            Timer.delay(this, pause, rotateOnce);
        });

    }

}

The @entity meta

You may have noticed that our visual field has an @entity meta. This is needed to tell this component's entity is typed as a Visual and will be kept under the visual field. Once bindAsComponent() has been called, that visual field will be the visual this component is attached to!

By default, if you don't provide any field with @entity meta in your Component class, the entity the component is attached to will be accessible under the entity field, and it's type will be Entity.

An excercise for you

Now that you have that RotateVisual component around, try to use it on a visual, it could be any visual, like a Quad, or a Text instance! Give it a try and play with it:

var rotateVisual = new RotateVisual();
someVisual.component('rotateVisual', rotateVisual);

You can take a look at a complete example here.

Statically typed components

Until now, we attached components dynamically with the following syntax:

entity.component('myComponent', myComponent);

This is convenient because it is all dynamic and allows to assign components at runtime without much restrictions.

That said, there may be some cases where you want to always have the same component attached to a class and would rather just access it with a field (without needing a cast). Good news, you can do that with the @component meta.

Back with our RotateVisual component, we could actually have a custom Visual subclass that always has this component attached to it:

import ceramic.Visual;

class MyCustomVisual extends Visual {

     public var rotateVisual = new RotateVisual();

    public function new() {

        super();

    }

}

When using components this way, you can access it in a more natural way:

// Create a new instance of our custom visual
var visual = new MyCustomVisual();

// Change the pause time of the `RotateVisual` component
visual.rotateVisual.pause = 5.0;

Depending on the use case, you may prefer this syntax or the dynamic one!

In general, the dynamic syntax will be useful if you want to extend Ceramic's built-in entities and visuals with components but don't want to create a custom subclass for that. On the other hand, the static syntax is handy when you are writing you own specialized entities and visuals and know which components you need to attach to them already.

More examples of components

Ceramic comes with some built-in components. You could take a look at the Click component, which adds advanced click interactions to any Visual: it can detect when a pointerDown event becomes an actual click or if it should be ignored if it looks more like a drag.

Using the Click component
// Create click component
var click = new Click();

// Attach the click component to a visual
someVisual.component('click', click);

// Listen to the component's `click` event
click.onClick(this, () -> {
    // Clicked on visual!
});

The StateMachine class provided by Ceramic is also a component, but that class alone deserves a dedicated guide! (soon 🤞)!

A note about ECS: Ceramic's entity-component architecture is not an ECS (entity-component-system) implementation. In Ceramic, you can use components to store additional data, but also add game logic to entities if you want. Nothing is strictly enforced.

If you still want to get closer to an ECS implementation, Ceramic provides the System class you can inherit from to create more central pieces of code that manage groups of entities. Take a look at the ArcadeSystem or SpriteSystem classes to see concrete examples (a proper guide about systems is planned).

What now?

You did a small tour around components, so next time you think of writing a reusable piece of code in your Ceramic project, consider using the Component interface! You might be surprised by how flexible it can be 😊. That is all for now!


Continue reading ➔ Text