← All documentation

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.RND may 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