React-like API
Familiar hooks pattern inspired by React’s useState, useEffect, and more
Phaser gives you registry (global) and data (local) for state management. They work fine, but the API is verbose and error-prone:
// Phaser's built-in way - this == scenethis.game.registry.set('volume', 0.5);const volume = this.game.registry.get('volume');
this.game.registry.events.on('changedata-volume', (game, value) => { console.log('Volume changed to', value);});
this.data.set('score', 42);const score = this.data.get('score');
this.onChangeFn = (scene, value) => { console.log('Score updated to', value);};this.data.events.on('changedata-score', this.onChangeFn); // if you pass an anonymous function, you cannot unsubscribe
// when move to another scene, you must unsubscribe. Boring and easy to forgetthis.data.events.off('changedata-score', this.onChangeFn);With phaser-hooks, you get a simple, React-like API:
const volume = withGlobalState(this, 'volume', 0.5);volume.get(); // Returns: 0.5volume.set(0.8); // updates the value
this.unsubscribe = volume.on('change', () => { console.log('Volume changed →', volume.get())}); // Returns the easy unsubscribe function
// when changing scenesthis.unsubscribe();React-like API
Familiar hooks pattern inspired by React’s useState, useEffect, and more
Multiple Hook Types
Local, global, persistent, computed, undoable, and debounced state management
TypeScript First
Full TypeScript support with complete type inference and safety
Automatic Cleanup
Built-in scene cleanup to prevent memory leaks
Validation
Built-in validators for common patterns plus custom validation support
Debug Mode
Comprehensive logging for development and troubleshooting
import { withLocalState } from 'phaser-hooks';// or const { withLocalState } from 'phaser-hooks';
const withPlayer = (scene: Phaser.Scene) => { const player = withLocalState(scene, 'player', { hp: 100, maxHp: 100, level: 1, });
return player;};
// hooks/withSettings.tsimport { withGlobalState } from 'phaser-hooks';
const withSettings = (scene: Phaser.Scene) => { const settings = withGlobalState(scene, 'settings', { volume: 0.8, difficulty: 'normal' }); return settings;};
// scenes/gameScene.tsimport { withLocalState, withGlobalState } from 'phaser-hooks';
class GameScene extends Phaser.Scene { private unsubscribe?: () => void;
create() { // 1. Local state (scene-specific, auto-cleanup) const player = withPlayer(this); // clean and reusable within the same scene
// 2. Global state (persists across scenes) const settings = withSettings(this); // the same instance in all scenes
// 3. Update state player.patch({ hp: 90 }); // Partial update settings.set({ volume: 0.5, difficulty: 'hard' }); // Full update
// 4. Read state console.log(player.get().hp); // 90 console.log(settings.get().volume); // 0.5
// 5. Listen to changes this.unsubscribe = player.on('change', (newPlayer, oldPlayer) => { if (newPlayer.hp < 20) { console.warn(`Low health! Your old HP was ${oldPlayer.hp}`); } }); }
shutdown() { // 6. Clean up (local state auto-cleans, but it’s good practice) this.unsubscribe?.(); }}withLocalState - Scene-specific state with automatic cleanupwithGlobalState - Application-wide state across all sceneswithPersistentState - State with localStorage/sessionStorage persistencewithComputedState - Derived state that auto-updates from source statewithUndoableState - State with built-in undo/redo functionalitywithDebouncedState - State with debounced updatesWhile React hooks use the “use” prefix, this library uses “with” to avoid linting issues. ESLint’s hooks rules expect “use” functions only in React components and .jsx/.tsx files. Since Phaser games typically use plain TypeScript/JavaScript, the “with” prefix prevents false positives while maintaining clear, consistent naming.