# Getting Started with Emerald
A practical walkthrough that takes you from an empty page to a small game:
a player sprite you can move with keyboard **or gamepad**, tiles to stand on,
sound, saving, and a debug overlay to keep you honest. Every snippet uses real
engine APIs — copy them as-is.
For the full API surface see the [README](../README.md); for the level editor
see [Emerald Tile Forge](../../emerald-tile-forge/README.md).
## 1. Setup
```html
```
```js
import { Emerald, Scene, Color, InputManager } from "emeraldengine";
const canvas = document.getElementById("game");
const emerald = new Emerald(canvas); // WebGL2 context + default camera
emerald.setBackgroundColor(new Color(30, 34, 50, 255));
const scene = new Scene();
const input = new InputManager();
function resize() {
emerald.resize(window.innerWidth, window.innerHeight);
}
window.addEventListener("resize", resize);
resize();
emerald.run((dt) => {
scene.update(dt); // ticks Behaviours, physics sync, animations
emerald.drawScene(scene, dt); // renders all cameras (+ post effects)
input.update(); // advances justPressed/justReleased edges
});
```
`run()` also accepts options: `{ pauseOnBlur: true, fixedStep: 1/60, fixedUpdate }`
for background-tab pausing and fixed-timestep physics.
## 2. A sprite with animation
> **About the asset paths.** `assets/hero.png`, `assets/tiles.png` and the audio
> files below are placeholders — point them at your own files. Loading is
> asynchronous, so a missing file never stops your game: the object just renders
> nothing and the console logs
> `Could not initialize texture "assets/hero.png"`. If a sprite is invisible,
> check that path first. The frame numbers assume a 16x16 spritesheet with 8
> frames per row — match them to your own sheet.
```js
import { GameObject, Texture, Animator, Vector2, Vector3 } from "emeraldengine";
const player = new GameObject("player", new Vector3(0, 0, 0), 0, new Vector2(48, 48));
// (path, frameW, frameH, framesPerRow, totalFrames, msPerFrame, autoPlay, pixelart, useLighting)
const tex = new Texture("assets/hero.png", 16, 16, 8, 32, 120, false, true, false);
player.addComponent(tex);
const anim = new Animator();
anim.addClip("idle", [0, 1], { speed: 300 });
anim.addClip("run", [8, 9, 10, 11], { speed: 90 });
player.addComponent(anim);
anim.play("idle");
scene.add(player);
```
Flip with `tex.setFlipX(true)` — never by negating scale. Frame UVs are
automatically inset half a texel, so spritesheet frames never bleed into their
neighbors (no white seams).
### Pixel-art checklist
- Pass `pixelart: true` on textures (NEAREST filtering + whole-pixel positions).
- Snap the camera: `emerald.camera.setPixelSnap(true)` — kills sub-pixel
shimmer while a smooth-follow camera moves. The stored position stays
smooth; only the rendered position rounds.
## 3. Input: keyboard + every kind of gamepad
Bind *actions* once; read actions everywhere. Keyboard keys, mouse buttons,
gamepad buttons, and even analog stick directions are all just tokens:
```js
input
.mapAction("left", ["a", "ArrowLeft", "pad:0:dpadLeft", "pad:0:leftStickLeft"])
.mapAction("right", ["d", "ArrowRight", "pad:0:dpadRight", "pad:0:leftStickRight"])
.mapAction("jump", [" ", "w", "pad:0:south"]);
// in the loop:
const dir = input.getAxis("left", "right"); // -1 | 0 | +1
if (input.justPressed("jump")) jump();
// full analog, radial deadzone (diagonals not clipped), rescaled to 0..1:
const stick = input.getGamepadStick("left", 0, { invertY: true });
velocity.x = stick.x * speed; // stick.magnitude, stick.angle also available
```
- `"pad:0:south"` is the bottom face button on *any* controller — Xbox,
PlayStation and DirectInput layouts all resolve through mappings.
Common DirectInput pads (Logitech Dual Action, Twin USB adapters, 8BitDo)
are recognized out of the box; add your own with
`InputManager.registerGamepadMapping("mypad", { buttons: { south: 1 } })`.
- Deadzone/feel: `input.setGamepadDeadzone(0.25)` and
`input.setGamepadCurve(2)` (finer control near center).
- Rumble: `input.rumble(0, { duration: 120 })`.
## 4. Tiles and levels
For hand-built levels use the [Tile Forge editor](../../emerald-tile-forge/)
and its `EmeraldLevelLoader` (slices spritesheets, paints layers with parallax,
entities, per-tile polygon colliders — and renders all static tiles as **one
draw call per tileset**). For procedural grids use the engine's `Tilemap`:
```js
import { Tilemap, Physics } from "emeraldengine";
// (name, texturePath, options)
const map = new Tilemap("ground", "assets/tiles.png", {
tileSize: 32, frameWidth: 16, frameHeight: 16, framesPerRow: 4, totalFrames: 16,
});
map.setMap(grid2D); // rows of frame indices (-1 or null = empty)
scene.add(map.gameObject); // every tile lives on one GameObject
const physics = new Physics(-9.8, 30);
map.buildColliders(physics); // static bodies for solid cells, merged into runs
```
`TiledMap` and `Aseprite` importers are available for external tools.
## 5. Sound
```js
import { AudioManager } from "emeraldengine";
const audio = new AudioManager();
audio.add("assets/jump.wav", "jump", { bus: "sfx" });
audio.add("assets/theme.mp3", "theme", { bus: "music", loop: true });
audio.play("jump");
audio.fadeIn("theme", 1.5);
audio.setBusVolume("music", 0.6); // mix buses: master * bus * sound
audio.crossfade("theme", "boss", 2.0); // level transition
```
## 6. Saving
```js
import { Storage } from "emeraldengine";
Storage.save("save1", { level: 3, coins: 120 }, { version: 2, backup: true });
const data = Storage.load("save1", {
version: 2,
fallback: { level: 1, coins: 0 },
migrate: (old, fromVersion) => ({ ...old, coins: old.coins ?? 0 }),
});
```
Writes are versioned envelopes with a `.bak` mirror — a corrupted write
recovers from backup automatically.
For **large saves** (a persistent world, not just settings), use `EmeraldDB` —
the async IndexedDB twin with the same API and no 5MB localStorage cap:
```js
import { EmeraldDB } from "emeraldengine";
await EmeraldDB.save("world", world, { version: 1 });
const world = await EmeraldDB.load("world", { version: 1, fallback: newWorld() });
```
## 7. Post-processing
```js
import { PostEffects } from "emeraldengine";
emerald.addPostEffect(PostEffects.bloom({ threshold: 0.85, intensity: 0.65 }));
```
Bloom over your HUD makes text mushy, so render UI on its own camera and take
it out of the post-processing pass. Put UI objects on their own layer, give the
camera that layer, and exclude it:
```js
import { Camera } from "emeraldengine";
const UI_LAYER = 1;
healthBar.layer = UI_LAYER; // any GameObject that belongs to the HUD
const uiCamera = new Camera();
uiCamera.setOnlyLayers([UI_LAYER]); // draws nothing but the HUD
uiCamera.setExcludeFromPost(true); // and skips bloom/vignette/colorGrade
emerald.addCamera(uiCamera);
emerald.camera.setIgnoreLayers([UI_LAYER]); // keep the world camera off the HUD
```
## 8. Cleaning up (do this, it matters)
Removing an object from a scene does **not** free its GPU memory — that's
deliberate, so you can re-add it. When something is gone for good:
```js
scene.remove(enemy, { dispose: true }); // frees GL buffers + texture refs
// or when tearing down a whole level/screen:
scene.dispose();
```
Shared textures are reference-counted: the GL texture is deleted when the last
user disposes. Skipping this leaks GPU memory across level restarts.
WebGL context loss (mobile tab switches, GPU resets) is handled automatically —
rendering pauses and every texture, buffer, shader, and render target is
rebuilt on restore. Hook `emerald.onContextLost(cb)` / `onContextRestored(cb)`
to show an overlay if you want.
## 9. Watching performance
```js
import { DebugOverlay } from "emeraldengine";
const debug = new DebugOverlay();
// after drawScene each frame:
debug.update(emerald, scene);
```
The overlay shows FPS, frame-time graph, and the render counters —
`draws` (GPU draw calls), `quads` (sprites drawn, counting batched instances),
`binds` (texture switches). Programmatic access: `emerald.getRenderStats()`.
If `draws` grows with your level size, something isn't batched — reach for
`Tilemap`, `SpriteBatch`, or the level loader's instanced path.
## Where to go next
- **README** — full reference for every system (materials, particles,
networking, coroutines, tweens, state machines...).
- **Emerald Tile Forge** — the editor for hand-built levels, tilesets,
per-tile colliders and entity placement.