Injected Globals
Open the editor to use these globals live.
Your entry file runs inside a function. These parameters are in scope automatically — do not declare them.
Module files do not receive these globals. Pass dt, state, w, and h as function arguments from the entry file. See Multi-File Imports. (assets and ai are the exceptions — both are available in module files too.)
Quick Reference
| Global | Type | Description |
|---|---|---|
time | number | Cumulative simulation time in seconds |
dt | number | Delta time since last frame (seconds) |
state | object | Mutable object that persists across frames |
keys | object | Currently pressed keys ({ ArrowUp: true, w: true, ... }) |
w | number | Canvas width in CSS pixels |
h | number | Canvas height in CSS pixels |
assets | object | Asset library: images, sprite sheets, and tile grids |
ai | module | Not a global — import it (import ai from 'ai'): steering, flocking, and live cursor perception |
time
return {
text: { elapsed: time.toFixed(1) + 's' },
}| Property | Detail |
|---|---|
| Type | number |
| Unit | Seconds |
| Starts at | 0 |
| Increases by | dt each frame |
| Resets when | Restart, Reset, or code recompile |
time is simulation time, not wall clock. While paused, it does not advance.
dt
state.x += state.vx * dt;| Property | Detail |
|---|---|
| Type | number |
| Unit | Seconds |
| Typical value | ~0.016 (60 FPS) |
| Maximum | 0.05 (20 FPS floor) |
dt is clamped to 50ms so a tab regain does not explode the simulation.
MATH
Euler step: position += velocity * dt. Always multiply forces and velocities by dt so motion stays frame-rate independent.
WARNING
Never assume dt is constant. It fluctuates with load. Fixed increments like state.x += 2 make speed depend on FPS.
state
if (!state.init) {
state.x = 100;
state.vx = 0;
state.init = true;
}
state.vx += 50 * dt;
state.x += state.vx * dt;| Property | Detail |
|---|---|
| Type | Record<string, any> |
| Initial value | {} (plus click fields when present) |
| Persists | Across frames until cleared |
| Cleared by | Restart, Reset, page reload, or recompile after edit |
Store positions, velocities, arrays, flags — anything. The engine does not read your custom keys except the click fields below.
Pre-injected Properties
On canvas click the host writes:
| Property | Type | Description |
|---|---|---|
state.clickX | number | Canvas-local X of the click |
state.clickY | number | Canvas-local Y of the click |
Consume them by setting both to undefined after handling. See Mouse & Click Tracking.
keys
if (keys.ArrowUp || keys.w) {
state.y -= 100 * dt;
}| Property | Detail |
|---|---|
| Type | Record<string, boolean> |
| Initial value | {} |
| Updated | Every keydown / keyup on window |
| Cleared when | Window loses focus (blur) |
keys is an object whose properties are set to true while a key is held and false when released. Key names follow event.key — use ArrowUp, ArrowDown, ArrowLeft, ArrowRight for arrows, single characters for letter keys (w, a, s, d, r), and for space.
GAME BUILDING
With keys you can build any game — platformers, shooters, puzzles, Snake, Pong, Breakout, and more. Combine with state for game state, dt for frame-rate-independent physics, and mouse clicks for additional input.
w and h
const cx = w / 2;
const cy = h / 2;
return {
particles: [
{ x: cx, y: cy, r: 20, color: '#8B5CF6' },
],
};| Property | Type | Description |
|---|---|---|
w | number | Canvas width in CSS pixels |
h | number | Canvas height in CSS pixels |
These change when the window or editor panel is resized. Prefer w / h over hardcoded positions.
TIP
Center: const cx = w / 2, cy = h / 2. Bottom: h. Right: w.
assets
The built-in asset library. Pick assets from the Assets tab in the editor's left panel — clicking one inserts a sprite snippet into your code.
return {
sprites: [
{ asset: 'ship-0', x: w / 2, y: h / 2 },
],
}| Method | Returns | Description |
|---|---|---|
assets.image(id) | Image | null | Loaded Image for an asset id |
assets.size(id) | { w, h } | null | Natural pixel size |
assets.isLoaded(id) | boolean | Whether the image is ready |
assets.frame(id, index, cols) | { sx, sy, sw, sh } | null | Source crop for one frame of a sprite sheet |
assets.tiles(id, opts) | Sprite[] | Lays out a grid of sprites (see below) |
assets.tilemap(id, map, opts) | Sprite[] | Renders a 2D tile map from a sheet (see below) |
assets.list() | AssetListItem[] | All assets: { id, src, category, w, h } |
assets.frame
Crops one frame out of a sprite sheet. cols is how many columns the sheet has; frames are square cells read left-to-right, top-to-bottom.
sprites: [
{ asset: 'sheet-tiles', x: 50, y: 50, w: 32, h: 32, src: assets.frame('sheet-tiles', 0, 12) },
]null when the frame is out of range — pass it to src and the sprite is skipped.
assets.tiles
Returns an array of sprites forming a centered grid — perfect for tile maps, floors, and platforms.
sprites: [
...assets.tiles('sheet-tiles', { cols: 6, rows: 3, size: 32, x: w / 2, y: h / 2 }),
]| Option | Type | Default | Description |
|---|---|---|---|
cols | number | — | Columns in the grid |
rows | number | — | Rows in the grid |
size | number | natural width | Display size of each tile |
x | number | 0 | Grid center X |
y | number | 0 | Grid center Y |
For a plain tile texture (not a sheet) assets.tiles repeats that single image across the grid; for a sheet it steps through frames automatically.
assets.tilemap
Renders a 2D tile map from a sprite sheet. Each cell is a sprite positioned on a centered grid; 0 and null cells are skipped (empty space).
const MAP = [
[1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 2, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1],
]
sprites: [
...assets.tilemap('sheet-tiles', MAP, { tileSize: 32, x: w / 2, y: h / 2 }),
]| Option | Type | Default | Description |
|---|---|---|---|
tileSize | number | natural cell width | Display size of each tile |
x | number | 0 | Grid center X |
y | number | 0 | Grid center Y |
Map cells are number | string | null: a number indexes a frame of the sheet (see assets.frame), a string is a single-image asset id, and null (or 0) leaves the cell empty. The Level editor in the games editor writes maps like this to level.js for you — pick a sheet, paint, drop items, and export.
An exported level.js also exposes the map as game data, so your code can read the exact grid the editor shows (collision, spawn points, collectibles):
| Export | Description |
|---|---|
tileMap | The cell grid as an array of rows (frame numbers; 0/null are empty) |
tileSize | The cell size in pixels used for rendering and collision |
tileItems | Collectibles placed in the editor, as { asset, x, y, w, h } (absolute centers) |
buildLevel returns only the tile sprites — collectibles are not baked in, so your game code can remove them when collected (see the Tile Dash starter in the games picker).
Categories
Assets ship in five categories: characters, items, tiles, effects, and backgrounds. The curated library ships with Eyedially — everything is freely usable (Kenney CC0 art + generated pixel art).
GAME BUILDING
assets + sprites + keys + state is everything you need to build platformers, shooters, and collect-them-all games. See the Sprite cookbook.
ai
Behavioral AI for simulations that perceive, decide, and act. Unlike the globals above, ai is a built-in module — it is not injected as a global. Import it in any project (the editor, the public /p/:id page, and embeds all ship the same module), then use ai.* to give particles intelligence: react to the live cursor, flock with neighbors, and steer around obstacles.
import ai from 'ai';
import { seek, flock } from 'ai';Using ai without an import throws "ai is not defined" — add the import to every file that uses it.
The full tutorial with demos is in the AI cookbook. Quick start — a particle that chases your cursor:
import ai from 'ai';
if (!state.p) {
state.p = ai.agent({
x: 100, y: 100,
maxSpeed: 180, maxForce: 90,
brain: (me, sense) => ai.seek(me, sense.mouse.x, sense.mouse.y, 90),
});
}
ai.think(state.p, ai.sense(state.p, w, h, time), dt);
ai.wrap(state.p, w, h);| Helper | Description |
|---|---|
ai.agent({ x, y, vx, vy, maxSpeed, maxForce, brain }) | Create an agent with an optional brain(agent, sense) => { fx, fy } |
ai.think(agent, sense, dt) | Run the brain, apply force, clamp speed, move |
ai.mouse() | Live cursor { x, y, active } over the canvas |
ai.sense(agent, w, h, time) | Global perception { mouse, bounds, time, w, h } |
ai.neighbors(agents, agent, radius) | Agents within radius (excludes self) |
ai.seek / flee / arrive / pursue / evade / wander | Steering forces |
ai.separate / align / cohesion / flock | Flocking rules |
ai.avoid(agent, obstacles, opts) | Obstacle avoidance ({ x, y, r } circles) |
ai.limitSpeed / wrap / keepInBounds | Motion helpers |
ai.mouse() is the only way to read the live pointer position — clicks are still available as state.clickX/state.clickY.
How Injection Works
new Function('time', 'dt', 'state', 'w', 'h', 'keys', 'assets', '__modules',
`"use strict"; return (() => { YOUR_CODE })()`
)Your code runs in an IIFE in strict mode. __modules is the internal import registry — use named import syntax, not __modules directly.
Consequences:
- Top-level
let/const/varreset every frame - Only
state.*persists between frames - Helper functions defined in the entry file are recreated each frame (fine at 60 FPS)