# Game Builder Tutorial 3: Build a First-Person 3D Dungeon

The final tutorial takes the same data-driven pattern from [Tutorial 1](https://www.codenameone.com/blog/game-builder-2d-platformer/) and [Tutorial 2](https://www.codenameone.com/blog/game-builder-board-game/) into **3D**. Duke's last adventure sends him underground: **Crypt Walk**, a first-person dungeon of stone corridors where the tea cups have invaded his coffee break. He hunts them through the maze and fires coffee beans to smash them, while the walls box him in. The headline difference from the 2D tutorial is what happens at runtime — instead of sprites in a `Scene`, each element becomes a GPU-rendered `Model` under a perspective camera with lighting. You author the same way; the runtime renders it in 3D.

**What is Codename One?** Codename One is an open-source framework for building native iOS, Android, desktop, and web apps from a single Java or Kotlin codebase. Learn more at [codenameone.com](https://www.codenameone.com/).

Project setup is identical to Tutorial 1 — only the mode changes:

```bash
mvn cn1:create-game-scene -DclassName=com.example.crypt.CryptWalk -Dmode=3d
mvn cn1:gamebuilder
```

## Why 3D mode (and what "play style" means)

3D games come in families with very different cameras and controls: a flight sim has no ground, a racer follows a road, a dungeon crawler walks first-person between walls. Rather than make you wire each, the builder offers a **play style** — *open*, *flight*, *race*, *dungeon* — that picks a sensible camera, movement model, and collision for you, so the preview behaves like the genre while you design.

## Step 1 — A 3D scene

Pick **New scene → 3D Map**. Placement defaults to an accurate top-down grid (toggle **View → 3D: Perspective / Top-down** for an angled overview). The **3D Kit** pack supplies blocks, pillars, crates, a spawn and scenery.

![A new 3D scene with the 3D Kit](https://www.codenameone.com/blog/gamebuilder/dungeon-1-new-scene.png align="center")

## 3D assets are meshes

This is the big difference from Tutorials 1 and 2: in 2D an asset's art is an image or a sprite sheet, but in **3D it's a mesh** — geometry, not pixels. That's the asset model's third format, `TYPE_MESH`: `source` points at a **glTF /** `.glb` file, and at runtime `GameSceneView.buildModels` loads it with `GltfLoader` and realizes a `Model` under the perspective camera and lighting.

```json
{ "id": "statue", "name": "Statue", "kind": "actor", "type": "mesh", "source": "statue.glb" }
```

The starter **3D Kit** stays dependency-free by building its blocks, pillars and crates from **primitive meshes** (`Primitives.cube(...)`) shaded with each asset's colour, so you can lay out a level immediately without modeling anything. To use real modeled art, export a `.glb` from Blender (or drop in a CC0 model), put it in `games/assets/`, and set the asset's `type` to `mesh` with `source` pointing at the file — `buildModels` loads any element whose asset is a mesh and falls back to the primitive cube otherwise. Unlike sprites, meshes are **resolution-independent**: there's no DPI or frame bookkeeping, the GPU just scales the geometry.

## Step 2 — Pick the dungeon play style

With nothing selected, set the Inspector's **3D play style** to **dungeon**. That switches the preview to a first-person walker with wall collision — the genre we're building. (Switch it to *open* for a free arena or *flight* for an aerial flyby of the same layout; the level data doesn't change, only how it plays.)

![Choosing the dungeon play style](https://www.codenameone.com/blog/gamebuilder/dungeon-2-style.png align="center")

## Step 3 — Build the maze walls

A maze wants *continuous* walls, not a row of separate posts — so build them with the **Terrain** tool's **Wall** brush rather than placing individual pillar objects. Select **Terrain → Wall** and paint the maze: a solid perimeter plus a few inner walls, leaving a corridor for Duke. Each painted cell becomes a full-width stone block, so adjacent cells join into a seamless wall (and the radar shows them as solid, not dotted). In dungeon play style those walls stop the walker, and the floor is laid as continuous stone — no tile-grid gaps.

(You *can* still drop standalone **Pillar** or **Rock** objects from the 3D Kit as scenery; they're great as freestanding columns, just not as a maze wall.)

![Building the maze with the Wall brush](https://www.codenameone.com/blog/gamebuilder/dungeon-3-walls.png align="center")

## Step 4 — Add the player spawn

Place a **Spawn** where the player starts and tick **This is the player** in its Behavior section — that marks which element the camera and controls drive. Its **Elevation (Z)** field raises it for multi-level layouts.

![Adding the player spawn](https://www.codenameone.com/blog/gamebuilder/dungeon-4-spawn.png align="center")

## Step 5 — Walk it

Press **Live**. In dungeon style you walk in **first person** — **Left/Right turn, Up/Down walk** — and the maze walls stop you like real walls. A radar in the corner shows where everything is, which is essential once a level grows beyond one room.

![Walking the dungeon in first person](https://www.codenameone.com/blog/gamebuilder/dungeon-5-walk.png align="center")

Live, you walk the corridor in first person and look around — the walls stop you, and the sunlit faces brighten or fall into shadow as you turn:

![Walking the dungeon, in the Game Builder preview](https://www.codenameone.com/blog/gamebuilder/game-dungeon.gif align="center")

## Coffee versus tea: fighting back

A maze to walk is a start, but Duke came down here to clear out the tea cups. From the 3D Kit, drop **Tea Cup** actors along the corridor — they're the enemy — and give Duke a ranged attack: **Fire** (or the space bar) launches a coffee bean straight ahead.

![Duke fires coffee beans at the tea cups](https://www.codenameone.com/blog/gamebuilder/dungeon-6-combat.png align="center")

In the preview the beans fly down the corridor, and a hit pops the cup and scores it. In a shipped game that's an ordinary projectile loop in `onUpdate` — spawn a bean on the fire key, advance the live ones, and test each against the enemies:

```java
private final List<float[]> beans = new ArrayList<>();   // {x, z, dirX, dirZ}

@Override
protected void onUpdate(double dt) {
    GameInput in = getInput();
    if (in.wasGameKeyPressed(Display.GAME_FIRE)) {
        beans.add(new float[]{playerX(), playerZ(),
                (float) Math.sin(yaw()), (float) -Math.cos(yaw())});
    }
    for (Iterator<float[]> it = beans.iterator(); it.hasNext();) {
        float[] b = it.next();
        b[0] += b[2] * 7 * dt;
        b[1] += b[3] * 7 * dt;
        GameElement cup = enemyNear(b[0], b[1], 0.5);   // your spatial test
        if (cup != null) {
            scene().remove(spriteFor(cup));              // smash it
            addScore(cup.getInt("value", 25));
            it.remove();
        } else if (hitWall(b[0], b[1])) {
            it.remove();                                  // bean spent
        }
    }
}
```

The cups are just elements whose `assetId` your code treats as an enemy — exactly the "your rules read the data" idea from [Tutorial 1](https://www.codenameone.com/blog/game-builder-2d-platformer/#your-rules-coins-the-slime-and-winning), now in three dimensions. Want them to fight back? Move each cup toward Duke a little every frame and cost him a life on contact.

## Light and shade

Look up and you'll see the **sun**. A 3D level carries one or more lights (the `lights` array in the `.game`), and the renderer shades every surface by how directly it faces the light: wall faces turned toward the sun are bright, faces turned away fall into shadow — which is exactly what gives the maze its depth as you move. That's the whole point of having a visible light source rather than flat-lit boxes. Change the mood from code via `getLight()`:

```java
getLight().setDirection(0.3f, -1f, 0.2f)    // a lower sun casts longer, moodier shadows
          .setColor(0xfff2c14e)              // warm sunlight
          .setAmbientColor(0xff404858);      // fill light so shadows aren't pure black
```

On device this is real GPU lighting; the editor preview approximates the same sun angle so you can judge it while authoring.

## Sculpting terrain (floors, hills, holes, ramps)

3D levels aren't just objects on a flat plane. Select the **Terrain** tool and you can paint the ground itself: **Raise/Lower** elevation, carve **holes** (open sky a flight level can fall through), stamp **walls**, and **Paint** a surface material — grass, road, stone, sand, water. Painted elevation changes render as **smooth slopes**, not stairs, so a road can ramp uphill. The walker rides the terrain height and is stopped by walls and holes.

## What got saved, and how it renders

**Save** writes `src/main/resources/games/CryptWalk.game` (loaded at runtime as `/CryptWalk.game` — the resource namespace is flat). A 3D level stores the play style, the placed elements (with elevation and per-object scale), and any terrain you sculpted:

```json
{
  "mode": "3d", "cols": 16, "rows": 16, "tileSize": 1,
  "props": { "view3d": "dungeon" },
  "camera": { "eye": [0,8,14], "target": [0,0,0], "fov": 60 },
  "lights": [ { "dir": [0.4,-1,0.3], "color": "fff2e0", "ambient": "2a2f3a" } ],
  "elements": [
    { "id": "sp", "assetId": "spawn", "layer": "Models", "x": 8, "y": 14, "props": { "player": true } }
  ],
  "terrain": { "cols": 16, "rows": 16, "heights": [ ], "materials": [ ],
               "walls": [ "...a height per maze-wall cell, 0 elsewhere..." ] }
}
```

The companion is the same `GameSceneView` pattern as the other tutorials. The difference is realization: for a 3D level it builds one `Model` per element inside `onSetup(GraphicsDevice)` under a perspective camera with the level's lighting — on the GPU. Your code still lives in `onUpdate`:

```java
@Override
protected void onUpdate(double deltaSeconds) {
    GameInput in = getInput();
    // first-person controls (or let the built-in dungeon walker handle them)
    if (in.isGameKeyDown(Display.GAME_LEFT))  { turn(-1.4 * deltaSeconds); }
    if (in.isGameKeyDown(Display.GAME_RIGHT)) { turn( 1.4 * deltaSeconds); }
    if (in.isGameKeyDown(Display.GAME_UP))    { walkForward(3.0 * deltaSeconds); }
    // win when the player reaches the exit element, lose on a trap, etc.
}
```

> The in-editor preview is a fast software approximation for iteration; on device the scene is rendered by the GPU-accelerated `GameSceneView`.

## Physics, effects and overriding defaults

**Collision** is handled for you — dungeon style stops the walker at walls and holes. For richer physics (projectiles, doors, movable crates) step a `PhysicsWorld` from `onUpdate` exactly as in [Tutorial 1's physics section](https://www.codenameone.com/blog/game-builder-2d-platformer/#physics-effects-and-overriding-defaults).

**Effects** hang off the same loop. A concrete example — a footstep sound and a torch-lit light fade, both reading the level's own data:

```java
private final SoundPool sound = SoundPool.create(4);
private SoundEffect step;            // step = sound.load("/footstep.wav");

// in onUpdate, when the player advances a tile:
sound.play(step, 0.6f, 0f, 1f, 0);   // volume, pan, rate, no loop
getLight().setColor(torchLit ? 0xfff2e0 : 0x404858);   // brighten when a torch is lit
```

**Overriding** — the play styles are presets, not constraints. Want a hybrid (walk like a dungeon but with no wall collision)? Set *open* and add your own collision in `onUpdate`.

## Menus and HUD in 3D

A 3D game has its own interface to manage too — a map toggle, an inventory, a "you died" screen, a pause overlay — and because `GameSceneView` is a Codename One `Component`, all of it is the ordinary UI toolkit, not a 3D-specific layer. Drop a `Dialog` for the death screen, a `Toolbar` command for the map toggle, a `Container` of item buttons for the inventory. [Tutorial 1's menu section](https://www.codenameone.com/blog/game-builder-2d-platformer/#menus-hud-and-pause) applies unchanged — the game underneath happens to be 3D, but the menus are pure Codename One.

## Scaling up: streaming worlds

A single 16×16 room fits in memory, but an open-world RPG does not. The gaming runtime supports a **streaming, region-based world**: terrain is paged in and out as chunks, the map is split into linked **regions** that load and unload around the player for seamless area transitions, and surfaces use pluggable **materials**. In the editor, **New scene → Large World** creates a region graph you grow with *Add region* (north/south/east/west); the active region's streaming terrain is what you edit and preview. The same `.game`/companion pattern loads it.

## The finished game and next steps

You have **Crypt Walk**: a navigable first-person dungeon with sculpted terrain and wall collision. From here:

*   **Enemies** — place actors and, in `onUpdate`, move them toward the player and cost a life on contact (the slime logic from [Tutorial 1](https://www.codenameone.com/blog/game-builder-2d-platformer/#your-rules-coins-the-slime-and-winning) ports directly).
    
*   **A goal** — mark an *exit* element and end the level when the player reaches it.
    
*   **Items and doors** — collectibles open locked corridors; read a `key`/`locked` property.
    
*   **Bigger maps** — graduate to a Large World of streaming regions for a full crawler.
    

That's the trilogy — `mvn cn1:create-game-scene` then `mvn cn1:gamebuilder`, and you're building games by drawing them.
