ECS sin frameworks

En este artículo vamos a explicar cómo construir sistemas ECS sin frameworks, usando JavaScript y librerías gráficas propias. Cuando hablamos de ECS: Entidades, Componentes y Sistemas, muchas personas piensan inmediatamente en motores modernos como Unity o Unreal engine.

Esto puede dar la impresión de que ECS es una tecnología compleja reservada para grandes estudios o motores avanzados. Sin embargo, la realidad es muy distinta. ECS es simplemente una forma de organizar los datos y la lógica de un videojuego. ECS no depende de ningún lenguaje, motor o framework concreto. De hecho, puede implementarse perfectamente utilizando estructuras de datos básicas y código tradicional como JavaScript.

ECS es una arquitectura

Uno de los errores más habituales al comenzar a estudiar ECS consiste en pensar que necesitamos frameworks o librerías específicas para utilizarlo. Pero en realidad, ECS puede implementarse con estructuras de datos tan simples como arrays, objetos o listas. Por ejemplo:

let entities = [];
let positions = [];
let velocities = [];

Aunque este ejemplo resulta extremadamente simple, ya refleja una de las ideas fundamentales de un ECS puro: separar los datos según su función. En este caso (entities, positions, velocities) representan las entidades y componentes que pueden ser procesados posteriormente por distintos sistemas.

ECS puro y minimalista en JavaScript

En una implementación ECS pura, se utilizan las entidades únicamente como identificadores y se almacenan los componentes por separado. Por ejemplo:

let entities = [0, 1];
let positions = {
    0: { x: 100, y: 100 },
    1: { x: 200, y: 150 }
};
let velocities = {
    0: { vx: 2, vy: 0 },
    1: { vx: -1, vy: 1 }
};

Un sistema de movimiento podría recorrer las entidades y actualizar sus posiciones.

function movementSystem() {
    for (const entity of entities) {
        positions[entity].x += velocities[entity].vx;
        positions[entity].y += velocities[entity].vy;
    }
}

En este ejemplo de ECS puro no existe ningún framework y no existe ningún motor. Simplemente estamos aplicando los principios de un ECS puro utilizando JavaScript tradicional. Pero debo admitir que un ECS puro cuesta un poco de asimilar.

ECS propio y sin frameworks

Muchos desarrolladores independientes, entre los que yo me incluyo, utilizamos motores ECS desarrollados por nosotros mismos en lugar de frameworks. En estos casos resulta perfectamente posible adoptar ideas de ECS sin necesidad de implementar un ECS puro. Como ya he comentado en artículos anteriores podemos tener una entidad con sus propios componentes (datos):

let enemy = {
    position,
    velocity,
    health
};

y posteriormente utilizar sistemas para procesar esos componentes (datos).

movementSystem(enemies);
combatSystem(enemies);
renderSystem(enemies);

Aunque esta aproximación no sea un ECS puro orientado a los datos en sentido estricto, mantiene la filosofía fundamental de separar los datos de la lógica. Esto puede ser especialmente útil cuando buscamos una combinación equilibrada entre rendimiento, simplicidad y escalabilidad en nuestros videojuegos. Este es precisamente el enfoque que utilizo al desarrollar mi librería gráfica.

Librería gráfica propia

En mis proyectos personales utilizo una librería gráfica propia para crear los distintos elementos visuales del juego. En este caso muestro una sencilla función para generar las imágenes de mis videojuegos con funciones parecidas a esta:

function image(data) {
    Object.assign(this, data);
}
image.prototype.draw = function() {
    ctx.drawImage(
        this.img,
        this.x,
        this.y,
        this.width,
        this.height
    );
}

El objeto almacena sus propios datos (escalabilidad), asume que los datos recibidos son válidos (simplicidad) y evita realizar validaciones innecesarias (rendimiento). De este modo podemos crear un objeto gráfico con una simple instrucción:

let personaje = new mi_libreria.image({
    img: imagen,
    x: 10,
    y: 20,
    width: 64,
    height: 64
});

Posteriormente, todas las imágenes comparten la misma función de dibujo y pueden dibujarse usando el mismo nombre de función (sistema) simplemente haciendo:

personaje.draw();

Con este sistema propio de ECS obtenemos simplicidad y escalabilidad a la hora de crear imágenes, al tiempo que la función de dibujo de imágenes (sistema) alcanza un rendimiento muy alto.

¿Es esto ECS?

No exactamente, sin embargo, sí comparte algunas de las ideas fundamentales de un ECS:

  • Se evita duplicar código para cada objeto.
  • La lógica se reutiliza mediante funciones comunes.
  • Todos los elementos gráficos utilizan el mismo sistema de dibujo.

Este enfoque demuestra que muchas de las ideas asociadas a ECS pueden aplicarse progresivamente y sin necesidad de adoptar una implementación completa ni uso de frameworks.

ECS sin frameworks

Después de estudiar varios ejemplos, resulta fácil llegar a una conclusión: ECS no depende de Unity, Unreal Engine o frameworks. Lo importante no es la herramienta utilizada, sino la forma en la que organizamos los datos y la lógica de nuestro proyecto. Podemos construir sistemas ECS utilizando arrays, objetos, estructuras de datos propias o incluso motores desarrollados desde cero.

Aunque ECS suele asociarse a motores, frameworks o tecnologías avanzadas, sus principios pueden aplicarse perfectamente en proyectos personales utilizando únicamente estructuras de datos básicas. De hecho, muchos desarrolladores terminan incorporando ideas de ECS de forma gradual a medida que sus proyectos crecen, combinando entidades, componentes y sistemas con otras técnicas de programación.

Lo verdaderamente importante no es seguir una implementación concreta, sino comprender la filosofía que hay detrás de ECS y adaptarla a las necesidades de cada videojuego. En mi caso, la combinación de técnicas ECS con programación POO en JavaScript encaja especialmente bien.

JavaScript ofrece una flexibilidad única como lenguaje, lo que facilita la mezcla natural de enfoques como POO y ECS. Además, el manejo de objetos y estructuras de datos proporciona un rendimiento muy alto en entornos web, lo que lo hace perfectamente válido para la creación de videojuegos.

Combinar POO y ECS en JavaScript permite optimizar partes críticas como el renderizado, mientras que la POO aporta encapsulación, estructura y reutilización de lógica. Por otro lado, ECS facilita la separación de responsabilidades y la creación de sistemas comunes de forma limpia y escalable, especialmente en motores propios y sin frameworks.

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 !

Logotipo de Safe CreativeLogotipo de Creative Commons Attribution 4.0

Deja un comentario