En cuanto a NES (y SNES también en su mayoría), aquí hay una descripción básica. No escribí ningún juego de NES, pero escribí un emulador de NES (Graybox) e hice una buena cantidad de ingeniería de rev de carros viejos.
En cuanto al lenguaje de programación: sí, todo fue ensamblado. Programar el NES significaba trabajar directamente con interrupciones de hardware, puertos DMA, conmutación de bancos, etc. Afortunadamente, programar el 6502 (o más bien, el 2A03) es bastante fácil [1]:
- hay pocos registros: A, X e Y principalmente, los dos últimos solo se pueden usar para indexar e iterar
- el conjunto de instrucciones es pequeño y sobre todo sencillo
- no hay mucha memoria: la RAM principal es de 2 KB, con una extensión opcional de 8 KB respaldada por batería. De esos 2 KB, 256 bytes están reservados para la pila y la página 0 (los primeros 256 bytes) era donde querría almacenar sus punteros y valores más utilizados debido a algunos modos de direccionamiento especiales
Estas 3 cosas juntas crean un entorno que es lo suficientemente fácil de memorizar mientras se trabaja con él. Sí, administras toda la memoria tú mismo, pero eso significa esencialmente que creas un mapa completo de dónde va todo adelante y ese mapa no es muy grande porque solo tienes que preocuparte por 2K, por lo que puedes trazar eso en un pedazo de papel cuadriculado. Debía planificar las cosas un poco más y asignar estáticamente variables y constantes a las ubicaciones de RAM y ROM (en el cartucho).
Se vuelve un poco más complicado una vez que los datos de su cartucho superan los límites direccionables de la CPU. Eso es 64 KB, de los cuales los 32 KB más bajos se establecen en piedra y se asignan a todo tipo de puertos de hardware y RAM. Aquí es donde entra en juego el cambio de banco, lo que significa mapear una sección de la ROM en (parte de) el mayor espacio de direcciones de 32 KB.
Esto se puede usar como quiera el programador, pero un ejemplo de uso podría ser tener un juego con 3 niveles, con todos los datos de nivel, metadatos y código para cada nivel agrupados en áreas de memoria de 8 KB en el cartucho. El nivel puede tener devoluciones de llamada para, por ejemplo, inicialización, actualización por trama, etc. "Cargar" el nivel significaría asignar esa porción de memoria de 8 KB a, por ejemplo, 0xC000. A continuación, puede especificar que la rutina de inicio siempre está en 0xC000, la rutina de actualización de trama está en 0xC200 y los datos de nivel comienzan en 0xC800. El código principal del juego alojado en otro fragmento de memoria controla los cambios de nivel simplemente intercambiando el fragmento correcto y saltando a las direcciones absolutas 0xC000 y 0xC200 en los momentos apropiados.
Datos gráficos de Wrt: los datos de mosaicos de NES son mapas de 2 bits de 8x8 píxeles. Para el fondo, se combinan con una capa de 2 bits con resolución de 1/4. Estos valores de 4 bits se indexaron en una paleta de 16 entradas, con 53 colores únicos efectivos disponibles. Los sprites también usaron los datos de píxeles de 2 bits y cada sprite especificó su propio índice de grupo de 2 bits nuevamente formando un índice de pal de 4 bits. La imagen BG en pantalla es una matriz de 32x30 de números de índice de mosaico.
Esencialmente, al tener una tonelada de repetición e índices en índices, puede mantener los datos muy pequeños. Los datos de nivel a menudo se almacenaban como barras verticales de índices de mosaico y debido a que esas barras verticales también se reutilizaban, también se indexaban y solo se almacenaban una vez en el cartucho. Las técnicas simples de compresión de datos funcionan de manera similar. Esto permitió que Mario 1 tuviera 32 KB de datos (con espacio libre) y 8 KB de datos de mapa de bits.
En cuanto a los entornos de desarrollo, he visto algunas fotos en las que la gente trabajaba en algunas computadoras antiguas certificables conectadas a quemadores EEPROM para trabajar. La depuración asistida por herramientas no era realmente una posibilidad hasta después de la edad de SNES [2]. Esta es la razón principal por la que muchos juegos antiguos tienen errores "obvios" y por qué cosas como Gameshark podrían hacer lo que hacen; la salud del jugador siempre estará en la ubicación X de la memoria, por lo que puedes forzarla a que sea 100 en todo momento.
Si encuentra estas cosas interesantes, le animo a que consulte, por ejemplo, http://wiki.nesdev.com/w/index.php/Nesdev_Wiki.
También hay bastantes cursos de programación para NES en línea.
Espero que esta descripción simplificada haya dado una idea del desarrollo de juegos de la era de los 80.
[1] Relativamente hablando. También soy parcial ya que escribí Graybox en sí mismo en un 85% de ensamblaje PowerPC. [2] Vea la elaboración del artículo FF6: http://www.edge-online.com/features/the-making-of-final-fantasy-vi/