Phaser AE

Voxel World

Free packv3.0.0-43DWorld

A block world in the style of Minecraft: a chunked volume you can mine, place, tunnel through and build with, drawn with soft ambient occlusion and generated textures, with a raycast to point with and a character body that walks it.

This is a Free pack. Every phaser.io account can use it in their games.

Released October 14, 2026 · updated October 20, 2026

Voxel World: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

voxels3d

function voxels3d(opts: Voxels3dOptions = {}): Pack<{ voxels: Voxels3dApi }>

The voxels-3d pack: a chunked block volume with a procedural texture pack, exposed-face meshing with baked ambient occlusion, a DDA raycast and a character controller. Requires a 3D world and render-3d.

Helpers

BLOCK

const BLOCK: { readonly air: 0; readonly grass: 1; readonly dirt: 2; readonly stone: 3; readonly cobble: 4; readonly sand: 5; readonly log: 6; readonly planks: 7; readonly leaves: 8; readonly water: 9; readonly snow: 10; readonly glass: 11; readonly brick: 12; readonly coalOre: 13; readonly goldOre: 14; readonly gravel: 15; readonly bedrock: 16; }

Named ids so callers read BLOCK.grass not a magic number.

BLOCKS

const BLOCKS: (BlockDef | null)[]

The block table. BLOCKS[id] — id 0 (air) is intentionally a hole.

Functions

bakeTiles

function bakeTiles(res = 16, seed = 1): { data: Uint8Array; layers: number; res: number }

Bake the whole pack: count layers of res*res RGBA, contiguous. Pure.

faceTile

function faceTile(def: BlockDef, face: number): number

Pick the per-face tile layer for a block + face index (0..5, order = FACES).

halveRGBA

function halveRGBA(px: Uint8Array, w: number, h: number): { px: Uint8Array; w: number; h: number }

Box-filter downsample one RGBA layer by 2× (for mip generation). Pure.

meshChunk

function meshChunk(get: (x: number, y: number, z: number) => number, x0: number, y0: number, z0: number, ch: number): Float32Array

Mesh one chunk into an interleaved Float32Array (VOX_FLOATS per vertex). Only faces exposed to air / a non-opaque different block are emitted. get is the WORLD accessor (crosses chunk borders for correct culling+AO).

raycastVoxel

function raycastVoxel( get: (x: number, y: number, z: number) => number, ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, maxDist = 64, ): VoxelHit | null

March a ray through the grid; return the first SOLID block hit (or null). place = hit cell + face normal is the empty cell to build into.

tileMipChain

function tileMipChain(baked: { data: Uint8Array; layers: number; res: number }, mipmaps = true): TileLevel[]

The baked pack's whole mip chain, level 0 first: each level box-filters every layer of the one above with halveRGBA, down to 1×1 (floor(log2(res)) + 1 levels). One level when mipmaps is false. Both renderers upload exactly this, so WebGL2 and WebGPU sample the same texels at every distance. Pure.

vertexAO

function vertexAO(side1: boolean, side2: boolean, corner: boolean): number

Standard 3-sample corner AO: 0 (both sides closed) … 3 (all open).

voxelTerrain

function voxelTerrain(world: VoxelWorld, opts: VoxelTerrainOptions = {}): void

Fill a VoxelWorld with a landscape. Pure + deterministic by seed — the same (type, seed) is the same world on every machine. Every type is SOLID from the surface down to bedrock (dig-through), coloured grass/sand/snow by height, with an ore-flecked stone core. Reseed by calling clear() then this again.

Classes

VoxelAgent

class VoxelAgent

A mob: a VoxelBody plus a tiny brain. wander roams and randomly re-heads; seek/flee chase or avoid a target; all of them AUTO-JUMP when a wall blocks them (so they climb terraces like a Minecraft mob). Drive it with update(dt, target?).

  • readonly body: VoxelBody — The physics body that the brain drives. Read its position for rendering.
  • mode: VoxelAgentMode — The active steering mode. Change it at any time.
  • speed: number — The move speed, in blocks each second. Default 3.
  • heading: number — Current heading in radians (wander) — also where a mesh should face.
  • constructor(world: VoxelWorld, spawn: { x: number; y: number; z: number }, opts: VoxelAgentOptions = {})
  • update(dt: number, target?: { x: number; z: number }): void — One AI tick. target is required for seek/flee (a point to chase/avoid).

VoxelBody

class VoxelBody

An axis-aligned body that walks the voxel world: gravity, jumping, per-axis swept collision resolve against solid blocks, and auto step-up over 1-block ledges. Feed it a wished horizontal velocity + a jump flag each step; it resolves the move and reports onGround / blocked.

  • x: number — The X centre of the footprint, in blocks.
  • y: number — The bottom of the body, in blocks (Y).
  • z: number — The Z centre of the footprint, in blocks.
  • vy: number — The vertical speed, in blocks each second. Negative is falling.
  • onGround: boolean — True when the body stands on a solid block after the last step.
  • blocked: boolean — True when a wall stopped horizontal motion last step (AI: turn/jump).
  • readonly hw: number — Half the footprint width, in blocks. The body spans x - hw .. x + hw.
  • readonly height: number — The body height, in blocks. Default 1.8.
  • constructor(private world: VoxelWorld, spawn: { x: number; y: number; z: number }, opts: VoxelBodyOptions = {})
  • step(dt: number, wishX: number, wishZ: number, jump: boolean): void — Advance one tick. wishX/wishZ are desired horizontal velocity (blocks/s); jump launches if standing. Substepped so fast motion can't tunnel through a 1-block wall.

VoxelWorld

class VoxelWorld

A bounded grid of block ids, split into fixed-size chunks. Pure/testable.

  • readonly sx: number — The world width, in blocks (X).
  • readonly sy: number — The world height, in blocks (Y).
  • readonly sz: number — The world depth, in blocks (Z).
  • readonly ch: number — The chunk edge length, in blocks. Default 16.
  • readonly cx: number — The number of chunks along X.
  • readonly cy: number — The number of chunks along Y.
  • readonly cz: number — The number of chunks along Z.
  • readonly dirty: Set<number> — Chunk indices needing a remesh.
  • constructor(opts: VoxelWorldOptions = {})
  • inBounds(x: number, y: number, z: number): boolean — Returns true when the cell (x, y, z) lies inside the world bounds.
  • get(x: number, y: number, z: number): number — Block id at a cell, or 0 (air) outside the world.
  • set(x: number, y: number, z: number, id: number): void — Set a cell. Marks the owning chunk (and a bordering neighbour) dirty.
  • columnTop(x: number, z: number): number — Highest solid block at column (x, z), or -1 if the column is empty.
  • markAllDirty(): void — Mark every non-empty chunk dirty (after a bulk generate, or device loss).
  • clear(): void — Empty the world back to all-air. Marks every previously-filled chunk dirty so its geometry clears on the next remesh (used by reseed).
  • chunkCoords(idx: number): [number, number, number] — Chunk grid coords from a linear chunk index (for the mesher).
  • get chunkCount(): number — The total number of chunk slots in the world, filled or not.
  • hasChunk(idx: number): boolean — Returns true when the chunk at a linear index holds block data.

Constants

AO_LEVELS

const AO_LEVELS: number[]

Ambient-occlusion brightness by occluder count (0 = darkest corner). Deliberately gentle — the block look wants a hint of crease, not deep shadow (Minecraft absorbs very little light per face).

FACES

const FACES: Face[]

The six cube faces in mesher order: +X, -X, +Y, -Y, +Z, -Z. Each holds its normal, axes and UVs.

isOpaque

const isOpaque = (id: number): boolean=> …

Does this block fully hide the faces behind it? Air, leaves, glass and water do not.

isSolid

const isSolid = (id: number): boolean=> …

Does this block stop movement and answer a raycast? Water does not.

TILES

const TILES: readonly ["grassTop", "grassSide", "dirt", "stone", "cobble", "sand", "logTop", "logSide", "planks", "leaves", "water", "snowTop", "snowSide", "glass", "brick", "coalOre", "goldOre", "gravel", "bedrock"]

The tile layer names, in upload order. The index of a name is its array-texture layer.

VOX_FLOATS

const VOX_FLOATS: 8

The number of floats in one voxel vertex: pos(3) + uv(2) + layer(1) + face(1) + ao(1).

Types

BlockDef

interface BlockDef

One face's tile picked by axis.

  • name: string — The block name, for reading and debugging (BLOCK maps these names to ids).
  • top: number — Tile layer index for the +Y face.
  • bottom: number — Tile layer index for the -Y face.
  • side: number — Tile layer index for the four side faces (±X, ±Z).
  • opaque?: boolean — Fully hides faces behind it (default true). Leaves/glass/water = false.
  • solid?: boolean — Blocks movement / is pickable by the raycast (default true). Water = false.
  • cutout?: boolean — Alpha-tested in the shader (leaves, glass) — kept in the opaque pass.

TileLevel

interface TileLevel

One level of the texture pack's mip chain: every layer at w×h, one after another, as an array texture's level is laid out.

  • w: number — Level width in texels.
  • h: number — Level height in texels.
  • data: Uint8Array — RGBA bytes, w * h * 4 per layer, every layer in order.

VoxelAgentMode

type VoxelAgentMode = 'wander' | 'seek' | 'flee' | 'idle'

The steering mode of a VoxelAgent: roam, chase a target, avoid a target, or stand still.

VoxelAgentOptions

interface VoxelAgentOptions extends VoxelBodyOptions

Options for a VoxelAgent: the body options plus mode, speed and wander seed.

  • mode?: VoxelAgentMode — The steering mode to start in. Default 'wander'.
  • speed?: number — Move speed, blocks/s (default 3).
  • seed?: number — Seed for wander randomness (default derived from spawn).

VoxelBodyOptions

interface VoxelBodyOptions

Options for a VoxelBody: its size in blocks and its gravity, jump and fall tuning.

  • width?: number — Footprint width in blocks (default 0.6).
  • height?: number — Height in blocks (default 1.8).
  • stepHeight?: number — Auto-climb ledges up to this tall without jumping (default 1.05 — walks single-block terraces smoothly; set ~0.6 for jump-required full blocks).
  • gravity?: number — Downward acceleration, blocks/s² (default 28).
  • jumpSpeed?: number — Jump launch speed, blocks/s (default 8.4 ≈ clears ~1.25 blocks).
  • maxFall?: number — Terminal fall speed, blocks/s (default 40).

VoxelHit

interface VoxelHit

The result of a voxel raycast: the block hit, the entry face normal, and the ray distance.

  • x: number — The X cell of the solid block hit.
  • y: number — The Y cell of the solid block hit.
  • z: number — The Z cell of the solid block hit.
  • nx: number — The X part of the entry face normal (points back toward the ray origin).
  • ny: number — The Y part of the entry face normal.
  • nz: number — The Z part of the entry face normal.
  • dist: number — Ray distance to the hit.

Voxels3dApi

interface Voxels3dApi

game.voxels.

  • readonly world: VoxelWorld — The CPU grid — the contract everything else reads.
  • get(x: number, y: number, z: number): number — Block id at a cell, 0 outside.
  • set(x: number, y: number, z: number, id: number): void — Set a cell, marking its chunk and any bordering neighbour for a remesh.
  • columnTop(x: number, z: number): number — Highest non-air block in a column, or −1.
  • raycast(ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, maxDist?: number): VoxelHit | null — March a ray and return the first SOLID block, or null. The hit's face normal added to its cell IS the empty cell to build into.
  • landscape(opts?: VoxelTerrainOptions): void — Fill the volume with a generated landscape.
  • clear(): void — Empty it back to air.
  • body(spawn: { x: number; y: number; z: number }, opts?: VoxelBodyOptions): VoxelBody — A body that walks the blocks — the player, or a mob.
  • agent(spawn: { x: number; y: number; z: number }, opts?: VoxelAgentOptions): VoxelAgent — A body with steering on top of it.
  • readonly blockSize: number — World units per block.
  • readonly chunksDrawn: number — Chunks drawn last frame.
  • readonly verticesDrawn: number — Vertices submitted in visible chunks last frame.
  • readonly remeshed: number — Chunks re-meshed last frame.
  • enabled: boolean — Whether it draws.

Voxels3dOptions

interface Voxels3dOptions extends VoxelWorldOptions

Manifest options: the volume, the texture pack and how it draws.

  • tileRes?: number — Block-texture resolution in texels. Default 16 — the classic.
  • seed?: number — Texture-pack seed. Default 1.
  • blockSize?: number — World units per block. Default 1.
  • mipmaps?: boolean — Build mipmaps, which is what kills the shimmer on distant blocks. Default true.
  • cullDist?: number — Chunks further than this from the camera are skipped. Default 512.

VoxelTerrainOptions

interface VoxelTerrainOptions

Options for voxelTerrain and landscape: the landform, its seed, sea level and trees.

  • type?: VoxelTerrainType — Landform (default 'island'). All types fill SOLID down to bedrock, so you can dig right through them. 'island' + 'plains' add a sea; the rest are dry.
  • seed?: number — The random seed. The same (type, seed) makes the same world. Default 1.
  • seaLevel?: number — Water fill height. Defaults per type ('island'/'plains' have one; -1 = dry).
  • trees?: boolean — Scatter trees on grass (default true).
  • treeDensity?: number — Tree density multiplier (default 1).

VoxelTerrainType

type VoxelTerrainType = 'island' | 'flat' | 'plains' | 'hills' | 'mountains'

The landform that voxelTerrain builds: an island with a sea, or a dry or wet plain, or hills.

VoxelWorldOptions

interface VoxelWorldOptions

Options for a VoxelWorld: the world size in blocks and the chunk edge length.

  • sizeX?: number — The world width, in blocks (X). Default 128.
  • sizeY?: number — The world height, in blocks (Y). Default 48.
  • sizeZ?: number — The world depth, in blocks (Z). Default 128.
  • chunk?: number — Chunk edge in blocks (default 16). Bigger = fewer draws, costlier edits.