Building with Phaser
Phaser is plain JavaScript drawing to a canvas, which is what the platform runs. There is
no export step and no bridge layer to fight: your scene talks to the host in the same
postMessage protocol every other game here uses.
What Phaser gives you that needs care is the opposite of a visual editor's problem. Nothing
is hidden — but Phaser's conveniences (Math.RND, the delta-time loop, tweens) are built
for games where nobody is being paid, and two of them cannot decide anything here.
Read the trust model first. It explains why the rules below exist.
Where Phaser fits
A game has two halves, and Phaser is only ever the second:
| Half | File | Runs | Phaser? |
|---|---|---|---|
| The rules | game.ts / game.js |
On our server | No — plain ES2015 JavaScript, no engine |
| The bundle | your scene and assets | In the player's browser | Yes — it draws and takes input |
For a pooled game the rules are a ServerGame (see
determinism). The server plays them — deals the
board, applies each move, counts the score — and your scene draws what it is sent. The
scene is never given the seed. For a head-to-head game the rules are a match handler;
the Tic-Tac-Toe tutorial builds one.
That removes both of the things Phaser does that used to need care here:
Phaser.Math.RNDmay be used for anything in the scene — nothing the scene does decides anything. The deal, the spawns, the next piece are drawn in your rules, on the server, from the platform's generator.- The delta-time loop may drive every animation and tween. The game's clock is the server's; your scene only shows it.
The one thing the scene must not do is draw a board the server has not sent. A piece tweened to where the player dropped it, before the server has said it landed there, is a board that does not exist — tween from the old frame to the new one instead.
Ship Phaser inside your bundle
This is the mistake that costs the most time. Your game runs in a sandboxed frame with
no network access — a <script src="https://cdn.jsdelivr.net/…/phaser.min.js"> loads
nothing, Phaser is undefined, and your game never reaches ready.
Download the build and put it next to your code:
<script src="phaser.min.js"></script>
<script src="crazy8s.js"></script>
<script src="main.js"></script>
Talking to the platform
With a bundler, use createSoloClient from @crazy8s/game-sdk — it is the same as in the
Excalibur guide. Without one, the whole protocol for a
pooled game is this:
// Say you are listening. The host holds the first frame until you do.
parent.postMessage({ type: "ready", protocolVersion: 1 }, "*");
window.addEventListener("message", (event) => {
if (event.source !== parent) return;
const message = event.data;
if (message.type === "match-state") {
// A frame: { tick, tickRate, over, state } — `state` is your rules' view.
this.frame = message.view;
this.redraw(this.frame.state);
}
if (message.type === "match-complete") {
// The run is over. The score is the server's.
this.showFinal(message.score);
}
});
Send what the player does — an action and at most one whole number:
parent.postMessage({ type: "intent", action: "tap", payload: { value: tileIndex } }, "*");
The next frame shows what became of it. There is no completion to report and no score to send: the server ends the run and counts it.
Sizing your canvas
The platform gives your frame a width and a height. Use the scale manager, not a fixed size:
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 720,
height: 1280,
}
FIT letterboxes rather than stretching. A stretched board is one where a piece is not
where the player thinks it is — and on a staked game that is a dispute.
Design for a phone held upright. That is what nearly every player here has.
Size
phaser.min.js is around 1.2MB. That is comfortably inside the 10MB cap, but it is the
largest single thing most Phaser games here carry, so take the build you need:
phaser-arcade-physics.min.js is roughly half, and a turn-based game needs no physics at
all. Check the total before adding uncompressed audio.
Honest assessment
Phaser is the best-known option on this list, and for most people arriving with a game idea it is the shortest path from idea to something on screen — a large ecosystem, a lot of tutorials, and nothing to learn about exports.
Excalibur.js is a better fit if you are starting fresh and comfortable in TypeScript: it is smaller, typed throughout, and the platform's own card games are built on it, so its edges are ones we have already hit. Defold wins on bundle size if that is your constraint.
Pick Phaser if you already know it. The rules above are the whole of the difference.
Common mistakes
| Symptom | Cause |
|---|---|
| The game never becomes ready | Phaser loaded from a CDN — there is no network in the frame |
| Certification rejects the bundle | Phaser.Math.RND deciding something that is scored |
| Results vary between runs | delta driving game logic rather than drawing |
| Score rejected as non-integer | A tween or delta calculation left it a float — round it |
| The board looks stretched | Phaser.Scale.RESIZE or ENVELOP instead of FIT |
| Works locally, fails once uploaded | An asset loaded by URL rather than shipped in the bundle |
Related
- Getting started — the path end to end
- Trust model — what your game may and may not decide
- Writing the server rules — the half that decides outcomes
- Determinism — proving your rules are reproducible
- Publishing lifecycle — review, updates, revenue
- Building a client — for a front end, not a game
- Server API reference — every RPC, and who may call it
- Engine guides: Excalibur.js · Phaser · Defold
