Phaser (JavaScript / TypeScript)
Wire a Phaser 3 game to Yes2SDK with
@yes2games/js/phaser: one lifecycle object pauses and mutes the game around ads and platform pause events, and resumes WebAudio after every ad.
This page follows the phaser-ts template in the yes2sdk-js repo (Phaser 3, TypeScript, Vite). For the general setup, see the JavaScript / TypeScript Integration guide.
Install
npm i phaser @yes2games/js
Or start from the template: copy templates/phaser-ts out of the repo and change "@yes2games/js": "file:../.." in its package.json to the published version ("^0.1.0").
@yes2games/js/phaser does not import Phaser. It is typed against the parts of Phaser.Game it uses (pause(), resume(), sound.mute, sound.context and the sound manager's "mute" event), so it adds no second copy of Phaser to your bundle.
Boot order
- Get the SDK. In
npm run devthere is no SDK on the page, so install the mock. In a dashboard-bundled build the real SDK is already there. await sdk.initializeAsync()before the game boots.- Create the lifecycle in Phaser's
preBootcallback, so it is attached before any scene runs. - Report loader progress from the boot scene's
preload. - Call
startGameAsync()once the first interactive scene is up.
// src/sdk.ts
import { requireSdk, type Yes2SDK } from "@yes2games/js";
export async function loadSdk(): Promise<Yes2SDK> {
if (import.meta.env.DEV) {
const { installYes2SDKMock } = await import("@yes2games/js/mock");
installYes2SDKMock(); // dropped from `vite build`
}
return requireSdk(); // throws a clear error if no SDK is on the page
}
// src/main.ts
import Phaser from "phaser";
import { createLoadingProgressReporter, installInputGuards } from "@yes2games/js";
import { createPhaserLifecycle } from "@yes2games/js/phaser";
import { loadSdk } from "./sdk";
const canvas = document.getElementById("game-canvas") as HTMLCanvasElement;
installInputGuards(canvas); // arrow and space keys no longer scroll the host page
const sdk = await loadSdk();
await sdk.initializeAsync();
new Phaser.Game({
type: Phaser.WEBGL, // a provided canvas needs an explicit renderer type
canvas,
scene: [BootScene, GameScene],
callbacks: {
preBoot: (game) => {
const lifecycle = createPhaserLifecycle(game).attach();
// keep `lifecycle` and a progress reporter where your scenes can reach them
},
},
});
<canvas id="game-canvas" data-yes2-canvas></canvas>
<script type="module" src="./src/main.ts"></script>
data-yes2-canvas tells the dashboard which canvas to capture for screenshots. Without it the dashboard falls back to #unity-canvas, then the largest visible canvas.
Loading progress
Phaser's loader reports a 0 to 1 fraction. createLoadingProgressReporter() forwards it to Yes2SDK.setLoadingProgress only when the integer percentage changes:
export class BootScene extends Phaser.Scene {
preload(): void {
this.load.on("progress", (value: number) => progress.fraction(value));
// this.load.image(...), this.load.audio(...)
}
create(): void {
progress.percent(100);
this.scene.start("Game");
}
}
Keep progress moving during a long load. On Jest in Manual loading mode, the player is sent back to the Jest home screen after 15 seconds without a progress update (see Lifecycle & Events).
Ready, gameplay and ads
export class GameScene extends Phaser.Scene {
async create(): Promise<void> {
// build the menu...
await sdk.startGameAsync(); // the menu is up and interactive
}
startLevel(): void {
sdk.game.gameplayStart(); // real gameplay only, not menus
}
async endLevel(): Promise<void> {
sdk.game.gameplayStop();
await lifecycle.showInterstitial("level-complete");
}
async watchAdForCoins(): Promise<void> {
if (lifecycle.ads.isAdShowing()) return;
const outcome = await lifecycle.showRewarded("bonus-coins");
if (outcome === "viewed") this.addCoins(10); // grant only on "viewed"
}
}
lifecycle.showInterstitial() and showRewarded() go through the runtime's one-ad-at-a-time latch and watchdog, and never reject. They resolve "shown", "viewed", "dismissed" or "noFill". You can still pass your own callbacks as the second argument (adViewed, noFill and so on); they run alongside the lifecycle's pause and resume. See Ad Runtime & Engine Parity.
What createPhaserLifecycle(game) does
- On
beforeAdand the SDK'spauseevent:game.pause()andgame.sound.mute = true. - On
afterAdandresume:game.resume(), and the game's own mute choice comes back. Each source is tracked separately, so an ad that ends during a platform pause does not resume the game early. - Follows the platform mute switch (
audioEnabledChange, plussession.isAudioEnabled()at attach). - Follows your own mute choice through the sound manager's
"mute"event. Asound.mute = falseyou write while the helper holds the game muted applies when the helper unmutes. It never readssound.muteback, because that getter lags behind writes while the AudioContext is suspended. - Resumes
game.sound.contextafter every ad, fromafterAdand again when the show promise settles, if the WebAudio context was left suspended.
Call attach() after initializeAsync(). detach() removes the SDK event listeners.
Build and upload
npm run build # tsc + vite build into dist/
Zip the contents of dist/ so that index.html sits at the top of the zip, and upload it to the Yes2Games Dashboard. The template's vite.config.ts sets base: "./" and raises chunkSizeWarningLimit to 2000 kB, because Phaser alone is about 1.2 MB minified. See Vite Build Settings.