Vous vous demandez peut-être pourquoi tout le monde parle d’ECS en développement de jeux. C’est simple : l’Entity Component System, c’est une façon de construire un jeu qui colle super bien à la réalité d’une simulation moderne (beaucoup d’objets, beaucoup de calculs, et pas envie que tout s’écroule dès qu’on ajoute une nouvelle mécanique). Accrochez-vous, on va voir ça ensemble, tranquillement, mais en profondeur.
L’idée de base, c’est que l’ECS n’est pas “juste une autre architecture”. C’est data-oriented : on organise le code et surtout les données pour que le CPU soit content (cache, parallélisme, itérations en batch), et que le jeu reste flexible quand votre design évolue (et il évolue, tout le temps).
L’ECS, c’est quoi exactement ? (et pourquoi c’est différent)
L’ECS repose sur un trio très simple. Et justement, c’est cette simplicité qui fait sa force (même si, oui, au début ça peut sembler “bizarre” si vous venez de l’orienté objet).
Entity : c’est une identité, pas un objet
Une Entity, c’est généralement un ID. Point.
Ce n’est pas une classe “Joueur” avec des méthodes, ce n’est pas un “EnemyController”, ce n’est pas un objet qui “fait des trucs”.
Une entité, c’est un ticket qui dit : “hé, ces composants-là vont ensemble”. Et c’est tout.
Pourquoi c’est top ? Parce que ça évite d’avoir des objets lourds, avec des hiérarchies et des comportements partout. L’entité est légère, et donc vous pouvez en avoir très gros volumes (des dizaines ou centaines de milliers) sans que la structure elle-même vous ralentisse.
Component : c’est de la donnée pure (et ça change tout)
Un Component, c’est un petit bloc de données. Pas de logique, pas de méthode “Update()”, pas de “faire ceci si…”. Juste des champs.
Exemples classiques :
Position (x, y, z)Velocity (vx, vy, vz)Health (current, max)DamageTeamId
Un composant, c’est donc un état. Et vous assemblez une entité par composition : une entité “Joueur”, ce n’est pas une classe, c’est Entity + Position + Velocity + Health + Input + ….
C’est flexible, mais surtout, c’est stable : au lieu de modifier une hiérarchie d’héritage (souvent fragile), vous ajoutez/retirez un composant. Et voilà.
System : c’est le comportement, mais en “batch”
Un System, c’est la logique. C’est lui qui dit :
“Je prends toutes les entités qui ont Position et Velocity, et je les fais avancer.”
Et là, c’est le point clé : un système ne pense pas “objet par objet”, il pense groupe. Il itère sur un ensemble d’entités compatibles, souvent avec une organisation mémoire optimisée, et potentiellement en parallèle.
C’est efficace, mais attendez une minute… ce n’est pas juste “plus rapide”. C’est aussi plus prévisible, plus contrôlable, et souvent plus déterministe (un vrai sujet en multi et simulation).
ECS vs Orienté Objet : c’est la composition contre l’héritage
Vous avez peut-être déjà vécu ça : en OO, vous avez une hiérarchie du style Character -> Player -> Mage -> FireMage, et puis un jour vous devez faire un “Mage volant”, et là… c’est pas si sûr que la hiérarchie survive.
En ECS, vous faites :
- Entity +
MageComponent - +
FlightComponent - +
ManaComponent - +
SpellbookComponent
Et c’est réglé. C’est modulaire, c’est clair, et ça évite les arbres de classes qui deviennent une aventure (dans le mauvais sens).
En pratique :
| Approche orientée objet | ECS |
|---|---|
| Héritage souvent rigide | Composition flexible |
| Logique dispersée dans des objets | Logique regroupée dans des systèmes |
update() sur chaque objet |
Traitement batch (souvent parallélisable) |
| Difficile de scaler à très grande quantité | Pensé pour des volumes massifs |
Pourquoi l’ECS peut être méga performant (et pas juste “un peu”)
Quand on dit “ECS = performance”, ce n’est pas magique, c’est mécanique.
1) Localité mémoire et cache CPU
Si vos composants sont stockés de manière contiguë (par exemple des tableaux de Position), le CPU lit les données avec beaucoup moins de “sauts” mémoire. Résultat : meilleures perfs, surtout quand vous itérez sur des milliers d’éléments.
C’est un détail bas niveau, mais en jeu vidéo, c’est souvent le détail qui fait la différence.
2) Traitement en batch et parallélisme
Un système peut traiter :
- 10 000 entités d’un coup,
- en multi-thread,
- avec un scheduling propre (jobs).
Au lieu d’avoir 10 000 objets qui appellent chacun leur Update(), avec des appels virtuels, du polymorphisme, des références croisées… bref, tout ce qui finit par coûter cher.
3) Déterminisme (souvent) plus facile à viser
Ce n’est pas automatique, mais c’est plus accessible : quand les systèmes sont bien organisés, et que les données sont explicites, vous avez un meilleur contrôle sur l’ordre des opérations et sur l’état global.
Et pour du multijoueur (prédiction/rollback), c’est juste précieux.
Mettre en place l’ECS sur Unity (DOTS / Entities) : comment ça se passe
Unity propose un écosystème orienté ECS via DOTS (Data-Oriented Technology Stack). Ce n’est pas obligatoire, ce n’est pas “pour tous les jeux”, mais pour les projets ambitieux (beaucoup d’IA, simulation, monde dense, multi), c’est une vraie option.
Installation : les briques de base
Via le Package Manager, on installe généralement :
EntitiesBurstCollectionsMathematicsJobs- (optionnel) une solution physique avancée (selon le besoin)
C’est simple, mais… attendez une minute : le vrai “coût”, ce n’est pas l’installation, c’est le changement de mentalité. On passe de “mes objets vivent dans la scène” à “mes données vivent dans le monde ECS”.
Créer une entité : exemple minimal
En DOTS, on manipule des entités via l’EntityManager.
using Unity.Entities;
using Unity.Mathematics;
EntityManager em = World.DefaultGameObjectInjectionWorld.EntityManager;
Entity e = em.CreateEntity();
em.AddComponentData(e, new Position { Value = new float3(0, 0, 0) });
em.AddComponentData(e, new Velocity { Value = new float3(1, 0, 0) });
Ce qui compte ici, c’est le pattern : entité = ID, et la “forme” de l’entité vient des composants attachés.
Créer un système : là où la logique vit
Version moderne via ISystem et des queries :
using Unity.Entities;
public partial struct MovementSystem : ISystem
{
public void OnUpdate(ref SystemState state)
{
foreach (var (transform, velocity) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<Velocity>>())
{
transform.ValueRW.Position += velocity.ValueRO.Value * SystemAPI.Time.DeltaTime;
}
}
}
C’est lisible, c’est direct : “je prends tous ceux qui ont Transform + Velocity, je les avance”.
Et c’est exactement le cœur ECS : décrire un traitement sur un set de données.
Burst + Jobs : l’accélérateur (quand c’est bien utilisé)
- Burst, c’est un compilateur qui transforme du C# en code natif très optimisé.
- Le Job System, c’est l’outillage pour paralléliser proprement.
C’est puissant, mais… oui, debugging + performance tuning, c’est un autre sport. Ce n’est pas “je clique et c’est bon”. Il faut structurer ses données, éviter les accès aléatoires, et accepter que certaines habitudes OO ne passent plus.
ECS et multijoueur : Netcode et simulation
Unity propose aussi des outils orientés ECS pour le multi (prédiction, rollback, synchronisation). Là encore, ce n’est pas magique, mais l’approche “données + déterminisme” est très alignée avec les besoins réseau.
Et Godot dans tout ça ? (pas d’ECS natif, mais une philosophie proche)
Godot n’intègre pas un ECS “officiel” comme Unity DOTS. Mais sa structure nodale pousse déjà à la composition : un personnage, c’est un arbre de nœuds, et vous ajoutez des morceaux (collider, sprite, script, etc.).
Donc, dans l’esprit, c’est proche : composition plutôt qu’héritage massif.
Option 1 : rester idiomatique Godot (souvent le meilleur choix)
Pour beaucoup de jeux, le modèle Node/Scene est déjà très efficace. Vous prototypez vite, vous itérez vite, et ça compte.
Option 2 : ECS “pur” via addon ou implémentation maison
Si vous visez du scaling (beaucoup d’entités), vous pouvez :
- utiliser un addon ECS communautaire,
- ou coder un mini-ECS : dictionnaires/arrays pour les composants, et des boucles système.
Exemple simple (illustratif) :
# Composant Position
class_name Position
var x: float = 0.0
var y: float = 0.0
# Système Mouvement (pseudo-approche)
func movement_system(entities: Array, delta: float):
for entity in entities:
if entity.has_component("Position") and entity.has_component("Velocity"):
entity.position.x += entity.velocity.dx * delta
Ce n’est pas “la version la plus optimisée”, mais ça montre l’intention : les systèmes parcourent des ensembles d’entités compatibles.
Implémenter un ECS soi-même : tentant, mais à cadrer
Faire son ECS from scratch, c’est formateur (et parfois nécessaire si vous avez un moteur custom). Mais c’est aussi un terrain glissant, parce que les “détails” deviennent vite très gros :
- stockage (SoA vs AoS),
- queries efficaces,
- archétypes,
- gestion mémoire,
- ajout/suppression de composants,
- sérialisation data-driven…
Une version ultra simple peut ressembler à ça (juste pour l’idée) :
class ECS {
entities = new Map();
systems = [];
createEntity() { /* génère un ID unique */ }
addComponent(entityId, component) { /* stocke par type */ }
update() { this.systems.forEach(s => s.process(this)); }
}
C’est bien pour comprendre, mais si vous visez la performance, vous allez rapidement devoir aller vers des structures plus spécialisées (archétypes, chunks, tables contiguës, etc.). Et là, c’est une autre aventure.
Bevy (Rust) : l’ECS “au centre”, sans compromis
Bevy est un cas intéressant parce que l’ECS n’est pas “un module”. C’est la base de l’architecture. Vous pensez ECS dès le départ, et ça rend beaucoup de choses naturelles dans ce moteur.
Exemple minimal :
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Update, movement_system)
.run();
}
fn movement_system(mut query: Query<(&mut Transform, &Velocity)>) {
for (mut transform, velocity) in query.iter_mut() {
transform.translation += velocity.0;
}
}
Ce que Bevy apporte bien, c’est :
- une approche “ECS-first” propre,
- les avantages Rust (sécurité mémoire, performance),
- une structure data-driven très cohérente.
C’est top, mais… ça demande d’être à l’aise avec Rust, et ça change la manière de travailler si vous venez d’un pipeline Unity classique.
Bonnes pratiques ECS (celles qui font vraiment la différence)
Vous pouvez “faire de l’ECS” et quand même perdre les avantages si vous vous trompez de réflexes. Voilà les points qui comptent le plus.
Faire des queries précises (sinon vous itérez pour rien)
Un système doit viser exactement les entités dont il a besoin. Plus la query est propre, plus le traitement est direct.
Garder des composants “plats”
Des struct simples, sans références complexes, c’est souvent ce qui marche le mieux. Moins de pointeurs, moins de surprises, plus de cache-friendly.
Éviter de remettre de l’OO cachée dans les composants
Le piège classique : mettre des objets, des callbacks, des références vers des “managers” dans les composants. Ça “marche”, mais ça casse l’approche data-oriented (et souvent les perfs).
Accepter une courbe d’apprentissage (et prototyper avant)
L’ECS, c’est puissant, mais ce n’est pas toujours rentable pour un petit jeu. Debugger des systèmes, suivre des données, comprendre l’ordre d’exécution… ce n’est pas la même gymnastique.
Donc oui : faites un prototype. Mesurez. Ressentez le workflow. Et ensuite seulement, engagez-vous.
Les limites de l’ECS (parce que ce n’est pas une baguette magique)
- Pour des jeux simples, ça peut être “trop” (trop d’infra, pas assez de bénéfice).
- Le debugging peut être moins direct qu’un GameObject avec des scripts.
- La structure “tout est data + systems” demande une discipline : sans ça, on peut vite se perdre.
Mais l’inverse est vrai aussi : sur des projets avec beaucoup d’entités, l’approche classique peut devenir un mur, alors que l’ECS reste respirable.
Conclusion : quand choisir quoi (et comment démarrer sans se perdre)
Si vous voulez apprendre avec beaucoup de ressources et un écosystème énorme, Unity + Entities/DOTS, c’est un chemin très pratique (même si ça demande de changer ses habitudes).
Si vous voulez une approche “pure ECS” dès le départ, et que Rust vous attire, Bevy, c’est une option vraiment solide.
Et si vous êtes sur Godot, vous pouvez déjà faire énormément avec sa composition nodale, puis envisager une couche ECS si votre simulation devient massive.
L’ECS, c’est une manière de penser : les entités ne “font” rien, les composants décrivent, les systèmes agissent. Et quand ça clique, c’est assez satisfaisant (genre “ah oui, en fait c’est juste ça”).