如何讓瀏覽器 3D 家庭派對遊戲在平價手機上順暢運行
Shipping a 3D family party game in the browser that still runs on a cheap phone
Sloth Jam 開發者分享如何讓這款瀏覽器 3D 家庭派對遊戲在舊平板和入門 Android 手機上運行。遊戲以 TypeScript 和 Three.js(r186)製作,包含 64 款迷你遊戲,並透過按需載入、減少繪圖呼叫及自動調整畫質控制效能。遊戲採用固定 60 Hz 模擬步長,並提供離線遊玩。
Sloth Jam is a free cozy party game that runs in the browser at slothjam.com: 64 one-minute party games, a five-track kart race, and a whole island to explore. I make it on my own. The audience is families, which means the device in the room is often an old tablet or a hand-me-down Android phone, not a gaming PC.
This post covers the stack and the handful of tricks that keep it playable on those devices.
The stack
- TypeScript + Three.js (r186) for the client, bundled with Vite.
- No engine and no model files. There isn't a single glTF or FBX in the repo. Every sloth, tree, kart and building is built in TypeScript from simple shapes with vertex colors.
-
A shared rules package (
@party/rules) in a monorepo, imported by both the client and the server, so match scoring and game rules are written once. -
A small Node server using
wsfor the online town. Online play is in early testing and is off by default until a grown-up turns it on. - A Supabase Edge Function for the grown-up family account, which signs in with an emailed code instead of a password.
- A service worker so the game keeps working offline once it has loaded.
Code splitting: one chunk per game
With 64 mini-games, loading everything up front would be silly. Each game's view lives in its own module and is loaded on demand. The production build ships around a hundred JS chunks, so the first load doesn't pay for games nobody has opened yet.
The "toy" look is a material, not a texture budget
The art style is glossy painted plastic. Instead of textures, every surface uses vertex colors under a clear coat that catches a reflected studio sky, plus one soft sun shadow map. Ground, stone and sand get a matte flag so the town doesn't look wet. That gives a consistent look with almost no texture memory, which matters a lot on phones.
Draw calls are the real budget
A few rules from the engine config:
- Sloths don't cast real shadows. A sloth is seven meshes, so eight sloths in the shadow pass would cost 56 extra draws a frame. Each one stands on a cheap blob shadow instead.
- Cull what the camera can't see. Sloths behind the camera are skipped, instanced props outside the view are thinned out, and indoors the town outside the walls isn't drawn at all.
- Merge, but respect index limits. Static scenery is baked into big merged meshes. One cave hall spiked badly on a Galaxy A56, and the culprit was a bake that went past 65,535 vertices, the most a 16-bit index can address. Big bakes are now split into chunks under that limit.
Quality tiers that pick themselves
There are four tiers:
| Tier | Max pixel ratio | Shadow map | Shadow redraw | Post (lens) | MSAA |
|---|---|---|---|---|---|
| high | 2 | 2048 | every frame | on | 4 |
| medium | 1.75 | 1536 | every frame | on | 2 |
| low | 1.5 | 1024 | every 3rd frame | off | 0 |
| lowest | 1.15 | off | n/a | off | 0 |
Lower tiers also drop building detail and leave out some background characters.
First guess: on boot, the game reads the GPU name through WEBGL_debug_renderer_info and checks it, along with navigator.deviceMemory and hardwareConcurrency, against a short list of patterns for older Mali, Adreno and PowerVR chips and software renderers. That picks a starting tier.
Then it measures. The game loop runs a fixed 60 Hz simulation step and averages real frame times every two seconds. If frames are too slow for the current tier, it first lowers the render scale to 85%, then drops a tier, and finally goes to 70% scale on the lowest tier. On the two lowest tiers, a device that still can't keep up is capped at a steady 30 fps, which feels better than a jittery 40.
Then it remembers. The chosen tier is saved in localStorage with the GPU name for seven days, so the next visit starts at the right level instead of stuttering first. A ?quality= URL parameter overrides it all for testing.
A fixed timestep that forgives slow frames
The simulation ticks at 60 Hz, separate from rendering. A long frame is clamped to 0.25 seconds, and the loop runs at most five catch-up ticks, so a hiccup on a weak device slows the game a little instead of teleporting everyone. Rendering interpolates between ticks.
Phone-friendly details
- The pixel ratio is capped (2 on the top tier), because a 3x phone screen at full resolution is a lot of pixels for very little visible gain.
- The page is a PWA: add it to your home screen and it opens full screen like an app.
- Controls are one stick and one button on touch, keyboard and gamepads, through the Gamepad API.
- The service worker never caches the account server, so sign-in always hits the network.
What I'd tell someone starting a browser 3D game
- Pick your worst target device on day one and test on it every week.
- Count draw calls before you count triangles.
- Let the game measure itself and step down quietly. Players never see a settings menu.
- Split code by feature so the first load stays small.
Sloth Jam is free and in early access at slothjam.com. If you try it on an older phone and it stutters, I'd love to know the model.
Disclosure: I design and direct Sloth Jam myself, and I use AI coding assistants as part of building it. This post was drafted with AI help and checked against the code.
來源:DEV Community · dev.to