A 3D game with three.js
You will make a small arena where every player is a capsule that turns to face where it runs. Others see you move and turn smoothly. The finished, styled version is examples/games/arena3d.html.
1. Load three.js
Section titled “1. Load three.js”npm install tickcli ws threenpx tickcli .On the tickcli server, <script src="lib:three"></script> gives the page three.js as the global THREE, made from the installed package. Anywhere else, any build that sets window.THREE works.
2. The scene
Section titled “2. The scene”<!doctype html><html><head><meta charset="utf-8"><title>Arena</title></head><body><script src="lib:three"></script><script src="lib:room"></script><script src="lib:game"></script><script src="lib:game3d"></script><script>const R3 = TickcliThree, SIZE = 12, up = new THREE.Vector3(0, 1, 0);let renderer, scene, camera, me, others, yaw = 0;
function capsule(color) { const m = new THREE.Mesh(new THREE.CapsuleGeometry(0.45, 0.7, 6, 16), new THREE.MeshStandardMaterial({ color })); m.position.y = 0.8; const o = new THREE.Group(); o.add(m); return o;}
Game({ title: "Arena", context: "webgl", aspect: 16 / 10, multiplayer: { max: 8, player: { x: [-SIZE, SIZE], z: [-SIZE, SIZE], qx: [-1, 1], qy: [-1, 1], qz: [-1, 1], qw: [-1, 1] } }, setup(g) { renderer = renderer || R3.renderer(g); // a WebGLRenderer on the shell's canvas, made once if (!scene) { scene = new THREE.Scene(); scene.background = new THREE.Color(0x0b1020); camera = new THREE.PerspectiveCamera(55, 1, 0.1, 100); scene.add(new THREE.HemisphereLight(0xffffff, 0x334155, 2)); const floor = new THREE.Mesh(new THREE.PlaneGeometry(SIZE * 2, SIZE * 2), new THREE.MeshStandardMaterial({ color: 0x1e293b })); floor.rotation.x = -Math.PI / 2; scene.add(floor); me = capsule(0x60a5fa); scene.add(me); others = R3.pool(scene, () => capsule(0xf87171)); // one capsule per other player } me.position.set((Math.random() - 0.5) * SIZE, 0, (Math.random() - 0.5) * SIZE); }, update(g, dt) { const a = g.axis(); if (a.x || a.y) { me.position.x += a.x * 6 * dt; me.position.z += a.y * 6 * dt; yaw = Math.atan2(a.x, a.y); } me.quaternion.setFromAxisAngle(up, yaw); g.me.set(R3.pose(me)); // {x, y, z, qx, qy, qz, qw}, rounded }, draw(g) { R3.resize(renderer, camera, g); // drawing size, pixel ratio, camera aspect others.sync(g.players.filter((p) => !p.me)); // placed where they are now, eased camera.position.set(me.position.x, 9, me.position.z + 11); camera.lookAt(me.position); renderer.render(scene, camera); },});</script></body></html>Open it in two windows and press Start.
What tickcli does here
Section titled “What tickcli does here”context: "webgl": the shell takes no 2D context. It still gives you the bar, the start and game-over screens, the lobby, keys and the touch pad;draw(g)runs every frame.R3.resizefollows the shell: when the window changes, or a slow device lowers the pixel ratio, the renderer and camera follow.- Rotations are quaternions.
qx, qy, qz, qware eased on other screens as one turn (slerp, the short way round, always of length 1). Declare each as[-1, 1]; they go out with 3 decimals. - Positions keep decimals. In webgl mode the host’s shared positions keep 2 decimals (metres to the centimetre); players’ fields follow their declared range.
- Things the host moves ease too:
g.shared.set({gem: R3.pose(gem)})inhost(),R3.place(gemMesh, g.shared.gem)indraw().
- Shadows, fog, a glowing gem and a pickup burst:
arena3d.html. - Every option of game3d.js.