🌾 FarmHeart

← Back to Blog

State Management Patterns for Browser Games

ArchitectureStateGame Dev · 10 min read

Every browser game, from a simple clicker to a 3D farming simulation, boils down to state: where the player is, what they own, what time it is in-game, and which quests are active. As your game grows beyond a few hundred lines of JavaScript, how you organize that state determines whether adding features feels effortless or terrifying.

The Problem with Global State

Most browser games start with a single object:

const game = { player: { x: 0, y: 0, hp: 100 }, inventory: [], day: 1 };

This works perfectly for prototypes. But once you add weather, NPC schedules, crop growth timers, market prices, and save/load support, that flat object becomes a minefield. Change one property and three unrelated systems break because they were all reading from the same place with no coordination.

Pattern 1: Event-Driven State

The simplest improvement over a global object is an event bus. Systems don't read state directly — they listen for events and react.

When the player plants a crop, the farming system emits CROP_PLANTED. The UI listens and updates the HUD. The tutorial system listens and marks a quest step complete. The sound system listens and plays a planting sound effect. Nobody reaches into anyone else's state.

When to use it: Small to medium games with fewer than ~20 interacting systems. The event bus is easy to debug and doesn't require any dependencies.

Pattern 2: Finite State Machines

FSMs are ideal for anything with clear, named states and well-defined transitions: NPC behavior (idle → walking → talking → sleeping), menu screens (title → playing → paused → game over), and animation controllers.

Each state is an object with enter(), update(dt), and exit() methods. Transitions happen through a single setState() call that runs the old state's exit and the new state's enter. This eliminates the "am I still in the attack animation?" bugs that plague games with boolean flag spaghetti.

For complex NPCs, hierarchical state machines (HFSM) let you nest states — a "farming" super-state contains "plowing," "seeding," and "watering" sub-states, each with their own transitions.

Pattern 3: Entity Component System (ECS)

ECS separates what an entity is (an ID) from what it has (components: Position, Sprite, Health, Inventory) and what happens to it (systems: MovementSystem, RenderSystem, CombatSystem).

This pattern shines in games with many entities that share some behaviors but not others. A chicken has Position + Sprite + AnimalBehavior. A crop has Position + Sprite + GrowthTimer. A tool has no Position but has DurabilityComponent. Systems iterate over entities that match their component signature, ignoring everything else.

PatternBest ForComplexity
Global ObjectPrototypes, game jamsTrivial
Event BusSmall-medium gamesLow
FSM / HFSMNPC behavior, menus, animationsMedium
ECSSimulation-heavy games, many entitiesHigh
Redux-like StoreComplex UI + game state, time travelMedium-High

Pattern 4: Redux-Like Immutable Store

Borrowed from web app development, this pattern treats game state as a single immutable tree. All changes go through dispatched actions and pure reducer functions. The store holds the entire game world, and every frame renders from this snapshot.

The killer feature is time travel debugging: record every action, and you can replay the entire game frame by frame, rewind to any point, and inspect exactly what changed. For a farming game with hundreds of interconnected systems — crop prices affecting NPC mood affecting quest availability — this is invaluable.

The downside is performance. Creating a new state tree every frame generates garbage for the GC to collect. Solutions include structural sharing (only cloning the branch that changed) and using typed arrays for hot-path data like entity positions.

Pattern 5: Hybrid Approach

Most real games mix patterns. FarmHeart uses:

The key is picking the right pattern for each layer, not forcing one pattern to do everything.

Serialization and Save/Load

Whatever pattern you choose, it needs to serialize to JSON for localStorage or cloud saves. This means:

Pro tip: Build a "state diff" tool early. Log the entire state before and after each frame, diff them, and display what changed. It will save you hours of debugging when something mysteriously updates that shouldn't.

Performance Considerations

Browser games run in a garbage-collected environment, so state management patterns that create lots of temporary objects (like pure immutable stores) need extra care. Pool your event objects, reuse component instances, and avoid Object.assign() in hot loops. Profile with Chrome DevTools' Allocation Timeline to find hidden GC pressure.

See State Management in Action

FarmHeart manages 800+ features across crops, animals, weather, and economy — all in your browser.

Play FarmHeart Now