With Global State
The withGlobalState hook provides application-wide state management that persists across all scenes. Perfect for settings, user profiles, game progress, or any data that should survive scene transitions.
How It Works
Section titled “How It Works”Global Singleton Pattern
Section titled “Global Singleton Pattern”Unlike withLocalState (which is scene-specific), withGlobalState creates state that is shared across your entire game:
- ✅ Persists across scenes - Data survives when you switch scenes, etc.
- ✅ Accessible everywhere - Any scene can read/write the same state
- ✅ Same key = same instance - Just like React’s context/global state
import { type HookState, withGlobalState } from 'phaser-hooks';
type Settings = {  volume: number;};
type SettingsState = HookState<Settings> & {  setVolume: (volume: number) => void;};
export const withSettings = (scene: Phaser.Scene): SettingsState => {  const state = withGlobalState<Settings>(scene, 'settings', { volume: 0.8 });
  return {    ...state,    setVolume: (volume: number) => {      state.set({ volume });    },  };};const { withGlobalState } = require('phaser-hooks');
module.exports = {    withSettings: (scene) => withGlobalState(scene, 'settings', { volume: 0.8 }),};Usage Example
Section titled “Usage Example”class GameScene extends Phaser.Scene {  create(): void {    const settings = withSettings(this);    settings.set({ volume: 0.5 });    // Or using patch    // settings.patch({ volume: 0.5 });
    this.song = this.sound.add('theme', { volume: settings.get().volume });    this.song.play();    // ...  }}Manual Cleanup Required
Section titled “Manual Cleanup Required”class GameScene extends Phaser.Scene {  private unsubscribe?: () => void;
  create() {    const settings = withGlobalState(this, 'settings', { volume: 0.8 });
    this.unsubscribe = settings.on('change', (newSettings) => {      console.log('Settings changed:', newSettings);    });  }
  shutdown() {    // CRITICAL: Clean up listeners!    this.unsubscribe?.();
    // Or remove all listeners at once    // const settings = withGlobalState(this, 'settings', { volume: 0.8 });    // settings.clearListeners();  }}Creating Custom Hooks (Recommended)
Section titled “Creating Custom Hooks (Recommended)”Create reusable global hooks for consistent access across your game:
import { type HookState, withGlobalState } from 'phaser-hooks';
type Settings = {  volume: number;  musicEnabled: boolean;  difficulty: 'easy' | 'normal' | 'hard';  language: string;};
type SettingsState = HookState<Settings> & {  toggleMusic: () => void;  setDifficulty: (difficulty: Settings['difficulty']) => void;  adjustVolume: (delta: number) => void;};
export function withSettings(scene: Phaser.Scene): SettingsState {  const state = withGlobalState<Settings>(scene, 'settings', {    volume: 0.8,    musicEnabled: true,    difficulty: 'normal',    language: 'en',  });
  return {    ...state,
    toggleMusic: () => {      const current = state.get();      state.patch({ musicEnabled: !current.musicEnabled });    },
    setDifficulty: (difficulty: Settings['difficulty']) => {      state.patch({ difficulty });    },
    adjustVolume: (delta: number) => {      const current = state.get();      const newVolume = Math.max(0, Math.min(1, current.volume + delta));      state.patch({ volume: newVolume });    },  };}const { withGlobalState } = require('phaser-hooks');
function withSettings(scene) {  const state = withGlobalState(scene, 'settings', {    volume: 0.8,    musicEnabled: true,    difficulty: 'normal',    language: 'en',  });
  return {    ...state,
    toggleMusic: () => {      const current = state.get();      state.patch({ musicEnabled: !current.musicEnabled });    },
    setDifficulty: (difficulty) => {      state.patch({ difficulty });    },
    adjustVolume: (delta) => {      const current = state.get();      const newVolume = Math.max(0, Math.min(1, current.volume + delta));      state.patch({ volume: newVolume });    },  };}
module.exports = { withSettings };Usage Example
Section titled “Usage Example”Use your custom hook across multiple scenes:
import { withSettings } from './hooks/withSettings';
class SettingsScene extends Phaser.Scene {  private unsubscribe?: () => void;
  create() {    const settings = withSettings(this);
    // Create UI controls    const volumeSlider = this.createVolumeSlider();    const musicToggle = this.createMusicToggle();    const difficultyButtons = this.createDifficultyButtons();
    // Update settings when UI changes    volumeSlider.on('change', (value: number) => {      settings.patch({ volume: value });    });
    musicToggle.on('click', () => {      settings.toggleMusic();    });
    difficultyButtons.on('select', (difficulty: string) => {      settings.setDifficulty(difficulty);    });
    // Update UI when settings change    this.unsubscribe = settings.on('change', (newSettings) => {      volumeSlider.setValue(newSettings.volume);      musicToggle.setChecked(newSettings.musicEnabled);      difficultyButtons.setSelected(newSettings.difficulty);    });  }
  shutdown() {    this.unsubscribe?.();  }}
// GameScene.ts - Access the SAME settings!class GameScene extends Phaser.Scene {  private unsubscribe?: () => void;
  create() {    const settings = withSettings(this);
    // Apply settings to game    this.sound.volume = settings.get().volume;
    if (!settings.get().musicEnabled) {      this.sound.stopAll();    }
    // React to settings changes in real-time    this.unsubscribe = settings.on('change', (newSettings) => {      this.sound.volume = newSettings.volume;
      if (newSettings.musicEnabled) {        this.sound.play('background-music', { loop: true });      } else {        this.sound.stopAll();      }    });  }
  shutdown() {    this.unsubscribe?.();  }}const { withSettings } = require('./hooks/withSettings');
class SettingsScene extends Phaser.Scene {  create() {    const settings = withSettings(this);
    const volumeSlider = this.createVolumeSlider();    const musicToggle = this.createMusicToggle();
    volumeSlider.on('change', (value) => {      settings.patch({ volume: value });    });
    musicToggle.on('click', () => {      settings.toggleMusic();    });
    this.unsubscribe = settings.on('change', (newSettings) => {      volumeSlider.setValue(newSettings.volume);      musicToggle.setChecked(newSettings.musicEnabled);    });  }
  shutdown() {    this.unsubscribe?.();  }}
// GameScene.jsclass GameScene extends Phaser.Scene {  create() {    const settings = withSettings(this);
    this.sound.volume = settings.get().volume;
    this.unsubscribe = settings.on('change', (newSettings) => {      this.sound.volume = newSettings.volume;
      if (newSettings.musicEnabled) {        this.sound.play('background-music', { loop: true });      } else {        this.sound.stopAll();      }    });  }
  shutdown() {    this.unsubscribe?.();  }}Key Features
Section titled “Key Features”| Feature | Description | 
|---|---|
| Cross-scene | State persists across all scenes in your game | 
| Global access | Any scene can read/write the same state | 
| Singleton pattern | Same key returns the same state instance | 
| Manual cleanup | You must call unsubscribe()orclearListeners() | 
| Type-safe | Full TypeScript support with type inference | 
When to Use
Section titled “When to Use”✅ Use withGlobalState for:
- Game settings (volume, difficulty, language)
- User profile (name, avatar, preferences)
- Persistent game progress (unlocked levels, achievements)
- Authentication state (logged in, user ID)
- App-wide UI state (notifications, modals)
❌ Don’t use withGlobalState for:
- Scene-specific data (use withLocalState)
- Data that should persist after closing the game (use withPersistentState)
- Temporary UI state within a scene
withLocalState vs withGlobalState
Section titled “withLocalState vs withGlobalState”| Aspect | withLocalState | withGlobalState | 
|---|---|---|
| Scope | Single scene | Entire application | 
| Lifetime | Scene active → destroyed | Game start → game end | 
| Cleanup | Automatic | Manual (required!) | 
| Use case | Player HP, enemies | Settings, profile | 
| Reset on scene change | Yes | No | 
Memory Management
Section titled “Memory Management”⚠️ Critical: Always clean up global state listeners to prevent memory leaks!
Method 1: Unsubscribe Function
Section titled “Method 1: Unsubscribe Function”class GameScene extends Phaser.Scene {  private unsubscribe?: () => void;
  create() {    const settings = withGlobalState(this, 'settings', { volume: 0.8 });    this.unsubscribe = settings.on('change', (newSettings) => {      // Handle change    });  }
  shutdown() {    this.unsubscribe?.(); // Clean up!  }}Method 2: clearListeners()
Section titled “Method 2: clearListeners()”class GameScene extends Phaser.Scene {  private settings?: ReturnType<typeof withSettings>;
  create() {    this.settings = withSettings(this);
    this.settings.on('change', (newSettings) => {      // Handle change    });  }
  shutdown() {    this.settings?.clearListeners(); // Remove all listeners!  }}Method 3: Phaser’s shutdown event
Section titled “Method 3: Phaser’s shutdown event”class GameScene extends Phaser.Scene {  create() {    const settings = withSettings(this);
    const unsubscribe = settings.on('change', (newSettings) => {      // Handle change    });
    // Auto-cleanup when scene shuts down    this.events.once('shutdown', unsubscribe);  }}function withGlobalState<T>(  scene: Phaser.Scene,  key: string,  initialValue: T,  options?: {    debug?: boolean;    validator?: (value: T) => true | string;  }): HookState<T>Parameters
Section titled “Parameters”- scene- The Phaser scene instance (needed for registry access)
- key- Unique identifier for this state (global singleton reference)
- initialValue- Initial state value (used only on first call across entire game)
- options(optional)- debug- Enable console logging for debugging
- validator- Validation function for state updates
 
Returns
Section titled “Returns”A HookState<T> object with methods: get(), set(), patch(), on(), once(), off(), clearListeners().
See Also
Section titled “See Also”- withLocalState - For scene-specific state
- withPersistentState - For localStorage persistence
- Creating Custom Hooks - Deep dive into custom hooks
- Cleanup & Memory Management - Best practices
Next Steps
Section titled “Next Steps”- Learn about state persistence with localStorage
- Explore creating custom hooks
- See real-world examples of global state in action