Appearance
Entities
This chapter introduces the core entity system. Instead of updating everything in main.ts, we create reusable objects (entities) that own their own update and render logic, then let the engine manage them.
Why entities?
Beginners usually start with one big update() function. That works until the game grows. Entities let you split behavior into small, reusable pieces so each object “knows how to update itself.”
What we add
- A base
Entityclass andiEntityinterface. - An
EntityManagerto update and render a list of entities. - A simple
Vec2type for position/velocity. - Engine support for
addEntity()and anonInit()hook.
The Entity contract (lib, ch05)
Entities have position, alive state, and two methods:
ts
export interface iEntity {
position: Vec2;
alive: boolean;
update(deltaTime: number, screenSize?: Vec2): void;
render(ctx: CanvasRenderingContext2D): void;
}The abstract Entity base class just wires up the common fields so subclasses can focus on behavior.
EntityManager
EntityManager owns a list and handles the per‑frame lifecycle:
add()andremove()manage the list.update()calls each entity and removes any that are no longer alive.render()draws each entity.
This is the first step toward a real engine loop with actors.
Beginner tip: the alive flag is a simple way to remove objects without worrying about array mutations mid‑loop.
Player entity
Player extends Entity and encapsulates movement, rotation, and rendering. The shape is drawn in local space and then rotated at the player’s position.
The input logic is separated into PlayerController, which turns keyboard state into a rotation value and thrust toggle:
ts
getRotation(): number {
if(this.input.isDown("ArrowLeft")) return -1;
if(this.input.isDown("ArrowRight")) return 1;
return 0;
}
isThrusting(): boolean {
return this.input.isDown("ArrowUp");
}This pattern keeps input wiring out of the entity itself.
Why that matters: you can swap input sources later (keyboard, gamepad, AI) without changing the Player class.
Engine integration
The engine adds an onInit() hook and a convenience method:
onInit()is called after the canvas is created.addEntity()forwards to the entity manager.update()andrender()now delegate to the manager.
Demo flow
In main.ts, we create a player and register it during onInit():
ts
protected override onInit(): void {
const player = new Player(
{ x: this.ctx.canvas.width / 2, y: this.ctx.canvas.height / 2 },
new PlayerController(this.input),
);
this.addEntity(player);
}From this point on, the player updates and renders automatically every frame.
Pattern to remember
Entities own behavior; the engine owns orchestration. That separation keeps your game loop clean.
Library changes
- Added
lib/Entityandlib/EntityManager. - Added
lib/types/vec2andlib/types/index. - Engine now exposes
addEntity()and calls the entity manager each frame.