Repository navigation
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();| 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 |
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)callsonDetachon its components and on its children's components. - Switching scenes calls
onExiton the old scene andonEnteron the new one. The old scene's colliders leave physics. - An object removed and added again has
onAttachcalled again.
There is no addGameObject and no changeScene.
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.
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.
Each frame, in order:
-
tickevent with{ dt }. The game updates the current scene here. - Zero or more fixed steps:
fixed_updateevent, then every system'sfixedUpdate(dt). At most 5 steps per frame. - Every system's
update(dt). -
renderevent. 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.
| 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.
this.input.isKeyDown('ArrowLeft'); // KeyboardEvent.key
this.input.isMouseButtonDown(0);
this.input.mouse.x; // client coordinatesListeners are on window. Mouse coordinates are client coordinates, not canvas coordinates.
await game.kernel.loader.loadImage('hero', '/img/hero.png');
const image = game.kernel.loader.get('hero');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_enteronce when a pair starts to overlap andcollision_exitonce 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. -
raycastreturnsnullandsetGravitydoes 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);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).
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.
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.
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.
Start
Progression
Engine
Packages
Reference