How do you set up the first scene of a browser game? A step-by-step start with Three.js for camera, light, a low-poly field and clickable plots.
In a browser game, the first scene is where players decide “this game is fast”. In this post we’ll build a low-poly field with Three.js and make its plots clickable.
Scene, camera, light
Farm games usually use a camera looking down at a slight angle. An orthographic camera has no perspective shrink, so the field reads like a board on a table.
import * as THREE from 'three';
const scene = new THREE.Scene();
const aspect = innerWidth / innerHeight;
const camera = new THREE.OrthographicCamera(-10 * aspect, 10 * aspect, 10, -10, 0.1, 100);
camera.position.set(12, 14, 12);
camera.lookAt(0, 0, 0);
scene.add(new THREE.HemisphereLight(0xffffff, 0x6b8f5a, 1.1));
const sun = new THREE.DirectionalLight(0xfff1d6, 1.4);
sun.position.set(6, 10, 4);
scene.add(sun);
Laying out the plots
Each plot is a thin box. If we draw the same geometry hundreds of times, an InstancedMesh brings it down to a single draw call.
const plot = new THREE.BoxGeometry(1.8, 0.2, 1.8);
const soil = new THREE.MeshLambertMaterial({ color: 0x8a5a3b });
const field = new THREE.InstancedMesh(plot, soil, 9);
const m = new THREE.Matrix4();
for (let i = 0; i < 9; i++) {
m.setPosition((i % 3) * 2 - 2, 0, Math.floor(i / 3) * 2 - 2);
field.setMatrixAt(i, m);
}
scene.add(field);
Clicking: which plot?
A raycaster sends a ray from a point on the screen into the scene. With an InstancedMesh, the hit’s instanceId gives us the plot number.
const ray = new THREE.Raycaster();
addEventListener('pointerdown', (e) => {
const p = new THREE.Vector2((e.clientX / innerWidth) * 2 - 1, -(e.clientY / innerHeight) * 2 + 1);
ray.setFromCamera(p, camera);
const hit = ray.intersectObject(field)[0];
if (hit) selectPlot(hit.instanceId);
});
Three rules for performance
- If you draw the same object again and again, use instancing.
- Only enable shadows on the light that needs them; it’s the most expensive setting on mobile.
- Compress textures and load the scene after the rest of the page has opened.
These steps are the skeleton of a game. The loop, quests and economy are built on top. For a live example, open Farmie.