Terrain
Ground from presets or heights, solid with physics, sculpted in Build or in code.
Don't build ground out of boxes or a giant plane. The API reference ("Terrain3D") has every field and function.
The default path: one Terrain3D
import { Transform } from '@onceworlds/engine';
import { Terrain3D } from '@onceworlds/engine/modules';
world.spawn([Transform(), Terrain3D.preset('hills', { seed: 7 })]);
It is centred on its entity, coloured by height and slope (grass, rock on steep slopes, sand low down, snow up high) and, with
Physics3D, solid: characters walk on it, cars drive on it, balls roll down it. Nothing else to add. In a scene it is a node of
kind Terrain3D, and there its preset works by name (it is the landform).
| Preset | For |
|---|---|
hills |
Rolling green land: open worlds, farms, golf, adventure. The default. |
mountains |
Ridges and peaks with snow (192 m, 48 m tall): climbing, skiing, a backdrop. |
island |
Land in the middle that falls below 0 at its edges: add Water3D.preset('ocean') at 0. |
canyon |
A winding gorge between stepped mesas: racing, westerns. |
dunes |
Sand ridges: deserts. |
flat |
Ground to sculpt in Build, or for a level of your own on top. |
Another seed is another landscape of the same kind. size is metres along x and z, height how tall the landforms are,
resolution the cells a side (128 by default: 1 m cells on the default 128 m). Keep cells about a metre and resolution at 256 or
less unless the game needs more: a 512 terrain takes a few hundred milliseconds to make and four times the memory of a 256.
Putting things on the ground
import { terrainHeightAt } from '@onceworlds/engine/modules';
for (const [x, z] of treeSpots) {
const ground = terrainHeightAt(world, x, z); // { height, entity } or null off the terrain
if (ground) world.spawn([Transform({ position: [x, ground.height, z] }), ...tree()]);
}
It is the same surface physics uses, to the centimetre, and cheap enough to ask every frame (a bot's footing, a shadow blob).
Its look
The four layers have colours (grass, rock, sand, snow) and may have pictures (grassMap... snowMap: image assets, or
the library's Library.texture('grass'), 'rock', 'sand', 'snow', repeating every tile metres). sandLevel (metres), snowLine (a share of height; 1 or more: no snow) and rockSlope
(degrees) say where they go. toon: true for a cartoon look. Painting in Build lays a layer wherever you want it.
Sculpting in Build
Select the Terrain3D node: the Terrain panel opens and the 3D viewport's toolbar gets Raise, Lower, Smooth, Flatten, Paint and Erase
Paint (keys 1 to 6). Drag to sculpt: Shift does the opposite (raise lowers, paint erases), [ and ] resize the brush, Esc takes the
stroke back, and each stroke is one undo step. The result goes in the scene as two compact codes, heights and paint: leave them to
Build (a few strokes are a few hundred characters).
Changing it while the game runs
import { sculptTerrain, terrainOf } from '@onceworlds/engine/modules';
function crater(world, terrain, x, z) {
const data = terrainOf(world, terrain.id);
const at = terrain.get(Transform).position;
for (let i = 0; i < 5; i++) sculptTerrain(data, { tool: 'lower', x: x - at.x, z: z - at.z, radius: 5, strength: 1 }, 0.1);
}
Brushes work in the terrain's own space (metres from its centre). Drawing and the collider follow at once. In a multiplayer game
send the brush to every page (an event or rpc) and apply it there; don't replicate heights. resetTerrain(world, entity) undoes
live changes.
Heights of your own (a heightmap you generated): Terrain3D.heightmap(heights, { size: [200, 200] }) with (n + 1) × (n + 1)
numbers, row after row along +z.
Limits
- Turn terrain about y only, and scale it evenly.
- One terrain is up to 512 cells a side; for a bigger world make the cells bigger (
size) rather than adding terrains (their edges wouldn't meet). - It draws in chunks of 32 cells that get coarser with distance (sooner on phones), so a big terrain stays cheap to draw.
- Baked lighting includes terrain: a valley gets darker than a summit (see lighting.md).