En este de artículo explicaremos qué es el ECS: Entidades, Componentes y Sistemas en la programación de Videojuegos y por qué existe. Cuando comenzamos a desarrollar videojuegos solemos utilizar Programación Orientada a Objetos (POO). Es una forma natural de organizar nuestro código: cada objeto del juego se representa mediante una clase que contiene tanto sus datos como su comportamiento.
Por ejemplo, podemos crear una clase para el jugador, otra para los enemigos, otra para los proyectiles y así sucesivamente. Durante las primeras fases del desarrollo este enfoque funciona perfectamente. Sin embargo, a medida que el proyecto crece comienzan a aparecer problemas cada vez más difíciles de resolver.
Nuevos tipos de enemigos, armas especiales, estados adicionales, comportamientos únicos, habilidades temporales, efectos visuales y cientos de excepciones terminan generando sistemas complejos, difíciles de mantener y cada vez más acoplados.
Precisamente para intentar solucionar estos problemas surgió ECS (Entity Component System), una arquitectura que propone una forma diferente de organizar los datos y la lógica de un videojuego. Antes de estudiar cómo funciona ECS, primero debemos entender qué problemas intenta resolver.
¿Qué es ECS?
De forma simplificada las siglas ECS corresponden a:
- Entity (Entidad): Las entidades representan los objetos del juego.
- Component (Componente): Los componentes simplemente almacenan datos.
- System (Sistema): Los sistemas contienen la lógica que procesa esos datos.
Esta separación puede parecer extraña al principio, especialmente para quienes vienen de la programación orientada a objetos, donde los datos y el comportamiento suelen encontrarse dentro de la misma clase. Sin embargo, esta diferencia es precisamente la clave de ECS.
Por qué existe ESC ?
ECS (Entity, Component, System) surge a partir de de las «debilidades» de la POO (Programación Orientada a Objetos), especialmente el lo que se refiere al desarrollo de videojuegos. Donde paradógicamente las fortalezas de la POO, como la herencia, se vuelven en su contra. Veámoslo con algunos ejemplos teóricos.
Herencia excesiva
Uno de los mecanismos más utilizados en POO es la herencia, por ejemplo:
Entity
├─ Character
│ ├─ Player
│ └─ Enemy
A primera vista parece una solución elegante. Podemos colocar funcionalidades comunes en las clases superiores y reutilizarlas en las clases derivadas. Un ejemplo simplificado podría ser:
class Entity {
update() {}
render() {}
}
class Character extends Entity {
health = 100;
}
class Player extends Character {
jump() {}
}
class Enemy extends Character {
attack() {}
}
Jerarquías imposibles de mantener
Todo parece perfectamente organizado, sin embargo el problema aparece cuando el videojuego crece. Supongamos que ahora necesitamos distintos tipos de enemigos como por ejemplo:
Entity
└─ Enemy
├─ FlyingEnemy
├─ TankEnemy
└─ BossEnemy
Para ello podríamos crear nuevas clases enemigas mediante herencias como:
class FlyingEnemy extends Enemy {
fly() {}
}
class TankEnemy extends Enemy {
shield() {}
}
class BossEnemy extends Enemy {
specialAttack() {}
}
Hata aquí todavía parece razonable, pero poco después surgen nuevas necesidades. Si ahora queremos:
- Enemigos voladores con escudo.
- Jefes voladores.
- Jefes con escudo.
- Jefes voladores con escudo.
- Enemigos tanque que además disparan misiles.
La jerarquía comienza a multiplicarse y a convertirse en algo difícil de mantener, mezclando nombres para entender las propiedades y métodos que contienen, como:
Enemy
├─ FlyingEnemy
├─ TankEnemy
├─ BossEnemy
├─ FlyingTankEnemy
├─ FlyingBossEnemy
├─ TankBossEnemy
└─ FlyingTankBossEnemy
Clases gigantes
De modo que con la programación POO cada nueva combinación genera nuevas clases y la arquitectura empieza a convertirse en una pesadilla. Además, otro problema habitual es que las clases acumulan responsabilidades y acaban convirtiéndose en clases gigantes. De modo que un enemigo puede terminar teniendo:
class Enemy {
// Movimiento
move() {}
// Combate
attack() {}
// IA
think() {}
// Animación
animate() {}
// Sonido
playSound() {}
// Inventario
dropLoot() {}
// Estados
stun() {}
poison() {}
freeze() {}
}
Lo que empezó siendo una clase sencilla (Enemy) acaba convirtiéndose en un archivo de cientos o miles de líneas donde modificar una parte puede afectar a otras. Encontrar errores se vuelve más difícil y reutilizar funcionalidades resulta cada vez más complicado.
Acoplamiento
Cuando los datos y la lógica están mezclados dentro de las mismas clases, todo acaba dependiendo de todo, por ejemplo:
enemy.attack();
enemy.move();
enemy.animate();
enemy.playSound();
enemy.updateAI();
Cada sistema necesita conocer detalles internos del enemigo y la consecuencia es un fuerte acoplamiento entre distintas partes del código. Cuanto más acoplado está un sistema, más difícil resulta modificarlo sin romper algo inesperadamente.
Un ejemplo clásico
Imaginemos un juego de acción donde tenemos varios tipos de enemigos con una arquitectura inicial basada en POO que podría parecerse a esto:
Entity
└─ Enemy
├─ FlyingEnemy
├─ TankEnemy
└─ BossEnemy
Sin embargo, con el tiempo aparecen nuevas características y ahora algunos enemigos pueden:
- Volar.
- Tener escudo.
- Lanzar misiles.
- Curarse.
- Invocar aliados.
- Teletransportarse.
La jerarquía y su arquitectura comienza a crecer rápidamente y ccada nueva habilidad genera más combinaciones:
Enemy
├─ FlyingEnemy
├─ ShieldEnemy
├─ MissileEnemy
├─ FlyingShieldEnemy
├─ FlyingMissileEnemy
├─ ShieldMissileEnemy
├─ FlyingShieldMissileEnemy
└─ ...
Llega un punto en el que mantener la estructura resulta prácticamente imposible. Este problema es conocido como explosión combinatoria de la herencia y es uno de los principales motivos por los que muchos motores modernos buscan alternativas. De este problema surtge la idea del ECS: Entidades, Componentes y Sistemas.
La idea fundamental detrás de ECS
ECS propone abandonar gran parte de estas jerarquías. En lugar de crear una clase diferente para cada combinación posible, los objetos se construyen a partir de piezas independientes. Por ejemplo:
Enemy
+ Position
+ Health
+ Fly
+ Shield
+ Missile
Otro enemigo podría tener:
Enemy
+ Position
+ Health
+ Heal
+ Teleport
Y un jefe enemigo:
Enemy
+ Position
+ Health
+ Fly
+ Shield
+ Missile
+ Summon
En lugar de crear nuevas clases, simplemente añadimos o eliminamos componentes. La cantidad de combinaciones posibles aumenta enormemente sin necesidad de crear nuevas jerarquías. Simplemente le añadimos o quitamos componentes a cada nueva entidad.
Esto ha sido sólo una explicación a nivel teórico, en los próximos artículos veremos cómo trabajar con ECS: Entidads, Componentes y Sistemas con métodos prácticos y, porque no, incluso mezclar ambos.
¿Qué es ECS?
ECS nació como respuesta a algunos de los problemas más habituales de las arquitecturas orientadas a objetos utilizadas en videojuegos (POO). Cuando los proyectos crecen, las jerarquías de herencia se vuelven complejas, las clases acumulan responsabilidades y el acoplamiento entre sistemas dificulta el mantenimiento del código.
Es importante entender que ECS no surge porque la POO (Programación Orientada a Objetos) sea mala. De hecho, muchos videojuegos comerciales siguen utilizándola con éxito. ECS aparece porque ciertos problemas, especialmente aquellos relacionados con grandes cantidades de entidades y combinaciones de comportamientos, pueden resolverse de forma más cómoda mediante composición que mediante herencia.
La propuesta de ECS consiste en separar los datos de la lógica y construir los objetos mediante composición en lugar de herencia. Sin embargo, ECS no tiene por qué entenderse como un sustituto absoluto de otras arquitecturas. En muchos casos es posible combinar conceptos de ECS con técnicas tradicionales de POO (Programación Orientada a Objetos), aprovechando las ventajas de ambos enfoques según las necesidades de cada videojuego.
En los próximos capítulos estudiaremos con detalle cada una de las piezas que forman esta arquitectura ECS: Entidades, Componentes y Sistemas, comprendiendo cómo trabajan juntos para crear videojuegos flexibles, escalables y más fáciles de mantener.
Este artículo forma parte de la serie ECS: Entidades, Componentes y Sistemas, donde se explica su arquitectura y cómo aplicarla en la programación de videojuegos.
¡ Espero que este artículo sea de vuestro interés !