API Reference
All public API is imported by name from the webpunk.ts package entry — no path aliases needed.
The central controller. Creates the game loop, manages the scene stack, and exposes all subsystems via IEngine.
new Engine(config)Create and configure the engine.
const engine = new Engine({ canvas, renderer })canvas: HTMLCanvasElementThe canvas element to render into.
renderer: IRendererA CanvasRenderer instance.
debug: boolean?Enables the debug overlay.
saveProvider: ISaveProvider?Custom save backend (defaults to localStorage).
engine.renderer: IRendererThe active renderer.
engine.assets: AssetLoaderLoad textures, audio, and fonts.
engine.input: InputManagerRaw key / mouse state.
engine.actions: ActionMapNamed action bindings.
engine.camera: CameraCamera position and layers.
engine.collision: CollisionSystemRegister / unregister colliders.
engine.audio: AudioManagerSFX and BGM playback.
engine.events: EventEmitter<GameEventMap>Global typed event bus.
engine.save: SaveManagerPersistent save data.
engine.ui: UIManagerScreen-space UI canvas management.
Example
engine.replaceScene(new GameScene()) // swap current scene
engine.pushScene(new PauseScene()) // overlay — current scene pauses
engine.popScene() // resume previous
engine.replaceSceneUnder(new GameScene()) // replace the scene belowScenes are the top-level unit of game state. Only the top of the scene stack is active.
preload(engine, reportProgress): Promise<void>Optional. Load assets; call reportProgress(0–1) as they load. Drives the built-in loading bar.
onEnter(engine)Called when the scene becomes active. Set up layers and entities here.
onExit()Called when the scene is removed. Clean up layers and event listeners.
onPause()Called when another scene is pushed on top.
onResume()Called when the scene is restored to the top.
fixedUpdate(dt)Called at 60 Hz. Use for physics and deterministic logic. dt is in milliseconds.
update(dt)Called once per frame. Use for input handling and animations.
render(renderer, ip)Called once per frame. ip is a 0–1 interpolation value for smoothing.
FadeScenebuilt-inPush to cross-fade between two scenes. Fades to black, fires onMidpoint, then fades back.
engine.pushScene(new FadeScene(300, (eng) => {
eng.replaceSceneUnder(new GameScene())
}))LoadingScenebuilt-inDisplayed automatically when a scene has a preload() method. Renders a smooth progress bar.
Entities own components. Components implement logic and rendering. Subclass Entity and add components in the constructor.
addComponent<T>(c: T): TAttach a component; sets c.entity = this.
getComponent<T>(Type): T | undefinedFind the first component of a type.
getComponents<T>(Type): T[]Find all components of a type.
hasComponent<T>(Type): booleanCheck if a component type exists.
removeComponent<T>(Type): voidDetach and call onDetach.
entity.active: booleanWhen false, update and render are skipped.
entity.destroy(): voidMarks entity for removal at end of frame.
Transform.position: Vector2Local position in world pixels (the entity centre).
Transform.scale: Vector2Local scale.
Transform.rotation: numberRotation in radians.
Transform.worldPositionComputed world-space position (read-only).
Transform.setParent(t | null)Attach to a parent Transform.
SpriteRenderer.sprite: Sprite | nullThe sprite to draw (from a SpriteSheet).
SpriteRenderer.drawWidth / drawHeight: number | nullOverride draw size. null = use sprite size.
Animator.setClips({ idle, run, ... })Register named AnimationClips.
Animator.play(name)Switch to a looping clip.
Animator.playOnce(name, onFinish)Play once, call callback when done.
HealthComponent(maxHp)Tracks HP. Exposes takeDamage(), heal(), ratio (0–1), isDead.
Example
class Player extends Entity {
constructor(tex: Texture, x: number, y: number) {
super('player')
const t = this.addComponent(new Transform())
t.position = new Vector2(x, y)
const sheet = new SpriteSheet(tex, 16)
const sr = this.addComponent(new SpriteRenderer())
sr.sprite = sheet.sprite(2, 0)
const col = this.addComponent(new BoxCollider())
col.width = 12; col.height = 12
col.layer = CollisionLayer.Player
col.mask = CollisionLayer.Enemy
}
}The camera renders named layers in z-order. World layers get the camera transform; UI layers are screen-space.
camera.addWorldLayer(name, order, fn)Register a world-space layer. fn receives (renderer, interpolation). Lower order = behind.
camera.addUILayer(name, order, fn)Register a screen-space layer (HUD, menus). No camera transform applied.
camera.removeLayer(name)Remove a layer by name.
camera.clearLayers()Remove all layers (call in onExit).
camera.position: Vector2Camera world position (top-left corner).
camera.viewportSub-rectangle of the logical screen the world renders into. Shrink to create margins.
camera.controllerAssign an ICameraController for automatic movement.
FollowControllerbuilt-inSmoothly follows an entity with optional deadzone and map clamping.
const ctrl = new FollowController({
lerpFactor: 0.1,
deadzone: { x: 8, y: 6 },
mapBounds: { width: 1536, height: 512 },
})
engine.camera.controller = ctrl
ctrl.follow(player)All coordinates are in logical pixels (default 320×240). The renderer scales to the physical canvas automatically.
new CanvasRenderer(canvas, options?)Create the renderer. resolution defaults to 320×240.
scaling.mode: 'integer' | 'fit' | 'stretch' | 'fixed'integer = largest whole multiple (default). fit = aspect-preserving. stretch = fills both axes. fixed = exact scale.
scaling.filter: 'pixelated' | 'smooth'pixelated (default) = crisp nearest-neighbour. smooth = bilinear for a faux-CRT blur.
clear(color?)Fill the canvas. Defaults to black.
drawImage(image, srcRect, dstRect)Draw a sprite or canvas region.
drawRect(rect, color, fill?)Draw a filled (default) or outlined rectangle.
drawCircle(center, radius, color, fill?)Draw a filled (default) or outlined circle.
drawLine(from, to, color, lineWidth?)Draw a line.
drawText(text, position, style)Draw text. style: { color, size, font?, align? }. align: 'left' | 'center' | 'right'.
pushTransform / popTransformSave and restore state with an offset/scale.
pushClip(rect) / popClip()Clip subsequent drawing to a logical-pixel rect.
toggleScaleFilter()Toggle between pixelated and smooth upscale filter at runtime.
logicalWidth / logicalHeightConfigured logical resolution (default 320 / 240).
Define named actions in main.ts and query them anywhere. ActionMap is preferred over raw InputManager.
actions.defineAction(name, bindings)Bind keys/mouse buttons to a named action. Call once in main.ts.
actions.isActionPressed(name)True for one frame when the action fires.
actions.isActionHeld(name)True every frame while held.
actions.isActionReleased(name)True for one frame on release.
input.isKeyHeld(code)True while a key is held (raw KeyboardEvent code).
input.isKeyPressed(code)True for one frame on press.
input.isKeyReleased(code)True for one frame on release.
input.mousePosition: Vector2Mouse position in logical pixels.
input.isMousePressed(button)True for one frame on click. button 0 = left.
Example
// In main.ts — define once:
engine.actions.defineAction('jump', [
{ type: 'key', code: 'Space' },
{ type: 'key', code: 'ArrowUp' },
])
// In scenes/entities — query anywhere:
if (engine.actions.isActionPressed('jump')) { ... }Register colliders in onEnter, unregister in onExit. The engine resolves collisions after each fixedUpdate.
BoxCollider.width / heightCollider size in logical pixels (default 16).
BoxCollider.offset: Vector2Offset from entity position. Use (-w/2, -h/2) to centre.
BoxCollider.layerBit-flag layer this collider sits on.
BoxCollider.maskBit-flag layers this collider tests against.
BoxCollider.isStatic: booleanIf true, immovable (walls, platforms).
BoxCollider.isTrigger: booleanIf true, no physics response — fires onTriggerEnter/Exit only.
onCollisionEnter(other, face)Fired when collision starts. face is relative to this collider.
onCollisionExit(other)Fired when collision ends.
onTriggerEnter / onTriggerExitFired for trigger colliders.
CollisionFace: None | Top | Bottom | Left | RightWhich side of THIS collider was hit.
collision.register(collider)Register a collider with the collision system.
collision.unregister(collider)Unregister a collider.
collision.setTileMap(map)Hand a TileMap to the collision system for tile collision.
collision.resolveTileCollision(boxCollider)Resolve a BoxCollider against solid tiles. Returns CollisionFace.
Create maps in Tiled and export as JSON (.tmj). TileMapRenderer pre-bakes each layer to an OffscreenCanvas.
new TiledJsonLoader(assets).load(path)Load a .tmj map. The tileset image is loaded automatically.
TileMapRenderer.setMap(map)Assign a TileMap. Pre-bakes tile layers on next render.
map.widthInPixels / heightInPixelsMap size in world pixels.
map.tileLayersArray of TileLayerData (name, tiles[][], collidable).
map.objectLayersArray of ObjectLayerData (name, objects[]).
map.getTileGid(layer, col, row)Get the global tile ID at a position.
map.isTileCollidableAt(col, row)True if the tile is solid.
map.isCollidable(worldX, worldY)True if a world position is on a solid tile.
map.worldToTile(x, y)Convert world coords to { col, row }.
map.tileToWorld(col, row)Convert tile coords to Vector2 world position.
new SpriteSheet(texture, tileW, tileH)Divide a texture into uniform tiles.
sheet.sprite(col, row)Get a single Sprite at (col, row). Zero-based.
sheet.row(row, startCol, count)Get count frames starting at startCol in a row.
AnimationClipObject with name, loop (boolean), and frames array of { sprite, duration }.
AnimationClipLoader.load(path)Load AnimationClips from a JSON descriptor file.
Example
const sheet = new SpriteSheet(texture, 16, 16)
const clip: AnimationClip = {
name: 'run', loop: true,
frames: sheet.row(1, 0, 6).map(s => ({ sprite: s, duration: 80 })),
}
const anim = entity.addComponent(new Animator())
anim.setClips({ idle: idleClip, run: clip })
anim.play('run')Screen-space widget system. Widgets are added to named UICanvas containers managed by UIManager (engine.ui).
engine.ui.add(canvas)Register a UICanvas and return it.
engine.ui.setTheme(theme)Apply a UITheme globally to all canvases.
engine.ui.clear()Remove all canvases (called automatically when scenes exit).
UICanvas(name, sortOrder)Named container for UI elements.
canvas.addElement(element)Add a UIElement; returns it for chaining.
UIElement.anchor: AnchorPosition relative to the configured logical resolution.
UIElement.offset: Vector2Offset from the anchor point.
UIElement.visible / themedShow/hide element. themed = false opts out of theme application.
UITextSingle line of text. Properties: text, color, fontSize, font, bitmapFont.
UIPanelFilled/outlined rectangle. Properties: fillColor, borderColor, borderWidth.
UIProgressBarFill bar for health, loading, etc. Properties: value (0–1), orientation, fillColor.
UIButtonClickable button. Pass engine.input to constructor. Properties: label, onClick, normal/hover/pressed states.
UIImageDraws a Sprite. Set sprite + optional width/height to scale. Set insets for nine-slice.
UIGridGrid of cells. Configure columns, rows, cellSize, padding. Fill with setCell(row, col, data).
AnchorEnum: TopLeft, TopCenter, TopRight, MiddleLeft, Center, MiddleRight, BottomLeft, BottomCenter, BottomRight.
UITheme.createDefault(options?)Procedural skin (no art assets). Options: fill, border, accent, text, fontFamily, radius, smoothing.
UITheme.load(path, assets)Load a skin from a JSON descriptor (atlas, regions, colors, font).
nineSlice(sprite, insets)Create a nine-patch UIBackground strategy.
solid({ fill, border })Create a flat-color UIBackground strategy.
Accessed via engine.audio. Load audio with engine.assets.loadAudio() first.
audio.playSFX(buffer, volume?)Play a sound effect. volume 0–1 (default 1).
audio.playBGM(buffer, fadeDurationMs?)Play background music with an optional fade-in.
audio.stopBGM(fadeDurationMs?)Stop BGM with an optional fade-out.
audio.setSFXVolume(volume)Set global SFX volume (0–1).
audio.setBGMVolume(volume)Set global BGM volume (0–1).
Accessed via engine.assets. Assets are served from public/ and are cached — loading the same path twice returns the cached value.
assets.loadTexture(path)Load a texture from public/. Returns a Texture.
assets.loadAudio(path)Load an audio buffer from public/.
assets.preloadTextures(paths, onProgress)Batch preload textures with progress callback.
assets.loadFont(family, url, opts?)Register a custom font via FontFace API. Idempotent per family|url.
assets.loadGoogleFonts(...families)Inject Google Fonts and resolve once ready to paint. Never rejects on CDN failure.
Example
// Preload in scene.preload():
const tex = await engine.assets.loadTexture('/sprites/player.png')
await engine.assets.loadFont('Science Gothic', '/fonts/ScienceGothic.ttf')
await engine.assets.loadGoogleFonts('Press Start 2P')Accessed via engine.events. Events are typed via GameEventMap — augment it in events.d.ts for custom events.
events.on(event, handler)Subscribe to a typed event.
events.emit(event, payload)Emit a typed event.
events.off(event, handler)Unsubscribe a handler.
events.clear(event)Remove all handlers for an event.
Example
// events.d.ts — add custom events:
declare module 'webpunk.ts' {
interface GameEventMap {
'player:died': Record<string, never>
'coin:collected': { value: number }
}
}
// Usage:
engine.events.on('coin:collected', ({ value }) => { score += value })
engine.events.emit('coin:collected', { value: 100 })Accessed via engine.save. Defaults to localStorage. Swap the provider for IndexedDB, Tauri filesystem, etc.
save.save(key, data)Serialize and save data to the key.
save.load(key, defaultValue)Load data or return defaultValue if missing.
save.has(key)Check if a key exists.
save.delete(key)Delete a saved key.
save.slot(name)Return a scoped SaveManager with a key prefix.
new Vector2(x, y)Create a 2D vector.
v.add / sub / scale / normalizeReturns a new Vector2 (immutable operations).
v.dot(other)Dot product.
v.lerp(target, t)Linear interpolation. t = 0–1.
v.magnitudeLength of the vector.
v.clone()Deep copy.
Vector2.ZERO / UP / DOWN / LEFT / RIGHTConvenience constants.
new Rect(x, y, width, height)Axis-aligned rectangle.
r.contains(point) / intersects(other)Point and AABB tests.
r.intersection(other)Returns the overlapping Rect or null.
Rect.fromCenter(point, w, h)Construct a Rect centred on a point.
Tweens interpolate a number from one value to another. They are manually ticked — call tween.tick(dt) in update(dt).
new Tween({ from, to, duration, easing, onUpdate, onComplete?, loop?, pingPong? })Create a tween. duration is in milliseconds.
tween.tick(dt)Advance the tween. Call in update(dt).
tween.isCompleteTrue once finished (non-looping).
tween.cancel()Stop without firing onComplete.
tween.reset()Restart from the beginning.
Easing.linearNo easing.
Easing.easeIn/OutQuad / Cubic / BackStandard easing functions. All take t ∈ [0,1] and return [0,1].
Example
// Fade out then pop the scene:
const fade = new Tween({
from: 1, to: 0, duration: 400,
easing: Easing.easeOutQuad,
onUpdate: (v) => { this._alpha = v },
onComplete: () => engine.popScene(),
})
// In update(dt):
fade.tick(dt)
// PingPong pulse:
const pulse = new Tween({
from: 7, to: 9, duration: 900,
easing: Easing.easeInOutQuad,
pingPong: true,
onUpdate: (v) => { this._textSize = v },
})Full TypeScript typings
Every public API ships with complete .d.ts declarations. Your IDE will autocomplete everything. The types live at node_modules/webpunk.ts/dist/index.d.ts.