Custom Components

KAPLAY uses a flexible component system that helps you compose game objects logic.

Let’s take a look at how the default component lifespan() is implemented:

function lifespan(time) {
    let timer = 0;

    return {
        id: "lifespan",
        update() {
            timer -= dt();

            if (timer <= 0) {
                destroy(this);
            }
        },
    };
}

Components are just functions that returns an object. The return object will contain all the exposed states, methods, and event hooks of the component.

In this case, the lifespan() component returns:

  • id: unique identification of the comp, used in require, .unuse(), etc.
  • update(): an event hook that’ll run every frame while the component is attached to a Game Object.

Also, all this inside the component functions refer to the Game Object it’s attached to.

Special Fields

Here’s a list of all special fields you can use in your component:

function myComp(dataNumber: number) {
    // Use closed local variable for internal data
    let data = dataNumber;

    return {
        id: "mycomp",
        // If this comp requires other comps to work, we use comp ids
        require: ["area", "pos"],
        // Runs when the obj is added to scene
        add() {
            debug.log("Hi! This should only be fire once.", data);
        },
        // Runs every frame
        update() {
            // We're using a method from "pos" comp here, so we declare require "pos" above
            this.move(200, 0);
        },
        // Runs every frame, after update
        draw() {
            drawLine(this.pos, mousePos());
        },
        // Runs when obj is destroyed
        destroy() {
            debug.log("Oh bye :<");
        },
        // What to display in debug inspect mode
        inspect() {
            return "some state that deserves to be shown in inspect mode";
        },
    };
}

// Usage
const obj = add([
    sprite("bean"),
    pos(0, 0),
    area(),
    myComp(123), // <- here
]);

All KAPLAY built-in components are implemented this way. You can check the source code here. Also Check out the component demo.

TypeScript

If you’re using TypeScript, we recommend creating an specific type for your component:

// You can import types from kaplay package
import type { Comp } from "kaplay";

interface MyCustomComp extends Comp {
    // you just need to declare new stuff here
    myCustomProp: number;
    myCustomMethod: (arg: string) => void;
}

function myCustomComp(): MyCustomComp {
    return {
        id: "myCustomComp",
        myCustomProp: 123,
        myCustomMethod(arg) {
            console.log(arg);
        },
    };
}

When you want to use this inside any component method, but TypeScript doesn’t know what type it is, you can type this to GameObj<MyCustomComp>:

function myCustomComp(): MyCustomComp {
    return {
        id: "myCustomComp",
        myCustomMethod(this: GameObj<MyCustomComp>) {
            this.myCustomProp; // typed from MyCustomComp
            this.tags; // typed from GameObjRaw
        },
    };
}

Even better if you got a component that requires other components, you can use GameObj<YourComp | OtherComp> to type this.

function specie(specie: string): SpecieComp {
    return {
        id: "specie",
        require: ["color"],

        specie: specie,
        add() {
            this.tag(this.specie); // add specie tag
        }
        evolve(this: GameObj<SpecieComp>, specie: string) {
            this.untag(this.specie); // remove original specie tag
            this.specie = specie;
            this.tag(specie);
        },
        evil(this: GameObj<SpecieComp | ColorComp>) {
            // this.color is typed
            this.color = Color.RED;
        },
    };
}
kaplay logo

Expanding KAPLAY