Como las empresas de videojuegos "creaban a mano" sus juegos antes de los motores maduros
La respuesta a esta pregunta es mucho más cruda de lo que la mayoría imagina.
Los desarrolladores de hoy abren Unity o Unreal y el editor está ahí, el motor de física está ahí, el pipeline de renderizado está ahí, el sistema de iluminación está ahí. Su trabajo es rellenar contenido dentro de un marco ya construido. Eso simplemente no existía en los años 80. No había marco de trabajo, no había motor, no había infraestructura reutilizable. Si querías hacer un juego, primero tenías que construir una máquina que pudiera ejecutarlo, y luego en esa máquina, usando lenguaje ensamblador, le decías de qué color debía ser cada píxel.
Todo empezó con píxeles.
Los gráficos de los primeros juegos se escribían directamente en memoria. Los programadores necesitaban saber la dirección de memoria correspondiente a cada posición de la pantalla. Para dibujar un punto en cierto lugar, escribías un valor en esa dirección — el valor representaba el color. Para dibujar una línea, escribías un bucle, indicando a la CPU que escribiera en cada N bytes desde esta dirección hasta aquella. Para dibujar un personaje, convertías cada fila de píxeles en números hexadecimales, los escribías línea por línea, encontrabas el desplazamiento correcto en la pantalla y colocabas esos números ahí.
Este proceso hoy se llama “renderizado por software”. En aquel entonces no tenía nombre, porque no había alternativa — simplemente se llamaba “cómo salía la imagen”.
Cuando Carmack trabajaba en el primer Wolfenstein, se enfrentó a un problema concreto: la arquitectura gráfica del IBM PC no soportaba el desplazamiento horizontal suave. La pantalla se actualizaba refrescando el fotograma completo de una vez, causando tearing al mover la imagen — parecía que temblaba. Pasó muchísimo tiempo estudiando los registros de bajo nivel de la tarjeta gráfica y encontró una manera de engañar al hardware para que hiciera algo que no soportaba de forma nativa, simulándolo por software para que la imagen se desplazara suavemente.
No había documentos para esto, ni experiencia previa. Simplemente se sentó, estudió a fondo los manuales de hardware y lo descubrió paso a paso mediante prueba y error.
Esta era la condición básica de trabajo de los programadores de aquella época. No te enfrentabas a un sistema con un manual — te enfrentabas al hardware mismo. Tenías que conocer el hardware mejor que el hardware se conocía a sí mismo, y encontrar espacio en sus grietas.
El sonido era una dimensión completamente diferente del problema.
El sistema de sonido de los primeros PC era un zumbador — el PC Speaker. Solo podía producir un sonido: un pitido. Para reproducir música con él, tenías que controlar la frecuencia de vibración de este zumbador, cambiando entre diferentes frecuencias a una velocidad extremadamente alta, aprovechando la persistencia auditiva humana para que sonara como una melodía. Esta técnica se llama PWM, modulación por ancho de pulso. El principio es física acústica, la implementación es control de temporización preciso al microsegundo. Si te equivocabas aunque sea un poco, lo único que obtenías era ruido.
Había programadores en esa época que investigaban专门 cómo extraer más capas de sonido del PC Speaker. Su método era cambiar entre diferentes frecuencias en ventanas de tiempo extremadamente cortas, creando un efecto pseudoestéreo. Esta técnica se trataba como magia negra en la escena demo de la época. Cogías un zumbador que solo podía hacer “piiii” y lograbas algo que sonaba cercano a la música. Otros programadores que lo oían no tenían idea de cómo lo habías hecho — porque el原理 no era intuitivo. Necesitabas una comprensión extremadamente precisa del control de temporización para lograrlo.
Cuando llegaron Adlib y Sound Blaster, trajeron el sonido de síntesis FM. Este era otro sistema que había que aprender desde cero. El principio de la síntesis FM es usar la forma de onda de una frecuencia para modular la forma de onda de otra, produciendo timbres complejos. Diferentes parámetros de modulación producían diferentes sonidos de instrumentos. Los programadores tenían que ajustar los parámetros ellos mismos, probar qué combinaciones sonaban a piano, cuáles a violín, cuáles a explosión. No había una respuesta estándar — todo dependía del oído y la experiencia. En aquella época, cada programador de sonido de cada empresa tenía sus propias tablas de parámetros acumuladas. Eran secretos comerciales en el sentido más estricto.
El sentido del diseño a nivel de lógica de juego era otro asunto completamente distinto.
Sin motor, no había detección de colisiones prefabricada, ni simulación física prefabricada, ni marco de IA prefabricado. Si querías que un personaje se detuviera al llegar a una pared en lugar de atravesarla, tenías que escribir la detección de colisiones tú mismo, definir qué significaba “colisión” tú mismo, y decidir cómo cambiarían la velocidad y la posición del personaje después de la colisión tú mismo.
La detección de colisiones de los primeros juegos era simple y brutal: tratar tanto al personaje como a los obstáculos como rectángulos, comprobar si los dos rectángulos se solapaban, y si lo hacían, detenerse. Este esquema era rápido, pero el problema era que los personajes reales de los juegos no son rectángulos — son sprites con formas. Siempre había un desfase entre la caja de colisión rectangular y el gráfico real. Por eso en los juegos antiguos a menudo las balas que claramente fallaban aún se registraban como impactos, o los personajes se detenían antes de tocar visiblemente la pared.
Los jugadores de la época no llamaban a esto un bug. Lo llamaban una característica del juego. Porque no sabían que podía ser de otra manera.
Otra cosa que merece la pena mencionar: los primeros PC tenían una memoria extremadamente limitada. 640KB fue el techo de hardware durante una década entera. Ese número hoy ni siquiera puede contener una captura de pantalla. Los programadores tenían que meter todo el contenido del juego — gráficos, efectos de sonido, mapas, código lógico — en esos 640KB, y además dejar espacio para los datos en tiempo de ejecución.
¿Cómo? Compresión, carga por fragmentos y reutilización.
La compresión de datos gráficos era una disciplina propia. Los programadores tenían que inventar sus propios algoritmos de compresión, haciendo que los datos gráficos fueran lo más pequeños posible al almacenarlos y descomprimiéndolos al instante cuando se necesitaban. Si el mapa era demasiado grande para caber, lo dividías en fragmentos, cargabas el fragmento en el que estaba el jugador y lo descargabas cuando salía. Hoy esta técnica se llama carga en streaming. Entonces se llamaba “no hay otro modo”.
Cada byte en memoria era disputado. Al escribir código, los programadores tenían que pensar constantemente: ¿puedo usar un tipo de dato más pequeño para esta variable? ¿Puedo usar menos variables temporales para esta lógica? ¿Puedo reducir el número de accesos a memoria en este bucle? Esta forma de pensar es en gran medida innecesaria para los programadores de hoy. El hardware moderno tiene suficiente margen de memoria y los compiladores son lo suficientemente inteligentes en optimización — no necesitas contar cada byte.
Pero los programadores de aquella época llevaban todo el funcionamiento interno del ordenador en la cabeza. Desde los ciclos de instrucción de la CPU hasta la disposición de la memoria y los registros de la GPU, tenían que entender cada capa, porque cada capa era una grieta que podrían necesitar explotar.
Carmack escribió una vez un fragmento de código para calcular la raíz cuadrada inversa, con un comentario que decía “what the fuck”. Este código usaba un truco de manipulación de bits extremadamente反直觉 para aproximar rápidamente la raíz cuadrada inversa, mucho más rápido que la biblioteca matemática estándar — todo para ahorrar tiempo de cálculo en el renderizado 3D. Este fragmento de código se convirtió más tarde en una leyenda de la cultura programadora, citado una y otra vez, porque representaba una forma de pensar que ha desaparecido: el método de exprimir el rendimiento de las matemáticas y la creatividad dentro de las limitaciones del hardware.
Los programadores de juegos de aquella época estaban más cerca de un híbrido de ingenieros y científicos. El problema que resolvían no era solo “cómo implementar esta funcionalidad” — era “si esta funcionalidad puede implementarse siquiera dentro de los límites físicos de esta máquina”.
Esas son dos preguntas muy diferentes.
La primera pregunta tiene una respuesta estándar: buscar en la documentación, llamar a la API, seguir los pasos. La segunda pregunta no tiene respuesta estándar. Tienes que inventar la respuesta tú mismo. A veces la respuesta existe, a veces no. Primero tenías que averiguar en qué caso te encontrabas, y luego decidir qué hacer.
La industria del juego actual es un sistema colaborativo masivo. Empresas de motores, empresas de middleware, empresas de herramientas — cada capa tiene a alguien trabajando en ella. Los desarrolladores están en la cúspide de este sistema, centrados en el contenido mismo. Esto es progreso, es algo bueno — permite que más personas hagan juegos, y que la escala y complejidad de los juegos superen con creces lo que era posible en el pasado.
Pero aquella época de construir juegos desde los píxeles tenía algo que ya no existe hoy.
Abres el juego y sabes de dónde vino cada punto de la pantalla, cómo se hizo cada sonido, cómo se juzgó cada colisión. Porque todo eso lo escribiste tú, línea por línea. No había nada que alguien más hiciera por ti.
Este sentimiento tiene un nombre. Se llama control total.
Así que los grandes que desarrollaron juegos en aquella época — eran verdaderamente grandes. Verdaderamente extraordinarios.