Capítulo 1. Historia de los patrones en juegos 2D: de Pac-Man a Stardew

CAP 01 · Bevy 0.18/0.19
"Los fantasmas de Pac-Man (Namco, 1980) son el ejemplo más antiguo de FSM en un juego comercial. Y, mira, 45 años después, siguen dando clases."

— Frase robada de una charla de GDC que no puedo citar sin tomarme tres cafés.

1.1 Por qué este libro (motivación, o: por qué sigues aquí)

Abre cualquier motor de juegos "moderno" y verás un menú tentador: ECS, behavior trees, observers, eventos, schedule graphs, fixed timestep... La mayoría de las cosas que vas a usar con Bevy ya existían cuando tu bisabuelo llevaba pantalones de campana. Lo que cambió no son las ideas, sino el lenguaje en que se cuentan y el café que tomaban sus autores.

Este capítulo es una visita guiada por cuarenta y pico años de juegos 2D, no para coleccionar lore, sino para mostrar cómo cada problema recurrente —"¿cómo hago que el fantasma decida?", "¿cómo evito que mi código se vuelva un plato de espaguetis?", "¿cómo represento una unidad en un RTS?"— se resolvió una y otra vez con patrones que siguen vivos en Bevy. Vamos a ver FSM, entity-component, observer, event bus, behavior tree y, ya en los años 2010, la versión moderna del entity-component llamada ECS.

Piensa en una cocina de restaurante: cada cocinero hace una sola cosa, pero entre todos sacan el menú. Esa es la idea que vertebra el libro, y la lleva defendiendo la industria desde los arcades, antes de que nadie la llamara "arquitectura".

1.2 Cronología visual 1978-2021

Antes de hablar de patrones, una línea de tiempo apretada. No es exhaustiva (sería un libro entero), pero ancla cada idea a un juego que ya conoces.

1978  Space Invaders (Taito) ............ primer "objeto" en RAM
1980  Pac-Man (Namco) ................... primera FSM comercial en fantasmas
1985  Super Mario Bros (Nintendo) ....... tiles + colisiones AABB
1989  Prince of Persia (Mechner) ........ animación rotoscópica
1993  Doom (id Software) ................ separación datos/lógica y doors/triggers
1995  Warcraft (Blizzard) ............... unidades como entidades seleccionables
1997  Age of Empires (Microsoft) ........ IA basada en tareas + pathfinding
1997  Dungeon Keeper (Bullfrog) ......... IA basada en scripts
1998  Baldur's Gate (BioWare) ........... código espagueti con guarnición
1998  Thief (Looking Glass) ............. IA stim-response con estados progresivos
1999  RollerCoaster Tycoon (Sawyer) ..... "ECS accidental" escrito en ensamblador
2014  Shovel Knight (Yacht Club) ........ plataformas casi ECS hechas a mano
2016  Stardew Valley (ConcernedApe) ..... UI densa + save local, mod-friendly
2016  Overwatch (Blizzard) .............. ECS en producción AAA
2017  Hollow Knight (Team Cherry) ...... tilemap masivo + luz 2D
2018  Celeste (Maddy Makes Games) ....... fixed timestep para plataformas perfectas
2020  Hades (Supergiant) ................ ECS indie, música adaptativa
2021  Inscryption (Daniel Mullins) ...... save con migración de versiones
2021  Bevy 0.6 (Cartes) ................. el ECS de Rust que une todo esto

Hay una flecha invisible que conecta todas esas viñetas: cada juego aprendió algo del anterior, se equivocó, y dejó un patrón escrito en alguna parte.

1.3 Cinco patrones que nacieron en un arcade

1.3.1 FSM (Finite State Machine, máquina de estados finitos): los fantasmas de Pac-Man

El 1980 Namco lanzó Pac-Man. Su director, Toru Iwatani, le pidió a su programador Shigeo Funaki que los fantasmas no se sintieran "tontos". El resultado fue un hito técnico y pedagógico: cada fantasma tenía cuatro estados (House, Scatter, Chase, Frightened) y un ciclo temporal (Scatter 7 s → Chase 20 s → Scatter 7 s → …). Cuando Pac-Man comía una power pellet, se activaba Frightened durante ~6-8 s, el fantasma se ponía azul y huía en línea recta. Si lo tocabas en ese estado, pasaba a Eaten (los clásicos ojos que vuelven a casa por el camino más corto).

Esto es una máquina de estados finitos: un ente con un estado interno, transiciones disparadas por condiciones, y un comportamiento distinto por estado. Es tan vieja como la informática, pero en Pac-Man fue la primera vez que millones de jugadores la sintieron en acción.

FSM / Finite State Machine (patrón): sistema con un número finito de estados y transiciones explícitas entre ellos. Cada estado define un comportamiento; cada transición, una condición.
Estado (concepto): "modo" en el que se encuentra una entidad. Un fantasma está en `Chase` o en `Frightened`, no en ambos.
Transición (concepto): cambio de estado disparado por una condición (timer, evento, colisión).
Componente (concepto): dato que se le "pega" a una entidad. Hoy Bevy lo llama `Component`; en 1980 nadie lo llamaba así, pero la idea ya estaba.

1.3.2 Entity-Component (antes de tener nombre): Space Invaders y Super Mario

En Space Invaders (Taito, 1978), Tomohiro Nishikado metió en la memoria del Intel 8080 un array de 55 invasores con coordenadas (x, y). Cada fila se movía al unísono y disparaba cuando le tocaba. No había "clase invasor" con herencia, había una tabla en RAM. Cada invasor era, en esencia, un conjunto mínimo de datos (posición, sprite, fila) más una función que actuaba sobre el array.

Eso es entity-component avant la lettre: la entidad (índice en el array) no contiene comportamiento; los datos van sueltos en columnas; el comportamiento vive en sistemas aparte.

Super Mario Bros (Nintendo, 1985) llevó la idea a otro nivel. El mundo se dividía en tiles de 16×16 píxeles: bloques, suelo, tuberías, monedas. Mario tenía una posición, velocidad, estado (Small, Big, Fire) y un sprite. Una seta era un sprite con una velocidad horizontal y un timer de rebote. Un Goomba era un sprite con velocidad y una IA microscópica ("camina hasta chocar con un bloque, entonces date la vuelta").

Nada de eso era herencia. Era composición: el mismo motor recorría tablas de objetos y aplicaba reglas genéricas. ¿Mario es invencible porque heredó de MarioEstrella? No: porque tiene un componente Invencible { restante: 8.0 } y una flag StarPower. Si le quitas la estrella, deja de ser invencible. Esa separación entre lo que eres y lo que tienes es la columna vertebral de ECS hoy.

1.3.3 Animation pipeline: rotoscopia y Prince of Persia

Saltemos a Prince of Persia (1989), Jordan Mechner. Mechner grabó a su hermano David saltando, gateando, cayendo, y luego pintó frame a frame sobre las fotos. El resultado: animación más fluida que cualquier arcade anterior. Lo importante aquí no es solo el arte: es que los datos de animación se separaron del código de juego. Un archivo de frames por personaje, índices, duraciones, callbacks de evento ("cuando termina la animación de caída, pasa a Idle"). Esto es un patrón de data-driven animation: el comportamiento del personaje lo dicta una tabla, no un switch gigante en C.

Bevy usa exactamente esto en su AnimationPlayer y en sistemas de sprite atlas con índices.

1.3.4 Scripting + IA: Dungeon Keeper, Baldur's Gate y Thief

Los 90 fueron la década en que la IA intentó no ser "tonta". Aparecieron tres líneas que perviven:

Hoy Bevy implementa exactamente ese "estimulo → respuesta" con observers: tú lanzas un evento (EntityEvent), y las entidades suscritas ejecutan su lógica. La sospecha del guardia es, literalmente, un Component con un contador que un observer decrementa con el tiempo.

1.3.5 Entity-Component "de verdad": Doom, Warcraft, RollerCoaster Tycoon

Doom (id Software, 1993) popularizó el bus de eventos: las puertas, los triggers de suelo, los monstruos y las armas se comunicaban con mensajes genéricos (Door::Open, Line::Cross). El motor no sabía qué haría el listener; solo entregaba el mensaje. Hoy esto es un patrón tan ubicuo que casi nadie se detiene a nombrarlo: Bevy lo llama EventReader<T> / EventWriter<T>.

Warcraft (Blizzard, 1995) y Age of Empires (Microsoft, 1997) dieron el salto conceptual: cada unidad (peón, marine, aldeano) era una entidad con atributos (HP, posición, propietario, orden actual) y un conjunto de comportamientos según tipo. La interfaz de selección múltiple era un detalle visual sobre una arquitectura entity-centric.

RollerCoaster Tycoon (1999), de Chris Sawyer, merece una mención especial: está escrito casi enteramente en ensamblador x86 y simula miles de visitantes con datos tabulares que son, sin quererlo, un proto-ECS. Cada visitante tenía estado (posición, hambre, felicidad,bladder), sin métodos propios. Sistemas externos actuaban sobre tablas. Sawyer lo hizo por rendimiento bruto, no por elegancia teórica, pero el resultado es idéntico al que persigue Bevy: datos separados, comportamientos centralizados.

Warcraft: Orcs & Humans (Blizzard, 1995) — uno de los primeros RTS donde grupos de unidades se mueven como uno solo. La selección múltiple obligó a separar "qué soy" (unidad) de "cómo me muevo" (formación).
Thief: The Dark Project (Looking Glass, 1998) — la IA original fue reescrita al 80 % por Tom Leonard tres semanas antes de la fecha de envío. Se sigue enseñando en universidades.
RollerCoaster Tycoon (Chris Sawyer, 1999) — escrito en ensamblador x86 con un editor hexadecimal como IDE. Compilaba en 30 segundos y corría miles de agentes.

1.4 Por qué OOP se atragantó

A finales de los 90 y principios de los 2000, la orientación a objetos reinaba. Si eras serio, hacías tu class Player : public GameObject : public Entity : public Updatable. Si eras más serio todavía, añadías design patterns: Singleton, Factory, Observer, Visitor... Y todo se fue volviendo un jeroglífico.

El problema con OOP en juegos es triple:

  1. Herencia bloquea composición. En class Zombie : public Enemy : public Actor : public Collidable : public Renderable, ¿qué pasa si quieres un zombi que no es Collidable? ¿Haces class FlyingZombie? ¿Duplicas código? ¿Un mixin? Cada nuevo caso rompe la jerarquía.
  2. El acoplamiento hace lentos los cachos. Cuando tu Player tiene 12 métodos virtuales y un update() que llama a input()physics()animation(), la CPU no puede predecir saltos y se pierde entre cachés.
  3. El árbol crece y se pudre. Baldur's Gate tiene literalmente decenas de miles de clases/scripts con dependencias cruzadas. Sin herramientas de refactor, mantenerlo es arqueología.

OOP no está "mal" en abstracto; pero para un dominio con cientos o miles de entidades semi-independientes, la herencia es la herramienta equivocada. La industria lo aprendió por las malas.

❌ Clasificar por *lo que las cosas son*: clase `Player`, clase `Enemy`, clase `EnemyWithShield`, clase `EnemyWithShieldAndFireAura`...
   Terminas con una pirámide de 17 niveles donde añadir un componente nuevo implica editar 4 archivos.

✅ Clasificar por *lo que las cosas tienen*: una entidad tiene `Health`, otra tiene `Position`, otra tiene `Sprite`. El comportamiento vive en sistemas que filtran por presencia de componentes.
   Añadir un aura de fuego = un componente nuevo, sin tocar las clases existentes.
💡 Por qué: la herencia favorece jerarquías; los juegos son grafos.

1.5 Cómo llegamos a Bevy (Amethyst → Bevy)

Antes de Bevy hubo Amethyst, un motor ECS en Rust que arrancó como proyecto en 2016. Amethyst sentó las bases: Specs como librería de ECS, plugins como unidades modulares, sistemas tipados. En 2021, Amethyst publicó "Starting fresh": dejaban el proyecto, recomendaban oficialmente a Bevy como heredero espiritual, y donaban lo que tenía sentido.

Bevy arrancó en 2019 de la mano de Carter Anderson (alias "Cartes") como un experimento personal. Su premisa: un ECS sin compromisos, con un render graph moderno, schedules explícitos, y una promesa de compilación rápida. Para 2021 ya era la opción de facto de quien quisiera hacer juegos 2D en Rust. La versión actual del libro, 0.18/0.19, es el resultado de cinco años de iteración despiadada: cada vez que Bevy simplifica una API, otra se deprecia. Hay dolores de cabeza, sí, pero también una coherencia interna que motores más viejos no tienen.

La genealogía completa, entonces, es:

arcade (entidad-en-tabla) → OOP (herencia) → datos-tabulares (RCT, Thief)
                          ↓
              ECS "moderno" en Rust: Amethyst (2016-2021)
                          ↓
                       Bevy (2019-)

Bevy no inventó ECS, lo consolidó, lo puso en Rust con ownership (posesión de memoria gestionada por el compilador) y lo adornó con observers, required components, schedules y un render graph que se explica solo. Cada capítulo de este libro mira un patrón de los que acabamos de ver y muestra cómo Bevy lo implementa hoy.

1.6 Cinco patrones que sobrevivieron 40 años

Para no perder el hilo, los patrones que aparecen en el resto del libro, mapeados a su origen:

PatrónNació (más o menos)Dónde lo verás en Bevy
FSMPac-Man (1980)#[derive(States)], SubStates
Entity-ComponentSpace Invaders / SMB (1978-85)Entity, #[derive(Component)]
Animation pipelinePrince of Persia (1989)AnimationPlayer, atlas
Scripted behavior / BTDungeon Keeper (1997)BT como componentes (cap. 21)
Event busDoom (1993)Event<T>, EventReader<T>
Stim-responseThief (1998)Observer, Trigger<E>
ECS modernoOverwatch (2016 blog/GDC 2017)World, System, archetype
Fixed timestepCeleste (2018)FixedUpdate, schedule
Composición indieHades (2020)entidades con muchos componentes

1.7 Glosario del capítulo

ECS / Entity-Component-System (arquitectura): modelo donde la entidad es un id, los datos van en componentes, y los comportamientos son funciones puras llamadas sistemas.
FSM / Finite State Machine (patrón): ver caja anterior.
OOP / Object-Oriented Programming (paradigma): programar con clases que heredan entre sí. Útil para GUI, incómodo para juegos con muchas entidades.
Stim-response (patrón de IA): la entidad recibe estímulos externos y reacciona según su estado interno (sospecha, alerta, miedo). Patrón precursor de los observers modernos.
Observer (patrón): entidad suscrita a un evento. Cuando el evento se dispara, el observer reacciona sin que el emisor sepa quién escucha.
Behavior tree / BT (patrón de IA): estructura en árbol donde cada nodo decide "éxito / fallo / en curso" y se compone en secuencias, selectores y decoradores.
Rotoscopia (técnica de animación): animar sobre imágenes de referencia filmadas del mundo real.
Event bus (patrón): sistema central que reparte mensajes entre productores y consumidores sin que se conozcan entre sí.

1.8 Trivia histórica

Space Invaders (Taito, 1978) — Tomohiro Nishikado lo programó en un Intel 8080 con 2 KB de RAM. Para acelerar el ritmo conforme caían los invasores, simplemente borraba menos filas en cada frame (los píxeles vacíos dejaban el efecto).
Prince of Persia (Jordan Mechner, 1989) — el 50 % de los *frames* de animación se grabaron a las 4 de la madrugada a su hermano David corriendo por el garaje.
Overwatch (Blizzard, 2016) — la charla *"Overwatch Gameplay Architecture and Netcode"* de Tim Ford en GDC 2017 popularizó el término ECS dentro de la industria AAA. Puedes verla en el GDC Vault.
Hades (Supergiant, 2020) — el ECS casero de Supergiant gestiona más de 300 componentes distintos. Cada arma, cada boon de un dios, cada estado narrativo es un componente separado.
Stardew Valley (ConcernedApe, 2016) — Eric Barone escribió todo el motor desde cero en C# usando MonoGame. Es el ejemplo más puro de "data-driven + UI densa + save local" sin frameworks externos.

🎯 1.9 Patrón del capítulo: "de la tabla al componente"

Problema: tengo cientos de "entes" distintos (zombis, balas, partículas, pickups) que comparten algunos rasgos pero no todos. Crear una clase por combinación es inviable.

Solución: tratar la entidad como un id y almacenar sus datos en componentes planos. Los sistemas iteran sobre las entidades que tengan los componentes que necesitan.

Cuándo sí:

Cuándo no:

En el próximo capítulo bajamos a tierra: Rust. Sin conocerlo, Bevy te parecerá un muro de ladrillos.

Lo que vimos

En el siguiente

Abrimos el capó de Rust: ownership, borrowing, enums, traits, Result y Option. Lo justo para que el compilador deje de insultarte y Bevy deje de sonar a otro idioma.