ECS by Feature
Entity Component System with Jecs or Matter, components shared and systems per side.
The architecture most specific to games. Data and behaviour are kept apart, and each feature folder owns the components and systems for one part of the game.
The Rule
An Entity Component System has three parts. In the words of Sander Mertens, the author of flecs (ECS FAQ): "entities, which are unique identifiers", "components, which are plain datatypes without behavior", and "systems, which are functions matched with entities that have a certain set of components".
Organised by feature:
- Components go in
Shared/. They're data, and both sides need to know their shape. - Systems go where they run. A system that decides (applying damage, validating speed) goes in
Server/. A system that presents (a hit flash) goes inClient/. A system both sides run, such as movement that the client predicts, goes inShared/. - Systems don't require each other. They communicate through the components they read and write.
The world and the scheduler are shared by every feature, so they sit at the top.
Where It Comes From
- flecs. Designing with Flecs says to "make sure to define your modules around features", such as rendering, collision detection or input management.
- Bevy. "All Bevy engine features are implemented as plugins" (Bevy). A plugin is a feature folder.
- Unity. In Netcode for Entities, each system runs in the client world, the server world, or both, which is the default. The side is a property of the system, not a top-level folder.
- Shipped games. The ECS FAQ lists Overwatch, Diablo II: Resurrected and Minecraft Legends among others. Blizzard presented Overwatch's ECS architecture and its netcode at GDC 2017.
- Roblox. Jecs ("A fast, portable Entity Component System for Luau") and Matter bring ECS to Luau. Matter's own example game is split by side at the root (
server/systems,client/systems,shared/components). This page shows the same kind of game, split by feature.
On Disk
Combathas its components inShared/(Health,Damage), systems that decide on the server and a system that presents on the client.Movement/Shared/Systems/Integrateruns on both sides: the server to simulate, the client to predict.Replicationcopies component changes from the server's world to the clients'.
The Config
The routes rogen init writes are enough:
{
"$schema": "https://ldgerrits.github.io/rogen/schema/2/rogen.json",
"rootDirs": ["src"],
"routes": {
"Server": "ServerScriptService",
"Client": "StarterPlayer/StarterPlayerScripts",
"Shared": "ReplicatedStorage/Shared",
"*": "ReplicatedStorage/Shared"
}
}(Ecs) is an invisible folder with no route, so the * route places World and Scheduler directly in ReplicatedStorage/Shared.
In Studio
Server systems stay in ServerScriptService, where clients can't see them. Components and shared systems sit in ReplicatedStorage, so both sides require the same modules.
Why It Fits a Game
- Many entities, shared behaviour. Mobs, projectiles and vehicles are combinations of the same components. A system written once applies to every entity that has them.
- The side is per system. Like Unity's worlds, a system runs on the server, the client or both, and the folder it's in says which. A feature with authority on the server and effects on the client stays in one place.
- Components are the contract. The server and client agree on data, not on each other's code.
Shared/Components.luauis everything the two sides have in common. - Performance. Jecs advertises cache-friendly archetype storage, and ECS keeps hot loops over plain data.
Trade-offs
- A different model. ECS asks you to think in data and queries instead of objects. UI and one-off logic are often simpler as plain modules beside it, which a feature folder allows.
- Systems are spread across folders. Matter's getting-started loads every system from one folder. With systems in every feature, collecting them is your entry point's job.
- Ordering is global. Systems in different features still run in one schedule. The order lives in the scheduler, not in the folders.
- Rogen doesn't check the rules. A system that requires another system still works. Keep the rule in review, or lint it: see Enforcing the Rules.