Lesson 24
Phaser basics: when vanilla canvas starts fighting back.
Plain canvas is excellent for small games. Phaser becomes useful when you need scenes, asset loading, sprites, cameras, arcade physics and structured input without rebuilding every tool yourself.
1. When Phaser is worth it
Use vanilla canvas when the game is small, custom and easy to hold in one file. Use Phaser when your game needs multiple screens, animated sprites, object groups, pointer input, tweens, cameras or physics. A tower defense game with a map scene, gameplay scene, selectable towers, projectiles and many sprites is a natural Phaser candidate. A one-button score game usually is not.
The tradeoff is control versus structure. Vanilla canvas gives you complete control over drawing and timing. Phaser gives you a tested architecture for common 2D game problems. The right choice is the one that reduces the amount of boring engine code you must maintain.
const config = {
type: Phaser.AUTO,
width: 672,
height: 480,
parent: "game",
backgroundColor: "#0a0f1a",
physics: {
default: "arcade",
arcade: { debug: false }
},
scene: [BootScene, MapScene, GameScene]
};
const game = new Phaser.Game(config);
Source pattern: Supagames Guardian Towers uses Phaser scenes, pointer input, generated textures and HUD updates for a larger tower defense structure.
2. Scenes replace giant state switches
Many vanilla games start with `state = "menu"` and grow into a long list: menu, play, pause, win, lose, shop, map select. Phaser scenes are a cleaner boundary. One scene can preload assets, another can select a map, and another can run gameplay. You can restart a scene without manually resetting every array in the whole app.
class MapScene extends Phaser.Scene {
constructor() {
super({ key: "MapScene" });
}
create() {
this.add.text(336, 40, "SELECT MAP", {
fontSize: "28px",
fontFamily: "Georgia, serif",
color: "#ffd700"
}).setOrigin(0.5);
this.add.rectangle(336, 220, 220, 90, 0x182033)
.setStrokeStyle(2, 0xffd93d)
.setInteractive({ useHandCursor: true })
.on("pointerdown", () => this.scene.start("GameScene", { mapIndex: 0 }));
}
}
Scenes are especially helpful for big games because they make transitions explicit. A scene owns its objects, input handlers and timers. When the scene stops, those objects are easier to clean up.
3. Preload, create and update
Phaser's basic rhythm is `preload`, `create` and `update`. Preload loads images, audio and atlases. Create builds the scene after assets are ready. Update runs each frame. This is similar to the game loop lessons, but Phaser handles the outer loop and gives you scene methods.
class GameScene extends Phaser.Scene {
preload() {
this.load.image("hero", "assets/hero.png");
this.load.image("gem", "assets/gem.png");
}
create() {
this.player = this.physics.add.sprite(80, 380, "hero");
this.cursors = this.input.keyboard.createCursorKeys();
this.gems = this.physics.add.group();
this.gems.create(360, 240, "gem");
this.physics.add.overlap(this.player, this.gems, this.collectGem, null, this);
}
update() {
const speed = 180;
this.player.setVelocity(0);
if (this.cursors.left.isDown) this.player.setVelocityX(-speed);
if (this.cursors.right.isDown) this.player.setVelocityX(speed);
if (this.cursors.up.isDown) this.player.setVelocityY(-speed);
if (this.cursors.down.isDown) this.player.setVelocityY(speed);
}
}
Do not put setup code in `update`. If you create sprites every frame by accident, the browser will slow down quickly. Create once, update values each frame.
4. Generated textures are useful for prototypes
You do not need final art to start. Phaser can generate textures with graphics objects, which is useful for prototypes and for simple Supagames-style icons. Later, replace generated textures with spritesheets when the mechanic is proven.
function makeTowerTexture(scene) {
const g = scene.make.graphics({ x: 0, y: 0, add: false });
g.fillStyle(0x223366);
g.fillCircle(16, 16, 15);
g.fillStyle(0xffd93d);
g.fillRect(12, 4, 8, 24);
g.generateTexture("tower-basic", 32, 32);
g.destroy();
}
Generated art should still be intentional. Use consistent colors, readable silhouettes and small highlights so the prototype is pleasant enough to test.
5. Keep DOM UI when it helps
Phaser can render text and buttons, but HTML remains strong for site-level UI, comments, ads, ratings, navigation and complex panels. A hybrid approach works well: Phaser owns the game scene; DOM owns the surrounding page and persistent HUD. Update DOM values from scene methods when gold, lives or wave changes.
class GameScene extends Phaser.Scene {
create() {
this.gold = 150;
this.hudGold = document.getElementById("goldText");
this.updateHud();
}
spendGold(cost) {
if (this.gold < cost) return false;
this.gold -= cost;
this.updateHud();
return true;
}
updateHud() {
if (this.hudGold) this.hudGold.textContent = this.gold;
}
}
This keeps game rendering fast while preserving normal website behavior. It also makes AdSense, comments and SEO content easier to manage outside the canvas.
6. Phaser QA checklist
- Each scene owns one job: boot, menu, map, gameplay, results or credits.
- Assets load in `preload`; objects are created in `create`; movement happens in `update`.
- Restarting a scene resets timers, groups and input handlers.
- DOM HUD elements are checked for null before updating.
- Touch and pointer input use the same gameplay functions as keyboard input.
- The game still explains itself with surrounding page text, not only canvas text.
Phaser is not a shortcut around design. It is a way to spend less time writing engine plumbing and more time making the game readable, fair and satisfying.