Best Practices for Games on Arcadible
Players judge games on Arcadible the way they judge games on any other platform: how fast they load, how they feel, and how polished they are. This guide covers what's specific to shipping a game on the web platform, in the order a player meets your game. It assumes you already know how to make a game.
Arcadible requires only what arc validate checks. Everything here is advice: take what fits your game.
Load fast
There's no install step: a new player downloads your game when they first open it. Set your targets for a phone on a mobile connection.
- Paint within 1 second. Put a small loading screen directly in
index.html, with inline CSS, so it renders before any script loads. Use the same art as your splash so the transition from the store matches. - Be playable within about 5 seconds. Load only the first screen, the first level, and their assets, and stream the rest during play, from your build or your own CDN.
- Compress what you ship. Choose compressed formats for textures, models, audio, and fonts, and minify your code. Most engines make these choices for you.
- Show progress. Base it on bytes loaded where you can, such as for a bundled file loaded with
fetch. Browsers don't report progress for ES module imports, so for those, animate the loading screen so it doesn't look stuck. - Handle failure. Time out and retry requests, and show an error with a retry option if loading fails.
Test with your browser's network panel throttled to a slow mobile connection and the cache disabled, which matches a new player's first load.
Make the first touch part of the game
Browsers block audio, fullscreen, and pointer lock until the player interacts with the page. A common workaround is a "Tap to play" screen that exists only to collect that interaction. Instead, design the opening so the player's first natural input provides it:
- Open on something interactive, such as an attract-mode title, a controllable character, or a menu. The player's first action, whether a jump, a steer, or selecting "New Game", is also the interaction the browser requires.
- Start audio on that input by calling
audioContext.resume()in your firstpointerdownorkeydownhandler, and fade the music in. - Design the opening to work without sound. Nothing plays before the first input, so use motion to prompt it.
Get to play quickly
- Give control early. Make logos skippable, and keep story sequences and settings out of the way of the first moment of play.
- Teach through play. A new player should understand the goal within a few seconds without reading instructions. Introduce one mechanic at a time, and show gestures with animation.
- Let players play before signing up. Ask for an account once they have progress to keep.
Fit every screen
Players use phones in both orientations, tablets, ultrawide monitors, and resizable desktop windows.
- Handle resizing with a
ResizeObserveron the canvas, rather than reading the size once at startup. - Choose a scaling strategy, whether that's letterboxing, extending the view, or reflowing the UI, and test the extremes: tall phones, square windows, 21:9 monitors.
- Keep UI inside the safe area, away from notches and home indicators, with
env(safe-area-inset-*). Let the game world extend behind them. - Declare your
orientation, so the Arcadible desktop app can shape its window for you. In a browser, show a "rotate your device" screen if your game only works one way.
A canvas can't use env(). Copy the insets into custom properties, and read them in JavaScript when the canvas resizes:
:root {
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
}
const style = getComputedStyle(document.documentElement);
const safeTop = parseFloat(style.getPropertyValue("--safe-top"));
const safeBottom = parseFloat(style.getPropertyValue("--safe-bottom"));
Support every input you declare
Declare your inputs accurately, and support each one fully, menus included. Switch prompts to whichever device the player used last.
- Keyboard: use
event.code, which names the physical key, so WASD stays in place on AZERTY and other layouts. Let players rebind keys. - Mouse: for mouse-look, use pointer lock with
requestPointerLock({ unadjustedMovement: true })for raw input. Escape always releases the lock, so pause the game whenpointerlockchangereports it's released. - Gamepad: the Gamepad API has no button events, so poll
navigator.getGamepads()every frame. With the"standard"mapping, button 0 is the bottom face button. Show Xbox, PlayStation, or Switch glyphs based ongamepad.id. - Touch: use Pointer Events for multi-touch. Make targets at least 44 CSS pixels, keep them clear of screen edges, which are reserved for system gestures, and hide touch controls on devices without touch.
Turn off browser defaults
Browsers scroll the page, select text on a double tap, open a context menu on right-click, and zoom on a pinch, which a game rarely wants. The starters turn these off already, in their index.html and main.js. If you started elsewhere, here's how.
In CSS, stop selection, scrolling, bouncing, and browser gestures:
html,
body {
margin: 0;
height: 100%;
overflow: hidden;
overscroll-behavior: none;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
}
canvas {
touch-action: none;
}
In the viewport tag, turn off zoom, and extend the page behind the notch and other display cutouts:
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"
/>
In JavaScript, suppress the context menu, and keep the keys your game uses from scrolling the page:
const GAME_KEYS = new Set([
"Space",
"ArrowUp",
"ArrowDown",
"ArrowLeft",
"ArrowRight",
]);
addEventListener("contextmenu", (e) => e.preventDefault());
addEventListener("keydown", (e) => {
if (GAME_KEYS.has(e.code)) e.preventDefault();
});
- Set a background color that matches your loading screen, so players never see a white flash.
- Style every element, so no default browser buttons, links, or form controls show.
- Hide the cursor when it isn't in use, such as during gamepad play.
Run smoothly
Aim for 60 frames per second, with a floor of 30 on the lowest-end device you support. Consistent frame times matter more than a high average.
- Drive the loop with
requestAnimationFrame, and never assume 60 Hz. Displays run at 90, 120, 144 Hz and more, so step your simulation at a fixed rate and interpolate when drawing. - Avoid per-frame allocation. Garbage collection pauses cause dropped frames. Pool objects, reuse arrays, and avoid creating closures in the loop.
- Warm up during loading. Compile shaders, upload textures, and decode audio before play, not the first time they're needed.
- Move heavy work off the main thread with Web Workers, and rendering too with
OffscreenCanvas. Arcadible games are cross-origin isolated, so threads andSharedArrayBufferare available. - Budget memory. Browsers on low-end phones can terminate a page using a few hundred megabytes. A 2048 × 2048 RGBA texture uses 16 MB of GPU memory, regardless of its file size.
- Mind the GPU. Most engines pick WebGPU or WebGL for you. In WebGL, avoid calls that stall the GPU, such as
getError()and synchronousreadPixels(). See MDN's WebGL best practices.
Scale resolution
Phones have a device pixel ratio of up to 3, which is nine times the pixels of a ratio of 1. Above 2, the visual gain rarely justifies the fill-rate cost.
- Cap the pixel ratio at 2, or lower for heavy 3D.
- Scale dynamically. Lower the render resolution when frame time rises, and raise it when there's headroom. Render the UI at full resolution.
- For pixel art, scale by whole numbers instead. Render to a small buffer, such as 320 × 180, and draw it at the largest whole-number multiple that fits the canvas at its full device resolution, with
imageSmoothingEnabledoff. Leave the pixel ratio uncapped: a small buffer costs little fill rate, and fractional scaling makes pixels uneven.
Test on real hardware
Development machines are much faster than typical player devices. Test on the phones you have, including an older or cheaper one if you can.
Pause, save, and resume
A page can be hidden or closed at any time, without warning.
- Pause on
visibilitychangeandblur, audio included, and resume to a pause menu. In the desktop app, your game keeps running in the background unless you pause it. - Save continuously, at checkpoints and when the page is hidden.
visibilitychangeandpagehideare the last events you can count on. - Keep progress in saves, which carry over to your next version, where browser storage doesn't.
Other recommendations
- Accessibility: reduce screen shake and flashing when
prefers-reduced-motionis set, don't convey information by color alone, and offer subtitles and control remapping. - Language: pick a default from
navigator.languages, let players change it, and format numbers and dates withIntl. - Network: let the game start without a server, retry with backoff, and show players a message they can act on rather than a raw error (Networking).
Next steps
- Runtime Limitations: what the platform allows, and its checklist.
- Testing Your Game: staging, the desktop app, and real devices.
- Your Store Page: the art and text players judge before they play.
- Going Public: the checklist before players can find your game.