(Last updated on )

ecs?

Why I Stopped Thinking in Objects and Started Thinking in Data


If you’ve spent any time building games with an object-oriented mindset, you’ve probably hit the wall. Not immediately — OOP feels great at first. You have a `Player` class, an `Enemy` class, maybe a nice `Character` base class they both extend. Tidy. Logical. Until the moment you need a `FriendlyEnemyWhoIsAlsoAMerchant`, and suddenly your inheritance tree looks like a family reunion nobody wanted.

Entity Component System — ECS — is the architecture pattern that solves this, and once it clicks, it’s hard to go back.


The OOP Problem, Concretely

In object-oriented game design, a game object *is* a thing. A `Guard` has methods. It *knows* how to patrol. It *knows* how to detect intruders. Its behavior and its data are bundled together, which feels intuitive because that’s how we think about real objects.

But games are full of edge cases. What happens when:

  • A guard gets drunk and starts wandering randomly?

  • A merchant needs to react to threats like a guard would?

  • An NPC falls asleep and temporarily stops being *anything* useful?

You end up with either deep, fragile inheritance chains or a bloated base class that has a little bit of everything — the infamous “god object.” Neither scales well. Refactoring either one mid-project is painful.


The ECS Mental Shift

ECS flips the model:

  • **Entities** are just IDs. Empty vessels. A number.

  • **Components** are plain data attached to those IDs. No logic. Just state.

  • **Systems** are functions that operate on entities that have a specific set of components.

A guard isn’t a `Guard` object. It’s an entity with a `Position` component, a `PatrolRoute` component, a `Faction` component, and maybe a `Schedule` component. If you want that guard to also be a merchant during off-hours? Add a `MerchantInventory` component. No new class. No refactor. Just data.

The behavior emerges from which components are present, and systems only run on entities that qualify.


Why This Matters More Than It Sounds

The power isn’t just organizational tidiness — it’s *composability at runtime*.

In a recent project I’m building (a simulation-heavy RPG engine), NPCs needed to:

  • Follow patrol routes

  • Stick to daily schedules

  • React to things they witnessed — a theft, a fight, someone walking around smelling like mushrooms

  • Have their behavior temporarily overridden by status effects

In an OOP model, each of those features risks touching a central `NPC` class. In ECS, each is its own component. A status effect that makes an NPC avoid you? That’s just attaching a `StatusEffect` component with the right data. The avoidance system picks it up automatically. Remove the component when the effect wears off. Done.

What surprised me most was how *emergent* this gets. I didn’t script NPCs confronting the player about witnessed behavior — I just built an observation pipeline that writes to a knowledge component, and a reaction system that reads from it. The confrontations fall out naturally. The status effect humor? Completely unscripted. It’s a side effect of composable, data-driven logic.


The Practical Tradeoffs

ECS isn’t without friction.

**Debugging is different.** When behavior is spread across systems rather than encapsulated in objects, tracking down why an NPC is doing something weird means checking which components it has and which systems are touching it — rather than just setting a breakpoint on a method. Good tooling helps enormously here.

**The mental model takes time.** If you’ve spent years thinking in classes, ECS feels backwards at first. You keep wanting to put logic *on* the entity. Resist that impulse.

**Queries are your friend.** The real productivity unlock is fluent querying — asking the world “give me all entities with *these* components” and getting them back efficiently. This is where purpose-built ECS libraries pay for themselves over rolling your own.


Why Miniplex Specifically

For JavaScript/TypeScript game projects, Miniplex hits a sweet spot. It’s lightweight, has an elegant TypeScript-first API, and its query system feels natural once you’ve internalized the ECS mental model. Defining a query is as simple as describing what components you care about — and it handles the entity bookkeeping so you don’t have to.

It also plays well with Phaser, which has its own scene/game object model. The trick is treating Phaser’s rendering objects as *one component* among many, rather than the center of gravity. Your simulation logic lives in ECS; Phaser just draws what the simulation says.


When to Reach for It

ECS shines when:

  • You have many entities with overlapping but non-identical behavior

  • You need to add or remove capabilities from entities at runtime

  • Emergent behavior from system interactions is a feature, not a bug

  • You want your simulation logic decoupled from your rendering

It’s probably overkill for a simple platformer with five enemy types. But if your game world has NPCs with schedules, knowledge, relationships, and dynamic status — ECS stops being a nice-to-have and starts feeling like the only sane choice.


The shift from “objects that do things” to “data that systems act on” is subtle on paper and profound in practice. Once your guard becomes an entity with components instead of a `Guard` class, a whole class of design problems quietly disappears — and a whole class of emergent surprises quietly appears.

That trade feels worth it.