webpunk.ts v0.2.1

API Reference

All public API is imported by name from the webpunk.ts package entry — no path aliases needed.

import { Engine, CanvasRenderer, Vector2 } from 'webpunk.ts'

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: HTMLCanvasElement

The canvas element to render into.

renderer: IRenderer

A CanvasRenderer instance.

debug: boolean?

Enables the debug overlay.

saveProvider: ISaveProvider?

Custom save backend (defaults to localStorage).

engine.renderer: IRenderer

The active renderer.

engine.assets: AssetLoader

Load textures, audio, and fonts.

engine.input: InputManager

Raw key / mouse state.

engine.actions: ActionMap

Named action bindings.

engine.camera: Camera

Camera position and layers.

engine.collision: CollisionSystem

Register / unregister colliders.

engine.audio: AudioManager

SFX and BGM playback.

engine.events: EventEmitter<GameEventMap>

Global typed event bus.

engine.save: SaveManager

Persistent save data.

engine.ui: UIManager

Screen-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 below

Scenes 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-in

Push 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-in

Displayed 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): T

Attach a component; sets c.entity = this.

getComponent<T>(Type): T | undefined

Find the first component of a type.

getComponents<T>(Type): T[]

Find all components of a type.

hasComponent<T>(Type): boolean

Check if a component type exists.

removeComponent<T>(Type): void

Detach and call onDetach.

entity.active: boolean

When false, update and render are skipped.

entity.destroy(): void

Marks entity for removal at end of frame.

Transform.position: Vector2

Local position in world pixels (the entity centre).

Transform.scale: Vector2

Local scale.

Transform.rotation: number

Rotation in radians.

Transform.worldPosition

Computed world-space position (read-only).

Transform.setParent(t | null)

Attach to a parent Transform.

SpriteRenderer.sprite: Sprite | null

The sprite to draw (from a SpriteSheet).

SpriteRenderer.drawWidth / drawHeight: number | null

Override 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: Vector2

Camera world position (top-left corner).

camera.viewport

Sub-rectangle of the logical screen the world renders into. Shrink to create margins.

camera.controller

Assign an ICameraController for automatic movement.

FollowControllerbuilt-in

Smoothly 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 / popTransform

Save 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 / logicalHeight

Configured 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: Vector2

Mouse 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 / height

Collider size in logical pixels (default 16).

BoxCollider.offset: Vector2

Offset from entity position. Use (-w/2, -h/2) to centre.

BoxCollider.layer

Bit-flag layer this collider sits on.

BoxCollider.mask

Bit-flag layers this collider tests against.

BoxCollider.isStatic: boolean

If true, immovable (walls, platforms).

BoxCollider.isTrigger: boolean

If 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 / onTriggerExit

Fired for trigger colliders.

CollisionFace: None | Top | Bottom | Left | Right

Which 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 / heightInPixels

Map size in world pixels.

map.tileLayers

Array of TileLayerData (name, tiles[][], collidable).

map.objectLayers

Array 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.

AnimationClip

Object 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: Anchor

Position relative to the configured logical resolution.

UIElement.offset: Vector2

Offset from the anchor point.

UIElement.visible / themed

Show/hide element. themed = false opts out of theme application.

UIText

Single line of text. Properties: text, color, fontSize, font, bitmapFont.

UIPanel

Filled/outlined rectangle. Properties: fillColor, borderColor, borderWidth.

UIProgressBar

Fill bar for health, loading, etc. Properties: value (0–1), orientation, fillColor.

UIButton

Clickable button. Pass engine.input to constructor. Properties: label, onClick, normal/hover/pressed states.

UIImage

Draws a Sprite. Set sprite + optional width/height to scale. Set insets for nine-slice.

UIGrid

Grid of cells. Configure columns, rows, cellSize, padding. Fill with setCell(row, col, data).

Anchor

Enum: 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 / normalize

Returns a new Vector2 (immutable operations).

v.dot(other)

Dot product.

v.lerp(target, t)

Linear interpolation. t = 0–1.

v.magnitude

Length of the vector.

v.clone()

Deep copy.

Vector2.ZERO / UP / DOWN / LEFT / RIGHT

Convenience 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.isComplete

True once finished (non-looping).

tween.cancel()

Stop without firing onComplete.

tween.reset()

Restart from the beginning.

Easing.linear

No easing.

Easing.easeIn/OutQuad / Cubic / Back

Standard 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.