Skip to content
Arnel Robles edited this page Oct 2, 2026 · 3 revisions

Engine

A small 2D engine: a kernel that runs systems, a scene of game objects, and components that hold the behaviour. It draws on a 2D canvas.

You do not need it for XP and achievements. It is there for mini games and for plugins.

import { Game, Scene, GameObject, Component } from 'dopaminejs/engine';

class Spin extends Component {
    update(dt) {
        this.gameObject.rotation += dt;
    }

    render(ctx) {
        ctx.fillStyle = '#ffd166';
        ctx.fillRect(-25, -25, 50, 50);
    }
}

const game = new Game({ width: 800, height: 600, backgroundColor: '#101820' });

const scene = new Scene();
const box = new GameObject(400, 300);
box.addComponent(new Spin());
scene.add(box);

game.setScene(scene);
game.start();

Game

Config Default Meaning
width, height 800, 600 Canvas size
backgroundColor '#000000' Fill used on clear
canvas none An existing canvas. Without one a canvas is created and appended.
renderer none Renderer options, merged over the four above. Also takes parent.
fixedTimestep 1 / 60 Seconds per physics step
Member Meaning
game.kernel The DopamineKernel
game.director Scene switching
game.renderer, ticker, physics, input Shortcuts to the kernel systems
game.start(), game.stop() Run or pause the loop
game.setScene(scene) Switch scene
game.destroy() Destroy the kernel, its systems and plugins

Scenes

class Level extends Scene {
    onEnter() { /* build the level */ }
    onExit() { /* tear it down */ }
}

game.setScene(new Level());
  • scene.add(obj) returns the object. scene.remove(obj) calls onDetach on its components and on its children's components.
  • Switching scenes calls onExit on the old scene and onEnter on the new one. The old scene's colliders leave physics.
  • An object removed and added again has onAttach called again.

There is no addGameObject and no changeScene.

Game objects

const player = new GameObject(100, 200);
player.rotation = Math.PI / 4;
player.scale.x = 2;

player.addChild(new GameObject(0, -40));
const body = player.addComponent(new Collider('box', 40, 60));
player.getComponent(Collider);

A game object has x, y, rotation, scale: { x, y }, components, children and parent. There is no position property.

Rendering translates, rotates and scales the canvas for the object, so a component draws around (0, 0). Children inherit the transform.

Components

class Player extends Component {
    onAttach() {}            // added to an object
    onDetach() {}            // object removed from the scene
    update(dt) {}            // every frame, dt in seconds
    render(ctx) {}           // every frame, 2D context
    onCollisionEnter(other) {}   // other is the GameObject hit
}

Inside a component: this.gameObject, this.kernel, and the shortcuts this.physics, this.input, this.loader, this.events. They are available once the object is in a scene that belongs to a game.

Anything a component adds (a listener, a timer, a DOM node) it removes in onDetach.

The loop

Each frame, in order:

  1. tick event with { dt }. The game updates the current scene here.
  2. Zero or more fixed steps: fixed_update event, then every system's fixedUpdate(dt). At most 5 steps per frame.
  3. Every system's update(dt).
  4. render event. The game clears the canvas and draws the scene here.

dt is capped at 0.1 seconds, so a background tab does not produce one huge step.

Built-in systems

Name Priority Reach it with
ticker 100 kernel.ticker
renderer 90 kernel.renderer
input 80 kernel.input
loader 70 kernel.loader
physics 60 kernel.physics
director 0 game.director

There is no audio or particles system until a plugin registers one.

Input

this.input.isKeyDown('ArrowLeft');    // KeyboardEvent.key
this.input.isMouseButtonDown(0);
this.input.mouse.x;                   // client coordinates

Listeners are on window. Mouse coordinates are client coordinates, not canvas coordinates.

Loader

await game.kernel.loader.loadImage('hero', '/img/hero.png');
const image = game.kernel.loader.get('hero');

Physics

Overlap detection only. No forces, no gravity, no resolution.

import { Collider } from 'dopaminejs/engine';

const collider = new Collider('box', 40, 60);    // or new Collider('circle', 0, 0, 20)
collider.tag = 'enemy';
enemy.addComponent(collider);

const hits = game.physics.checkOverlap(playerCollider, 'enemy');   // array of colliders
  • A collider is centred on its object and registers itself with physics.
  • Box against box and circle against circle are detected. Box against circle is not.
  • Every pair is tested each fixed step, so cost grows with the square of the collider count.
  • While two colliders overlap, onCollisionEnter(other) is called on both objects' components every fixed step. Components get no exit callback.
  • Since 2.4.0 physics also emits collision_enter once when a pair starts to overlap and collision_exit once when it stops, or when one of the two colliders is removed. Both carry { a, b }, the two colliders.
  • getBounds() returns { left, right, top, bottom } for a box and { x, y, radius } for a circle.
  • raycast returns null and setGravity does nothing.
const onEnter = ({ a, b }) => {
    if (a.tag === 'enemy' || b.tag === 'enemy') hurtPlayer();
};

game.kernel.events.on('collision_enter', onEnter);
game.kernel.events.off('collision_enter', onEnter);

Sprites and animation

import { Sprite, Animator } from 'dopaminejs/engine';

const sprite = hero.addComponent(new Sprite(image));
const animator = hero.addComponent(new Animator());

animator.addAnimation('run', [
    { x: 0, y: 0, w: 32, h: 32 },
    { x: 32, y: 0, w: 32, h: 32 }
], 12, true);   // fps, loop

animator.play('run');

Animator needs a Sprite on the same object. Sprite also has anchor, setFrame(x, y, w, h) and setTexture(image).

Components that ship with the core

These come from the main entry, not dopaminejs/engine.

import { ScreenShake, ParticleEmitter, ParticleSystem, TextureGenerator } from 'dopaminejs';

const shake = camera.addComponent(new ScreenShake());
shake.shake(8, 0.3);   // intensity in pixels, seconds

const emitter = coin.addComponent(new ParticleEmitter(new ParticleSystem()));
emitter.play('confetti');

TextureGenerator.createGround and createCharacterSheet draw placeholder textures on a canvas, for prototypes with no art.

Vector2

add, sub, scale and normalize change the vector and return it. Use clone() first to keep the original. distance(v) and Vector2.distance(a, b) return a number.

The v1 globals

GlobalPhysics, GlobalInput and GlobalLoader still work and log one deprecation warning on first use. They are not connected to a Game. Use game.kernel.physics, or this.physics in a component.

Next

Clone this wiki locally