This post assumes basic knowledge of Kaboom.js and JavaScript. Kaboom.js is a JavaScript library for making games quickly. To learn how to use it, you can watch my project based tutorials on YouTube here or dive into the docs here.
I recently discovered that you could define custom events in Kaboom.js and they’re a game changer for making your codebase more maintainable.
For example, you can define a custom event and set up an event handler that will run when the you trigger that event.
There are two benefits with custom events :
You can reduce the amount of logic you need to write in onUpdate() methods.
You can neatly organize your code because you can write most of the code related to an entity in the same place.
To understand how they work, here is a simplified example :
const state = new StateManager() // assuming we have a way of managing global state
const healthBar = k.add([
// ... components omitted for conciseness
])
healthBar.on("update", () => {
healthBar.width = state.getPlayerHp() * someConstant
})
const player = k.add([
// ... components omitted for conciseness
]);
player.on("hit", () => {
if (state.getPlayerHp() !== 0) {
state.setPlayerHp(state.getPlayerHp() - 1);
healthBar.trigger("update")
return;
}
k.destroy(player);
});
player.onCollide("bullet", () => {
player.trigger("hit")
})
A custom event is defined by creating an event handler using the “on” method available on all game objects.
Here, for the health bar, we created an event called “update” that when triggered, will update the width of the health bar to reflect the current player health.
Before I knew about custom events, I would handle that logic in an onUpdate() method (which is called 60 times per second) where I would constantly update the width of the health bar to reflect the player health. With a custom event, the health bar is only updated when the player health changes.
If we continue with our example above, we then define a custom event for the player called “hit”. Its role is to remove health from the player when they get hit. We trigger the “hit” event by using the trigger method. In this case, we do this within an onCollide() that runs when a collision occurs with any game object having the “bullet” tag.
To recap, what happens is the following :
When the player collides with a bullet, the “hit” event is triggered.
The “hit” event handler reduces the health of the player and then triggers the “update” event of the health bar.
The “update” event handler updates the width of the health bar to reflect the current health of the player.
The takeaway is that with custom events you can organize your code so that all the logic related to a specific entity (ex: player, the bullet, the health bar, etc…) can be written in the same place and triggered from elsewhere when needed.
This makes it easier to avoid the scenario where you need to write the health bar updating logic where the player logic is defined. Therefore, avoiding finding yourself in a tangled mess.
It also avoids needing to write wasteful code in onUpdate() methods. Now, the health bar only updates when the health of the player updates instead of updating constantly.
In the end, custom events remind me of signals in Godot and I think they’re pretty neat. To not miss out more JS gamedev content, subscribe!
Whenever I publish it, you could write the docs for it...
React native skia is great... this my render...const Render = () => {
const { SCREEN_HEIGHT, SCREEN_WIDTH ,MAP_OFFSET} = useConstants();
const bg =useImage(require("../../assets/maps/world_1.png"))
const systems = useRegistry((state) => state.systems);
if (!systems[SystemType.RENDER]) {
return null;
}
return (
<Canvas style={{ width: SCREEN_WIDTH, height: SCREEN_HEIGHT }}>
<Image
x={0}
y={MAP_OFFSET}
//transform={[{scale:1.7}]}
width={211 * 16}
height={15 * 16}
image={bg}
/>
{
systems[SystemType.RENDER].entities.map((entity, index) => {
const pos = entity.components.find(
(c) => c.name === CompName.POSITION
) as PositionState;
const area = entity.components.find(
(c) => c.name === CompName.AREA
) as AreaState;
const mass = entity.components.find(
(c) => c.name === CompName.MASS
) as MassState;
const rect = entity.components.find(
(c) => c.name === CompName.RECT
) as RectState;
const Spritey = entity.components.find(
(c) => c.name === CompName.SPRITE
) as SpriteState;
const Color = entity.components.find(
(c) => c.name === CompName.COLOR
) as ColorState;
if (pos) {
//console.log("pos", pos.state.x.value, pos.state.y.value);
const spriteHeight = 50;
const spriteWidth = 50;
let areaOutlineRender = null;
let rectRender = null;
let entityRender = null;
if (Spritey) {
// console.log("Spritey", Spritey.state.sprite);
const sprite = ecs.getState().getSprite(Spritey.state.sprite)
//console.log("sprite", sprite);
entityRender = (
<Image
key={`sprite-${index}`}
x={pos.state.x}
y={pos.state.y}
width={spriteWidth}
height={spriteHeight}
image={sprite}
/>
);
}
if (rect) {
console.log("rect", rect.state.width, rect.state.height);
rectRender = (
<Rect
key={`rect-${index}`}
x={pos.state.x}
y={pos.state.y}
width={rect.state.width}
height={rect.state.height}
color={Color?.state.color}
/>
);
}
if (area) {
areaOutlineRender = (
<Rect
key={`stroke-${index}`}
x={pos.state.x}
y={pos.state.y}
width={area.state.width}
height={area.state.height}
style="stroke"
color="green"
strokeWidth={6}
/>
);
}
return [entityRender, areaOutlineRender, rectRender];
}
return []; // Return an empty array instead of void
})
}
</Canvas>
);
};
export default Render ;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});