Skip to content

Adding Custom Methods

Extend hooks with domain-specific methods for cleaner APIs.

export function withPlayer(scene: Phaser.Scene) {
const state = withLocalState(scene, 'player', {
hp: 100,
maxHp: 100
});
return {
...state, // Include all HookState methods
// Add custom methods
takeDamage: (amount: number) => {
const current = state.get();
state.patch({ hp: Math.max(0, current.hp - amount) });
},
heal: (amount: number) => {
const current = state.get();
state.patch({ hp: Math.min(current.maxHp, current.hp + amount) });
}
};
}
// Usage - clean and expressive
const player = withPlayer(this);
player.takeDamage(30); // Instead of player.patch({ hp: player.get().hp - 30 })
player.heal(20);
import { type HookState, withLocalState } from 'phaser-hooks';
type Player = {
hp: number;
maxHp: number;
level: number;
};
type PlayerState = HookState<Player> & {
takeDamage: (amount: number) => void;
heal: (amount: number) => void;
levelUp: () => void;
isDead: () => boolean;
};
export function withPlayer(scene: Phaser.Scene): PlayerState {
const state = withLocalState<Player>(scene, 'player', {
hp: 100,
maxHp: 100,
level: 1
});
return {
...state,
takeDamage: (amount: number) => {
const current = state.get();
state.patch({ hp: Math.max(0, current.hp - amount) });
},
heal: (amount: number) => {
const current = state.get();
state.patch({ hp: Math.min(current.maxHp, current.hp + amount) });
},
levelUp: () => {
const current = state.get();
state.patch({
level: current.level + 1,
maxHp: current.maxHp + 10,
hp: current.maxHp + 10
});
},
isDead: () => state.get().hp <= 0
};
}
canAttack: () => {
const p = state.get();
return p.stamina >= 10 && !p.isStunned;
}
healthPercent: () => {
const p = state.get();
return (p.hp / p.maxHp) * 100;
}
gainExp: (amount: number) => {
const current = state.get();
const newExp = current.exp + amount;
if (newExp >= current.expToNextLevel) {
// Level up!
state.set({
...current,
level: current.level + 1,
exp: newExp - current.expToNextLevel,
expToNextLevel: calculateNextLevelExp(current.level + 1),
maxHp: current.maxHp + 10,
hp: current.maxHp + 10
});
} else {
state.patch({ exp: newExp });
}
}
import { events } from '@/events';
takeDamage: (amount: number) => {
const old = state.get();
state.patch({ hp: Math.max(0, old.hp - amount) });
scene.events.emit(events.PLAYER_DAMAGED, {
amount,
remaining: state.get().hp
});
if (state.get().hp === 0) {
scene.events.emit(events.PLAYER_DIED);
}
}

When to Add Methods vs When to Keep Simple

Section titled “When to Add Methods vs When to Keep Simple”

Add methods when:

  • Logic is complex or repeated
  • You want expressive API (takeDamage vs patch)
  • Validation is needed
  • Side effects (events, sounds) are involved

Keep simple when:

  • Just reading/writing properties
  • One-liner operations
  • State is used in many different ways