Skip to content

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.

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
hooks/withSettings.ts
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 });
},
};
};
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();
// ...
}
}
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();
}
}

Create reusable global hooks for consistent access across your game:

hooks/withSettings.ts
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 });
},
};
}

Use your custom hook across multiple scenes:

SettingsScene.ts
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?.();
}
}

FeatureDescription
Cross-sceneState persists across all scenes in your game
Global accessAny scene can read/write the same state
Singleton patternSame key returns the same state instance
Manual cleanupYou must call unsubscribe() or clearListeners()
Type-safeFull TypeScript support with type inference

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

AspectwithLocalStatewithGlobalState
ScopeSingle sceneEntire application
LifetimeScene active → destroyedGame start → game end
CleanupAutomaticManual (required!)
Use casePlayer HP, enemiesSettings, profile
Reset on scene changeYesNo

⚠️ Critical: Always clean up global state listeners to prevent memory leaks!

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!
}
}
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!
}
}
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>
  • 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

A HookState<T> object with methods: get(), set(), patch(), on(), once(), off(), clearListeners().