Hi everyone!
I just released DISCBALL3D, a multiplayer soccer game that runs in the browser. Up to 5v5, matches either play for a set time or end when one side reaches a goal target, and you can join a room from a link.
Play: https://discball3d.com
The idea
Each player controls a disc on a full 3D pitch. You run, jump, boost, curve the ball with Q/E and charge your kicks. Rooms show up in a live list, and you can lock one with a password or share it by link. Anyone who joins mid-match watches as a spectator until the host puts them on a team. If you’re short on players, the host can add bots, and the C menu gives your side team commands (attack, defend, guard the goal, pass to me, press). Bots on your team follow them.
Tech
- three.js (r181) with React Three Fiber + drei
- Rapier (3D) for physics, at a fixed 60 Hz step
- The host’s browser is the server. Each room’s authoritative simulation runs in a Web Worker in the host’s tab. Guests connect peer-to-peer over WebRTC DataChannels: one reliable channel for events and one unordered, no-retransmit channel for snapshots and inputs. When P2P can’t open in time, a small Socket.IO gateway relays the traffic. The gateway only handles the lobby, signalling and the relay, and runs no physics.
- Client-side prediction with full re-simulation. Every client runs the same Rapier world, built from the same shared code as the host: all players and the ball, stepped together. On each 30 Hz snapshot it rewinds to the host’s state, replays its own recorded inputs and any unacked kicks, and fades the correction out as a short visual offset instead of a snap. A parity test keeps the host world and the prediction world within 1e-4 of each other, tick for tick.
- Binary wire format: a 4v4 snapshot with a moving ball is about 220 bytes.
- Host migration: if the host leaves, a random player takes over the room, including the match in progress.
- Kicks are sent as intents, never as impulses. The host checks range, cooldown and timing against the ball where it was at the client’s own tick, then works out the impulse itself.
- Procedural grass in a single shader pass, no textures. It replaced a 12-layer shell-textured field and runs about 4x faster in a software renderer.
- Resolution adapts to your frame rate (a hill-climbing DPR controller).
- Goal celebrations (rocket, UFO, portal, tornado, trampoline…), shot effects and trails. They are prewarmed with
compileAsync, add no lights, and none of them animates a material, so a celebration never hitches a frame. - Touch controls for phones, 18 languages, and optional Google/Apple accounts with cosmetics, quests and achievements.
I’d love feedback, especially on how it feels with real latency (P2P vs relay), on performance across different GPUs and on mobile, and on the ball handling (dribbling, curve, lift). Happy to answer questions about the netcode or about running authoritative Rapier in a browser worker.
Thanks for playing! ![]()
