2D looks: lights, effects and post
Lights, shadows, hit flashes, outlines, glows, screen looks and shaders in 2D, one line each.
A 2D game on the engine draws on the GPU by itself (Canvas 2D only where a browser has no WebGL2), so it can afford lights, shadows, glows and screen effects even on a phone. Each takes one line: a look on the camera, lights in the world, effects on the things that need them. The full reference is the API's "2D on the GPU" part.
One line for each
| You want | Write |
|---|---|
| A night level | Lighting2D.night() on the camera; Light2D.lamp() at each lamp |
| A dungeon lit by torches | Lighting2D.dungeon() on the camera; Light2D.torch() at each torch; Occluder2D() on the walls (their Tilemap layer, shapes or sprites) |
| Neon | Lighting2D.neon() and PostFx2D.neon() on the camera; Light2D.neon('#ff2bd6'); Material2D.glowing() on signs |
| The player carries a light | player.add(Light2D({ radius: 5 })), or a flashlight: a child with Light2D.spot({ direction: 0 }) that turns with the player |
| A hit flash | flash2D(enemy) (any drawable; it fades back by itself) |
| An outline | Material2D.outlined('#ffffff') |
| The same picture in other colours (a team, a stronger enemy) | Material2D.recolored({ '#3b82f6': '#ef4444' }): each colour becomes its value, up to 4 |
| A thing that glows in the dark | Material2D.glowing(), and Light2D.glow('#ffd23f') so it lights what is around it |
| Burn away on death | tween(e, Material2D, { dissolve: 1 }, { ms: 500, onDone: () => e.despawn() }) on an entity with Material2D() |
| Grass, flags, water that sway | Material2D.swaying() |
| Sprites with depth under moving lights | Material2D({ normalMap: 'assets/hero_n.png' }) (a normal map laid out like the picture) |
| A soft, finished look | PostFx2D.soft() on the camera |
| Retro pixels, an arcade screen | PostFx2D.retro(), PostFx2D.crt() |
| A mood | PostFx2D.noir(), .horror(), .sunset(), .dream() |
// @run2gpu
import { createGame, Transform } from '@onceworlds/engine';
import { Render2D, Shape2D, Light2D, Lighting2D, Occluder2D, PostFx2D, Material2D, camera2D } from '@onceworlds/engine/modules';
const game = createGame({
space: '2d',
headless: true,
modules: [Render2D()],
scene(world) {
world.spawn([Transform(), camera2D({ height: 12, clear: '#08090f' }), Lighting2D.night(), PostFx2D.soft()]);
const player = world.spawn([Transform(), Shape2D({ shape: 'circle', radius: 0.4, fill: '#4ee1ff' }), Light2D({ radius: 5 })]);
world.spawn([Transform({ position: [3, 0, 0] }), Shape2D({ shape: 'rect', size: [1, 4], fill: '#2a3048' }), Occluder2D()]);
world.spawn([Transform({ position: [-4, 2, 0] }), Shape2D({ shape: 'roundRect', size: [2.4, 0.8], radius: 0.2, fill: '#ff2bd6' }), Material2D.glowing(), Light2D.neon('#ff2bd6')]);
void player;
},
}).start();
game.run(0.1);
expect(game.errors).toEqual([]);
Rules of thumb
Lighting2DandPostFx2Dgo on the camera entity (the one withcamera2D). A camera without them draws exactly as before and costs nothing extra; a minimap camera stays unlit.ambientdecides how dark it is:nightandduskstill show shapes,dungeonandcavehide what no light reaches, so give the player a light of their own. On the rare browser without WebGL2 the game draws with Canvas 2D: still dark where no light reaches, but without shadows, effects or bloom (aflash2Dstill flashes and a palette swap still recolours), so don't make a shadow the only way to see something.- Put
Occluder2D()on what should cast shadows: walls (on the walls' Tilemap, not the floor's), crates, pillars, characters. A closed shape stays lit on its own side and throws its shadow behind it. - Text, the UI, and anything drawn with
blend: 'add'(sparks, glows) are never darkened.Material2D({ unlit: true })keeps any other thing at its own colour (signs, eyes, screens). - Bloom makes bright things glow:
Lighting2D.glow(bright light) orPostFx2D.bloom(anything brighter thanbloomThreshold).Material2D({ glow: 1 })makes one thing twice as bright, so it blooms. - On phones: a dozen lights with shadows and a few dozen without are fine; the player's Graphics setting trims light map size, shadowed lights and bloom by itself. Thousands of sprites are fine too.
- Nothing fits?
Material2D.custom(name, { glsl })for one drawable's pixels andrender2d.postPass(name, { glsl })for the whole view: a GLSL ES 3.00 function (write1.0, not1, for a float). A mistake is reported once with its line, and the game carries on without the effect.