A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Scenes are self-contained game states that manage their own display lists, update loops, input handling, and systems. They form the structural backbone of Phaser games, representing different screens like menus, gameplay, pause screens, and game over states.
The base Phaser.Scene class provides the foundation for all game scenes.
class GameScene extends Phaser.Scene {
constructor() {
super({ key: 'GameScene' });
}
init(data) {
// Initialize scene with passed data
this.score = data.score || 0;
this.level = data.level || 1;
}
preload() {
// Load assets for this scene
this.load.image('player', 'assets/player.png');
this.load.audio('bgm', 'assets/music.mp3');
}
create(data) {
// Create game objects and set up scene
this.player = this.add.sprite(400, 300, 'player');
this.music = this.sound.add('bgm');
this.music.play({ loop: true });
}
update(time, delta) {
// Game logic updated every frame
if (this.input.keyboard.addKey('SPACE').isDown) {
this.player.y -= 200 * (delta / 1000);
}
}
}Scenes can be configured with various options:
class MenuScene extends Phaser.Scene {
constructor() {
super({
key: 'MenuScene',
active: true, // Start active
visible: true, // Start visible
pack: { // Preload pack
files: [
{ type: 'image', key: 'logo', url: 'assets/logo.png' }
]
},
cameras: { // Camera configuration
name: 'menuCam',
x: 0,
y: 0,
width: 800,
height: 600
},
map: { // Tilemap auto-load
key: 'menu_map',
tileWidth: 32,
tileHeight: 32
},
physics: { // Physics configuration
default: 'arcade',
arcade: {
gravity: { y: 300 }
}
}
});
}
}Scenes have a defined lifecycle with specific callback methods:
class LifecycleScene extends Phaser.Scene {
init(data) {
// Called first when scene starts
// Use for variable initialization
console.log('Scene initializing with data:', data);
this.playerName = data.playerName || 'Player';
}
preload() {
// Called after init, used for asset loading
this.load.image('background', 'assets/bg.jpg');
this.load.spritesheet('character', 'assets/char.png', {
frameWidth: 32,
frameHeight: 48
});
// Show loading progress
this.load.on('progress', (percent) => {
console.log('Loading:', Math.round(percent * 100) + '%');
});
}
create(data) {
// Called after preload completes
// Create game objects and set up scene
this.add.image(400, 300, 'background');
this.character = this.add.sprite(100, 400, 'character');
// Set up input
this.cursors = this.input.keyboard.createCursorKeys();
// Scene is now ready for interaction
console.log('Scene ready for', this.playerName);
}
update(time, delta) {
// Called every frame while scene is active
if (this.cursors.left.isDown) {
this.character.x -= 150 * (delta / 1000);
}
if (this.cursors.right.isDown) {
this.character.x += 150 * (delta / 1000);
}
}
}Scenes emit events during their lifecycle:
class EventScene extends Phaser.Scene {
create() {
// Listen to scene events
this.events.on('create', () => {
console.log('Scene created');
});
this.events.on('wake', (sys, data) => {
console.log('Scene woken up with data:', data);
});
this.events.on('sleep', () => {
console.log('Scene going to sleep');
});
this.events.on('pause', () => {
console.log('Scene paused');
});
this.events.on('resume', () => {
console.log('Scene resumed');
});
this.events.on('shutdown', () => {
console.log('Scene shutting down');
});
this.events.on('destroy', () => {
console.log('Scene destroyed');
});
}
}The Scene Plugin (this.scene) provides methods for managing scenes:
class GameScene extends Phaser.Scene {
create() {
// Start another scene
this.scene.start('NextScene', { score: this.score });
// Launch scene in parallel
this.scene.launch('UIScene');
this.scene.launch('BackgroundScene');
// Switch to scene (stops current, starts new)
this.scene.switch('MenuScene');
// Pause/Resume scenes
this.scene.pause('GameScene');
this.scene.resume('GameScene');
// Sleep/Wake scenes (like pause but stops updates)
this.scene.sleep('GameScene');
this.scene.wake('GameScene', { newData: 'value' });
// Stop scene
this.scene.stop('GameScene');
// Restart current scene
this.scene.restart({ resetScore: true });
// Set scene visibility
this.scene.setVisible(false, 'BackgroundScene');
// Move scenes up/down in render order
this.scene.moveUp('UIScene');
this.scene.moveDown('BackgroundScene');
this.scene.bringToTop('UIScene');
this.scene.sendToBack('BackgroundScene');
}
}Access the global scene manager through this.scene.manager:
class ManagerScene extends Phaser.Scene {
create() {
const manager = this.scene.manager;
// Get scene references
const gameScene = manager.getScene('GameScene');
const activeScenes = manager.getScenes(true); // Only active
const allScenes = manager.getScenes(false); // All scenes
// Scene queries
const isActive = manager.isActive('GameScene');
const isVisible = manager.isVisible('UIScene');
const isSleeping = manager.isSleeping('BackgroundScene');
// Scene operations
manager.start('GameScene', { level: 2 });
manager.pause('GameScene');
manager.resume('GameScene');
manager.sleep('GameScene');
manager.wake('GameScene');
manager.stop('GameScene');
manager.switch('MenuScene');
// Batch operations
manager.pauseAll();
manager.resumeAll();
// Scene events on manager
manager.on('start', (event, scene) => {
console.log('Scene started:', scene.sys.settings.key);
});
manager.on('ready', (event, scene) => {
console.log('Scene ready:', scene.sys.settings.key);
});
}
}Run multiple scenes simultaneously for layered functionality:
// Main game scene
class GameScene extends Phaser.Scene {
constructor() {
super({ key: 'GameScene' });
}
create() {
// Set up main game
this.add.image(400, 300, 'background');
this.player = this.add.sprite(100, 400, 'player');
// Launch UI overlay
this.scene.launch('UIScene');
// Launch background effects
this.scene.launch('ParticleScene');
// Communication between scenes
this.events.on('updateScore', (score) => {
this.scene.get('UIScene').events.emit('scoreChanged', score);
});
}
}
// UI overlay scene
class UIScene extends Phaser.Scene {
constructor() {
super({ key: 'UIScene' });
}
create() {
// Create UI elements that stay on top
this.scoreText = this.add.text(16, 16, 'Score: 0', {
fontSize: '32px',
fill: '#000'
});
// Listen for score updates
this.events.on('scoreChanged', (score) => {
this.scoreText.setText('Score: ' + score);
});
// UI doesn't scroll with camera
this.cameras.main.setScroll(0, 0);
}
}
// Background effects scene
class ParticleScene extends Phaser.Scene {
constructor() {
super({ key: 'ParticleScene' });
}
create() {
// Background particle effects
this.particles = this.add.particles(400, 300, 'particle', {
speed: 50,
lifespan: 2000,
quantity: 2,
scale: { start: 0.5, end: 0 }
});
// Render behind main scene
this.scene.moveDown();
}
}Scenes can communicate through events and data:
// Game scene sends events
class GameScene extends Phaser.Scene {
playerDied() {
// Send event to UI
this.events.emit('playerDied');
// Send event to other scenes via scene manager
this.scene.manager.scenes.forEach(scene => {
if (scene.sys.settings.key !== 'GameScene') {
scene.events.emit('gameOver', { score: this.score });
}
});
// Switch to game over with data
this.scene.start('GameOverScene', {
score: this.score,
level: this.level,
time: this.gameTime
});
}
}
// UI scene listens for events
class UIScene extends Phaser.Scene {
create() {
// Listen to game scene events
const gameScene = this.scene.get('GameScene');
gameScene.events.on('playerDied', () => {
this.showGameOverUI();
});
// Global scene events
this.events.on('gameOver', (data) => {
this.displayFinalScore(data.score);
});
}
}Create smooth transitions between scenes:
class TransitionScene extends Phaser.Scene {
fadeToScene(sceneKey, duration = 1000) {
// Fade out current scene
this.cameras.main.fadeOut(duration, 0, 0, 0);
// When fade completes, switch scenes
this.cameras.main.once('camerafadeoutcomplete', () => {
this.scene.start(sceneKey);
});
}
slideToScene(sceneKey, direction = 'left') {
const camera = this.cameras.main;
const targetX = direction === 'left' ? -800 : 800;
// Slide camera off screen
this.tweens.add({
targets: camera,
scrollX: targetX,
duration: 1000,
ease: 'Power2',
onComplete: () => {
this.scene.start(sceneKey);
}
});
}
create() {
// Transition buttons
this.add.text(100, 100, 'Fade Transition', { fontSize: '24px' })
.setInteractive()
.on('pointerdown', () => {
this.fadeToScene('NextScene');
});
this.add.text(100, 150, 'Slide Transition', { fontSize: '24px' })
.setInteractive()
.on('pointerdown', () => {
this.slideToScene('NextScene', 'left');
});
}
}Handle asset loading with dedicated loading scenes:
class LoadingScene extends Phaser.Scene {
constructor() {
super({ key: 'LoadingScene' });
}
preload() {
// Create loading UI
const progressBar = this.add.graphics();
const progressBox = this.add.graphics();
progressBox.fillStyle(0x222222);
progressBox.fillRect(240, 270, 320, 50);
const loadingText = this.add.text(400, 240, 'Loading...', {
fontSize: '20px',
fill: '#ffffff'
}).setOrigin(0.5);
const percentText = this.add.text(400, 300, '0%', {
fontSize: '18px',
fill: '#ffffff'
}).setOrigin(0.5);
// Update progress
this.load.on('progress', (percent) => {
progressBar.clear();
progressBar.fillStyle(0xffffff);
progressBar.fillRect(250, 280, 300 * percent, 30);
percentText.setText(Math.round(percent * 100) + '%');
});
this.load.on('complete', () => {
loadingText.setText('Complete!');
this.time.delayedCall(500, () => {
this.scene.start('GameScene');
});
});
// Load game assets
this.loadGameAssets();
}
loadGameAssets() {
this.load.image('background', 'assets/bg.jpg');
this.load.spritesheet('player', 'assets/player.png', {
frameWidth: 32,
frameHeight: 48
});
this.load.audio('music', 'assets/music.mp3');
// ... more assets
}
}Each scene has its own data manager for storing scene-specific data:
class DataScene extends Phaser.Scene {
create() {
// Set scene data
this.data.set('score', 100);
this.data.set('level', 1);
this.data.set('player', {
name: 'Hero',
health: 100,
mana: 50
});
// Get scene data
const score = this.data.get('score');
const player = this.data.get('player');
// Increment values
this.data.inc('score', 10);
this.data.inc('level');
// Toggle boolean
this.data.toggle('paused');
// Listen for data changes
this.data.on('setdata', (parent, key, value) => {
console.log('Data set:', key, value);
});
this.data.on('changedata-score', (parent, value, prevValue) => {
console.log('Score changed from', prevValue, 'to', value);
});
// Remove data
this.data.remove('temporaryValue');
// Reset all data
this.data.reset();
}
}Access game-wide data through the global registry:
class RegistryScene extends Phaser.Scene {
create() {
// Global data accessible from any scene
this.registry.set('playerName', 'Hero');
this.registry.set('highScore', 5000);
this.registry.set('unlockedLevels', [1, 2, 3]);
// Get global data
const playerName = this.registry.get('playerName');
const highScore = this.registry.get('highScore');
// Listen for global data changes
this.registry.on('setdata', (parent, key, value) => {
if (key === 'highScore') {
this.updateHighScoreDisplay(value);
}
});
// Any scene can update global data
this.registry.inc('highScore', 100);
}
}Each scene has access to various game systems:
class SystemsScene extends Phaser.Scene {
create() {
// Core systems
const game = this.game; // Game instance
const scene = this.scene; // Scene plugin
const anims = this.anims; // Animation manager
const cache = this.cache; // Cache manager
const registry = this.registry; // Global registry
const sound = this.sound; // Sound manager
const textures = this.textures; // Texture manager
// Scene-specific systems
const cameras = this.cameras; // Camera manager
const add = this.add; // GameObject factory
const make = this.make; // GameObject creator
const physics = this.physics; // Physics world
const input = this.input; // Input manager
const load = this.load; // Loader plugin
const time = this.time; // Clock
const tweens = this.tweens; // Tween manager
const lights = this.lights; // Lights system
const data = this.data; // Scene data manager
// Display management
const children = this.children; // Display list
const displayList = this.displayList; // Same as children
const updateList = this.updateList; // Update list
// Events
const events = this.events; // Scene events
}
}Extend scenes with custom functionality:
class CustomScene extends Phaser.Scene {
constructor(config) {
super(config);
// Custom properties
this.gameState = 'playing';
this.enemies = [];
this.powerUps = [];
}
init() {
// Custom initialization
this.setupCustomSystems();
}
setupCustomSystems() {
// Custom enemy manager
this.enemyManager = {
spawn: (x, y, type) => {
const enemy = this.add.sprite(x, y, type);
this.enemies.push(enemy);
return enemy;
},
update: () => {
this.enemies.forEach(enemy => {
// Update enemy logic
});
},
removeAll: () => {
this.enemies.forEach(enemy => enemy.destroy());
this.enemies = [];
}
};
// Custom event system
this.gameEvents = new Phaser.Events.EventEmitter();
this.gameEvents.on('enemyDefeated', this.onEnemyDefeated, this);
this.gameEvents.on('powerUpCollected', this.onPowerUpCollected, this);
}
update(time, delta) {
if (this.gameState === 'playing') {
this.enemyManager.update();
}
}
onEnemyDefeated(enemy) {
// Handle enemy defeat
this.data.inc('score', 100);
this.gameEvents.emit('scoreUpdated', this.data.get('score'));
}
onPowerUpCollected(powerUp) {
// Handle power-up collection
this.data.set('powerUpActive', true);
this.time.delayedCall(5000, () => {
this.data.set('powerUpActive', false);
});
}
}This comprehensive scene management system provides the structure and flexibility needed to create complex, multi-state games with smooth transitions and robust data management.
Install with Tessl CLI
npx tessl i tessl/npm-phaser