Example: Phaser 3
Wire a Phaser 3 game to the IncentIA external-games host. The Phaser side is a regular Phaser project; only the bootstrap script uses the helper from the quick start.
Project layout
my-phaser-game/
index.html
main.js
incentia-sdk.js
game/
Boot.js
Play.js
assets/
bg.png
button.png
Run zip -r my-phaser-game.zip * from inside the folder, then upload it in the games maestro (see Submission).
index.html
<!doctype html>
<html>
<body>
<div id="game-root"></div>
<script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script>
<script type="module" src="./main.js"></script>
</body>
</html>
main.js
import { readPlayContext, complete } from './incentia-sdk.js';
import { BootScene } from './game/Boot.js';
import { PlayScene } from './game/Play.js';
const ctx = readPlayContext();
// Stash the play context on the global registry so any scene can submit a
// completion signal without re-parsing the URL.
const game = new Phaser.Game({
type: Phaser.AUTO,
width: 480,
height: 320,
parent: 'game-root',
scene: [BootScene, PlayScene],
backgroundColor: '#1b1b1f',
});
game.registry.set('incentia.ctx', ctx);
game.registry.set('incentia.complete', complete);
game/Play.js
export class PlayScene extends Phaser.Scene {
constructor() {
super('Play');
}
create() {
this.add.image(240, 160, 'bg');
const button = this.add.image(240, 250, 'button').setInteractive({ useHandCursor: true });
let score = 0;
const text = this.add.text(20, 20, 'Score: 0', { fontFamily: 'sans-serif', fontSize: 18, color: '#fff' });
// Demo: tap the button to score, then submit on a long press.
button.on('pointerdown', () => {
score += 10;
text.setText('Score: ' + score);
});
this.input.keyboard?.on('keydown-SPACE', () => this.submit(score));
button.on('pointerover', () => button.setScale(1.05));
button.on('pointerout', () => button.setScale(1));
this.add.text(20, 290, 'Press Space to submit', { fontSize: 12, color: '#999' });
}
async submit(score) {
const ctx = this.registry.get('incentia.ctx');
const complete = this.registry.get('incentia.complete');
if (!ctx) {
this.scene.add('Outcome', { create: function () { this.add.text(20, 20, 'Dev outcome: win'); } }, true);
return;
}
try {
const outcome = await complete(ctx, { score });
const msg = outcome.outcome === 'win' ? `You won ${outcome.prize?.name ?? 'a prize'}` : 'Better luck next time';
this.add.text(120, 140, msg, { fontSize: 18, color: '#fff', backgroundColor: '#000', padding: { x: 8, y: 4 } });
} catch (err) {
this.add.text(20, 280, `Error: ${err.message}`, { fontSize: 12, color: '#f66' });
}
}
}
game/Boot.js
export class BootScene extends Phaser.Scene {
constructor() {
super('Boot');
}
preload() {
this.load.image('bg', 'assets/bg.png');
this.load.image('button', 'assets/button.png');
}
create() {
this.scene.start('Play');
}
}
Signal schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["score"],
"properties": { "score": { "type": "integer", "minimum": 0 } }
}
Tips
- Phaser ships its CDN build as a global
Phaser— use<script>notimportfor the engine itself; only the helper and your game modules needtype="module". - Phaser is about 1.2 MB. The default 25 MB bundle limit leaves plenty of room, but watch your asset folder.
- The Phaser script above loads from a public CDN. If you need the game to work offline or on a locked-down network, put
phaser.min.jsinside your zip and load it with a relative path.