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.
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.
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.
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.
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.
| Pattern | Best For | Complexity |
|---|---|---|
| Global Object | Prototypes, game jams | Trivial |
| Event Bus | Small-medium games | Low |
| FSM / HFSM | NPC behavior, menus, animations | Medium |
| ECS | Simulation-heavy games, many entities | High |
| Redux-like Store | Complex UI + game state, time travel | Medium-High |
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.
Most real games mix patterns. FarmHeart uses:
The key is picking the right pattern for each layer, not forcing one pattern to do everything.
Whatever pattern you choose, it needs to serialize to JSON for localStorage or cloud saves. This means:
saveVersion field and write migration functionsBrowser 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.
FarmHeart manages 800+ features across crops, animals, weather, and economy — all in your browser.
Play FarmHeart Now