Tutorial de programacion para NES - Básico

Foro sobre la N.E.S. Aqui esta el verdadero mejunje donde podreis conversar.

Responder
Avatar de Usuario
Diskover
##El Jefe##
Mensajes: 487
Registrado: Lun, 27 Oct 2003, 20:59
Ubicación: Planeta Tierra
Contactar:

Tutorial de programacion para NES - Básico

Mensaje por Diskover » Mié, 23 Nov 2011, 22:02

Comenzamos este muy interesante hilo sobre la programación para NES de la mano del maestro wave el cual nos explicará los pasos mas básicos mediante su framework basado en el lenguaje de programación NesHLA inventado en 2005 y que da bastantes buenos resultados como comprobareis mas adelante.

Yo mismo he conseguido crear unas roms muy básicas para la NES gracias a las enseñanzas de wave, y el mismo será el encargado de seguir ampliando conocimientos y mejorando su framework para que todo sea mas sencillo y fácil de hacer.

Este hilo se comenzó hace unas semanas en el foro de consolas clásicas de www.ElOtroLado.net pero debido al poco éxito que se cosechaba allí hemos decidido traerlo aquí, que seguro que sera mejor acogido.

Sin mas, os hago un recopilatorio del tutorial mas básico y de los avances que yo mismo he podido hacer sobre el mismo.

Espero que lo sepáis aprovechar.

HILO ORIGINAL: http://www.elotrolado.net/hilo_tutorial ... co_1694073

TUTORIAL BÁSICO
Arquitectura Basica
Antes de programar una sola linea hay que tener clara la arquitectura de la NES (la básica, sin uso de mappers)
La NES es capaz de diseccionar 64KB de memoria (con mappers la cosa cambia "un poco" porque nunca se pueden ver mas de 64KB a la vez).

Dirección -- Tamaño Dispositivo

$0000 $0800 2KB RAM
$0800 $0800 Mirrors de la RAM
$1000 $0800 Mirrors de la RAM
$1800 $0800 Mirrors de la RAM
$2000 $0008 registros PPU
$2008 $1FF8 Mirrors de $2000 cada 8 bytes
$4000 $0018 registros NES APU y I/O
$4018 $BFE7 PRG ROM del cartucho, PRG RAM del cartucho y registros del mapper

$FFFA - vector NMI
$FFFC - vector Reset
$FFFE - vector IRQ/BRK

Nuestros programas estarán siempre (a no ser que carguemos código en RAM) en la parte alta $8000-$FFF9.
Y nuestras variables en los primeros $2000 con excepción de
A) la pila, que esta en $01E0 - $01FF ya que usaremos solo 32bytes, sino se nos comería hasta 256
y
B) la copia de la memoria de los sprites, que se puede colocar en cualquier lugar de la RAM (en incrementos de 256 en 256) y que con el framework esta situada en $0200 y ocupa 256bytes

Asi que tendremos 2048 - 256 - 32 = 1760 (menos las que se comerá el framework.)
Importante, la ram que va de $0000 a $00FF permite acceder de forma mas rápida a la misma ya que la cpu se puede ahorrar un byte a la hora de escribir las instrucciones. Normalmente se utiliza esta ram para rutinas que requieran máxima velocidad. A esta zona especial se la llama Zero Page RAM.

vector NMI : puntero a la interrupción que se ejecutara cada vblank del monitor.
vector Reset : puntero a la interrupción que se ejecutara al hacer reset.
vector IRQ/BRK : puntero a la interrupción que se ejecutara al ejecutar la instrucción BRK o recibir una IRQ externa
Por ahora nos interesan los 2 primeros, que son muy fácilmente configurables en NESHLA como veremos mas adelante.

Configuración de la NES para el tutorial
Todos los ejemplos aquí generados serán para una NES sin ningún tipo de mapper, es decir, cartuchos con 32KB de ROM y 8KB de CHROM, que es la configuración mas sencilla y no requiere conocimiento de ningún mapper, si la cosa va mas allá se podrán hacer pruebas con otros mappers como AXROM, UNROM, MMC1 o MMC3, que están soportados por mi framework.

Programa básico
Una cosa muy importante a tener en cuenta cuando se programa en la NES es que si no se desactiva el vídeo, todos los cambios que se hagan en pantalla, sprites, etc, han de hacerse DENTRO de la interrupción NMI, para ello el framework incluye un sistema de colas para asignar tareas a la rutina NMI que ya esta configurada.

Descripción de los ficheros de un proyecto con el framework (o lo que nos importa tocar ya que el resto lo hará el framework por nosotros):

Aquí pondré para descarga próximamente un proyecto vació que cargará un fondo y permitirá mover un sprite por pantalla y pasare a explicar todos sus archivos.

Llegados a este punto decidme si habéis entendido algo para que pueda ir ampliando la información para que quede todo mas claro.


Estructura basica del Framework y explicacion de archivos importantes
La estructura de carpetas es la siguiente:
/bin: codigo fuente del framework y del NESHLA, no es necesario modificar nada aqui, pero si quereis echar un vistazo, mejor ;)
/docs: documento con informacion del NESHLA, tampoco es necesario hacer nada aqui
/tools: emuladores, editores graficos y un programa para cargar samples (que aun no he conseguido hacer funcionar :p)
/projects: aqui estaran las carpetas de nuestros proyectos, es donde hay que entrar

/projects/demo:
make.bat: genera el archivo nes en /out
runDebug.bat: ejecuta el archivo compilado en /out con fceux version debug
runProfile.bat: ejecuta el archivo compilado en /out con virtuanes version profile (permite calcular cuantos ciclos de cpu se gastan entre dos puntos)

/out: archivos compilados y la rom project.nes con nuestra demo :)
/data: archivos binarios a incluir en nuestro codigo

/src:
project.as: archivo base del proyecto, no hay que tocar nada.
/src/include/
/src/include/chr_data.h: aqui definimos los graficos que vamos a usar, en nuestro caso solo 2 archivos de 4KB
/src/include/project.h: aqui definimos varias caracteristicas del proyecto para el framework, por ahora tampoco hay que tocar nada
/src/ram/
/src/ram/sram.as: definición de variables en la RAM externa o SAVE RAM, no definiremos ninguna
/src/ram/wram.as: definición de variables en la RAM común, aquí todas las variables gordas que no sean criticas
/src/ram/zram.as: definición de variables en la RAM rápida, aquí las variables de bucles, rutinas criticas, etc...

/src/rom/
/src/rom/romdata.as: este archivo es importante, ya que definimos todos los bloques de 8KB que vamos a usar en nuestro proyecto,
para este caso, los 3 primeros bloques se utilizaran para datos y en el ultimo se pondrá el código del framework y de la demo.
/src/rom/main.as: aquí esta el código principal de nuestra demo, es el código que explicare mas adelante

La demo
http://www.mediafire.com/?26mgv1z115gggzt

Controles: Cruceta para mover al Mario animado.
Select y Start: mover el escenario.

Los gráficos del Drops son de un antiguo juego mio que intentaba rehacer para la NES.


Entendiendo la PPU

Una de las partes mas complicadas de la NES es su PPU, la que dibuja toda la pantalla, vamos a ver su mapa de memoria.
A este mapa de memoria no se puede acceder directamente con la CPU.

Direcciones -- Tamaño -- Descripción

$0000-$0FFF $1000 Pattern Table 0 [Banco CHR inferior]
$1000-$1FFF $1000 Pattern Table 1 [Banco CHR superior]
$2000-$23FF $0400 Name Table #0
$2400-$27FF $0400 Name Table #1
$2800-$2BFF $0400 Name Table #2
$2C00-$2FFF $0400 Name Table #3
$3000-$3EFF $0F00 Mirrors de $2000-$2FFF
$3F00-$3F1F $0020 Palette RAM indexes
$3F20-$3FFF $0080 Mirrors de $3F00-$3F1F

Descripción breve:
NOTA: 1 tile es un dibujo de 8x8 pixels, en el caso de la nes con 2bpp (2 bits por pixel)

Las pattern table
Son las que contienen los tiles a usar tanto por sprites como por fondos (256 tiles por tabla).
Los fondos solo pueden usar una de las dos tablas, que se puede definir en el framework mediante el macro:

Código: Seleccionar todo

vid_enablePPU_CTRL_1(#valor)


En la demo se utiliza:

Código: Seleccionar todo

vid_enablePPU_CTRL_1(#(BACKGRND_TABLE_0|SPRITE_TABLE_1))


Que asigna la tabla 0 al escenario y la tabla 1 a los sprites.

Los sprites en el caso de estar configurados a 8x8 (como en el caso de la demo, que es la configuración por defecto) también pueden utilizar solo una tabla, pero si utilizamos sprites de 8x16, estos utilizaran ambas tablas ya que al utilizar 2 tiles cada indice, se puede acceder a los 512 tiles disponibles.

En el caso de la demo no tenemos que cargar estos datos ya que vienen directamente de la CHRROM, si tuvieramos un mapper con CHRRAM, tendríamos que "cargar" los gráficos en la PPU antes de usarlos.

Las Name Table
Son las que definen que tiles usar para confeccionar el escenario. De serie, la NES trae memoria para almacenar 2 Name tables, hay algún juego con configuración 4-SCREEN que añade las dos restantes, pero lo obviaremos por ser poco frecuente.

Así que tenemos 2, que se situaran una encima de otra si el mirroring es HORIZONTAL y una al lado de otra si el mirroring es VERTICAL.

V:
01
01
H:
00
11

Para formar un "campo" de 512x480 pixels por donde podemos hacer scroll.
Cada Pantalla mide 256x240 por lo tanto tienen 32x30 tiles cada una, ocupando 960 bytes la definicion de una pantalla entera de izquierda a derecha y de arriba a abajo.
Los 64 bytes restantes son utilizados para dar "color" a los tiles, asignando 1 paleta a cada grupo de 2x2 tiles, a esto se le llama ATTRIBUTE table.
De la siguiente forma:

ABEFIJ
CDGHKL

De modo que el byte 1 da color a esos 4 tiles (ABCD) y aqui tenemos uno de los motivos porque en muchos juegos se utilizan recuadros de 16x16 pixels para el escenario.
El byte define la paleta para cada tile de la siguiente forma: DDCCBBAA donde cada pareja es una de las 4 paletas del escenario.

La paleta de colores
La paleta de colores de guarda en $3F00-$3F1F, son 32 valores pero no todos son usables, ahora explicare por que.
La NES tiene 8 paletas, 4 para el fondo y 4 para los sprites.
Para los sprites, el primer color es transparente por lo que tenemos solo 3 por sprite, lo que nos da 12 colores.
Para el fondo pasa algo similar, solo que el primer color de todas las paletas esta compartido y es el color de fondo.

Con el framework si queremos cargar una paleta de colores:
La pasamos al buffer de paletas (8 paletas, segun lo definido en #define NPALBUFFERS 8 en el archivo project.h, se puede ampliar):

Código: Seleccionar todo

//Cargamos la paleta de mario en el buffer 0
   ldx #0
   ldy #sizeof(PALENT)
   while(not zero) {
      lda marioPal, x
      sta palletesBuffer, x
      inx
      dey
   }


Y la cargamos donde queremos:

Código: Seleccionar todo

vid_setPalette(#0, #SPRITE_PALETTES.PAL_0);


Esta funcion se encarga de copiar los datos de nuestra paleta a la PPU en la direccion que toca, genial ¿no?
Eso si, esta funcion solo se puede usar cuando no tenemos el video activado, de modo que si no es asi, hay que cargarla en la NMI, a traves del servicio de colas:

Código: Seleccionar todo

int_queueNmiEventStart(#EFFECT.SET_PALENTRY)
         int_queueNmiEventParam(#0)
         int_queueNmiEventParam(#SPRITE_PALETTES.PAL_0)
         int_queueNmiEventEnd()


Los indices de la paleta siguen esta imagen:
Imagen

No esta recomendado utilizar los 6 colores negros de la derecha ya que podrían no funcionar bien en hardware real o incluso estropearlo.
Así que tenemos 14*4 = 56 colores a nuestra disposición.

En la siguiente entrega, como modificar esta memoria externa a la CPU, de la PPU, y las cabeceras de un archivo .nes (aunque esto se maneja bien desde el NESHLA).


Como modificar la memoria de la PPU (Paleta de colores, tiles, pattern tables, attribute tables...)
Lo primero que hay que hacer antes de intentar acceder a la memoria es resetear el registro de acceso, ya que como haremos dos escrituras para definir la direccion de destino, hay que indicar que empezaremos por la primera.

Esto se hace automaticamente al leer de $2002 (PPU.STATUS en el framework)

Código: Seleccionar todo

bit PPU.STATUS


Ahora ya podemos seleccionar la dirección que queremos modificar escribiéndolo en el registro $2006 (PPU.ADDRESS en el framework) , por ejemplo, el primer color de la paleta ($3F00)

Código: Seleccionar todo

lda #3F
sta PPU.ADDRESS
lda #00
sta PPU.ADDRESS


Para escribir en la dirección que hemos seleccionado, simplemente escribimos en $2007 (PPU.IO en el framework)

Código: Seleccionar todo

lda #FE
sta PPU.IO


Automáticamente la dirección de la PPU aumentara (1 o 32 dependiendo del valor del registro $2000 (mas adelante explicare estos registros de control)), de modo que no tenemos que seleccionar la dirección cada vez que escribamos un byte si escribimos una secuencia de bytes, por ejemplo, la paleta de colores.
Si queremos esta memoria también se puede leer en lugar de escribir siguiendo el mismo método, colocando la dirección y después haciendo la lectura de PPU.IO

Código: Seleccionar todo

lda PPU.IO


También se incrementa la dirección al hacer una lectura.

Es muy importante tener en cuenta que SOLO se puede escribir en la PPU o en el VBLANK (retrazo vertical, cuando se dispara la interrupción NMI si esta activada) o en el HBLANK (retrazo horizontal, difícil de sincronizar sin un mapper con interrupciones) o cuando el video esta desactivado, como en el caso de la demo, que se escribe todo al inicio.
Sino conseguiremos glitches varios de vídeo.

En el caso de mi framework hay varias operaciones que se pueden poner en cola para ejecutarse automaticamente en la nmi, como por ejemplo la paleta de colores con este codigo copiaria el buffer 0 a la paleta 0 de sprites:

Código: Seleccionar todo

int_queueNmiEventStart(#EFFECT.SET_PALENTRY)
int_queueNmiEventParam(#0)
int_queueNmiEventParam(#SPRITE_PALETTES.PAL_0)
int_queueNmiEventEnd()


Cuando termine de explicar los básicos de la NES (ya no queda tanto, que con el sonido no me voy a meter :p), intentare explicar como usar el framework para evitarse muchos problemas a la hora de programar para NES.


Scrolling
El scroll, como la mayoria de operaciones que afectan al dibujado, ha de ponerse cuando estas en HBLANK (aunque aqui no se hace de la misma forma, esto ya se explicara mas adelante si llegamos al MMC3), VBLANK o con el video desactivado.

Para setear el scroll en el framework, solo hay que modificar las variables scrX y scrY y automaticamente la rutina de la NMI lo seteara al final de la misma.

Como se haría si lo quisiéramos hacer manualmente?
Lo primero es hacer una lectura en $2002 (PPU.STATUS en el framework) para reiniciar el latch interno de la NES.

Código: Seleccionar todo

bit PPU.STATUS


Después escribimos los valores de scroll en $2005 (PPU.BG_SCROLL en el framework) (X y luego Y) por ejemplo (5, 10)

Código: Seleccionar todo

lda #5
sta PPU.BG_SCROLL
lda #10
sta PPU.BG_SCROLL


Y finalmente reescribimos el valor de $2001 (PPU.CNT0 en el framework) que tengamos en nuestro buffer del registro (para elegir cual es la Name Table a partir de la cual se calcula el scroll)

Código: Seleccionar todo

lda buffer
sta PPU.CNT0


Sprites
Existen 2 maneras de modificar los sprites en la NES, la primera es accediendo a la memoria de sprites con los registros $2003 (para seleccionar el byte a leer/escribir) y $2004 (para leer /escribir el valor seleccionado), la segunda, que es la usada por el 99% de los juegos, es utilizar un buffer en RAM de la memoria de sprites y utilizar la copia por DMA de la que dispone la NES para copiarlo a la memoria de video en la PPU, este metodo lo explicare mas adelante.

La memoria de sprites ocupa 256bytes, 64 sprites, 4 bytes cada uno. Estos 4 bytes se utilizan para lo siguiente:
Byte 0: Posicion Y-1 del sprite, si se escribe un valor entre $EF y $FF el sprite no se muestra.
Byte 1: Tile a utilizar por el sprite, si los sprites estan en modo 8x16 se podria ver de la siguiente forma:

76543210
||||||||
|||||||+- Banco de tiles ($0000 o $1000)
+++++++-- Numero de tile del sprite superior (la parte inferior contiene el siguiente tile)


Byte 2: Atributos del sprite, definidos de la siguiente forma:

76543210
||||||||
||||||++- Paleta (4 a 7) del sprite
|||+++--- No hacen nada
||+------ Prioridad (0: delante del escenario; 1: detrás del escenario)
|+------- Sprite rotado horizontalmente
+-------- Sprite rotado verticalmente

Byte 3: Posicion X del sprite, los valores de $F9 a $FF no haran que el sprite se muestre parcialmente por la izquierda.

Copiando los sprites por DMA
La NES soporta la copia automatica de todos los valores de los sprites de la RAM a la memoria de SPRITES de manera automatica de la siguiente forma:
Primero seleccionamos que "banco" de memoria utilizaremos de la RAM, cada "banco" serian 256 posiciones de memoria:

Banco 0: $0000 - $00FF
Banco 1: $0100 - $01FF
Banco 2: $0200 - $02FF
Banco 3: $0300 - $03FF
etc

Este banco se informa al DMA escribiendo el valor del banco en $4014 (PPU.SPR_DMA en el framework)
Por ejemplo si nos reservamos el banco 3:

Código: Seleccionar todo

lda #03
sta PPU.SPR_DMA


Esta operación consume 513 ciclos de reloj (importante porque en VBLANK tenemos cerca de 2200 para hacer modificaciones de video y esta operacion consume 513) y hace la copia en el momento en que se escribe el valor en el registro.

En el caso del framework la "shadowOAM" o copia de los sprites se encuentra en $0200 ya que el banco 0 es la ZRAM y es mejor utilizarla para otros menesteres y el banco 1 contiene la pila.
Y se indica que se quiere hacer la copia en la NMI con la siguiente instruccion en el codigo:

Código: Seleccionar todo

int_setUpdateSprites()


De modo que si queremos modificar la posición del sprite 0, debemos modificar los valores de $0300 a $0303 con los valores que queramos que tenga de Y, tile, Atributos y X respectivamente y activar el update de los sprites en la NMI.

La prioridad de los sprites va del 0 (mas prioritario) al 63 (menos prioritario) de modo que el 0 sera el que se muestre al frente y el resto detras de este en orden.

Limite de 8 sprites
La NES solo dibuja 8 sprites en cada linea horizontal, si hay mas de 8 sprites en una linea los siguientes no se dibujan.
Para evitar este efecto, se intentó paliar con el "flickering" que todos conocemos de la NES, para que en lugar de haber sprites que no se muestran, se vayan rotando entre ellos y simular mas sprites por linea, el framework, si se utilizan sus metasprites (un sprite hecho de sprites mas pequeños) ya hace esta rotacion de los sprites por ti (si se activa esta funcion) pero esto ya se explicara cuando se haga algún ejemplo con sprites.


Los registros de control $2000, $2001 y status $2000 (en el framework)
Los registros y sirven para "configurar" la manera en la que la nes se comporta,
con una simple escritura en ellos cambiamos el comportamiento de la misma, de la siguiente forma:
$2000 (PPU.CNT1 en el framework):

76543210
||||||||
||||||++- Direccion base de la Nametable (para el scroll)
|||||| (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
|||||+--- Incremento de la direccion de VRAM al leer/escribir desde la CPU
||||| (0: incrementar 1; 1: incrementar 32)
||||+---- Pattern table para los sprites
|||| (0: $0000; 1: $1000; se ignora en el modo de 8x16)
|||+------ Pattern table para el background (0: $0000; 1: $1000)
||+------- Tamaño de los sprites (0: 8x8; 1: 8x16)
|+-------- Seleccion de PPU master/esclava (no tiene efecto en la NES)
+--------- Generar una NMI al principio del VBLANK (0: no; 1: si)

Los bits 0 y 1 tambien se pueden ver como 2 bits extras de las coordenadas de scroll de la siguiente forma:

76543210
||
|+- 1: Añade 256 a la posicion X del scroll
+-- 1: Añade 240 a la posicion Y del scroll

En el framework, con tal de usar un buffer de este registro y ademas poder hacer otras operaciones facilmente,
se dispone de las siguientes funciones:

Código: Seleccionar todo

       vid_getPPU_CTRL_1()
       vid_setPPU_CTRL_1(valor)
       vid_enablePPU_CTRL_1(mascara)
       vid_disablePPU_CTRL_1(mascara)


Y estas constantes para hacer mas facil el seteo de propiedaes:

Código: Seleccionar todo

       CTRL_1.BCKGRND_TABLE_0
       CTRL_1.BCKGRND_TABLE_1
       CTRL_1.SPRITE_TABLE_0
       CTRL_1.SPRITE_TABLE_1
       CTRL_1.NAME_TABLE_0
       CTRL_1.NAME_TABLE_1
       CTRL_1.NAME_TABLE_2
       CTRL_1.NAME_TABLE_3
       CTRL_1.NMI
       CTRL_1.SPRITE8x16
       CTRL_1.ADDRINC32


De modo que se puede hacer una llamada tal que:

Código: Seleccionar todo

       //Sprites tabla 1 y background tabla 0
       vid_setPPU_CTRL_1(
           #(CTRL_1.BCKGRND_TABLE_0|CTRL_1.SPRITE_TABLE_1))


$2001 (PPU.CNT2 en el framework):

76543210
||||||||
|||||||+- Escala de grises (0: color normal; 1: display monocromo)
||||||+-- 1: Mostrar background en los 8 pixels a la izquierda de la pantalla; 0: ocultar
|||||+--- 1: Mostrar sprites en los 8 pixels a la izquierda de la pantalla; 0: ocultar
||||+---- 1: Mostrar background
|||+----- 1: Mostrar sprites
||+------ Intensificar rojo (y oscurecer el resto)
|+------- Intensificar verde (y oscurecer el resto)
+-------- Intensificar azul (y oscurecer el resto)

En el framework, con tal de usar un buffer de este registro y ademas poder hacer otras operaciones facilmente,
se dispone de las siguientes funciones:

Código: Seleccionar todo

       vid_getPPU_CTRL_2()
       vid_setPPU_CTRL_2(valor)
       vid_enablePPU_CTRL_2(mascara)
       vid_disablePPU_CTRL_2(mascara)


Y de las constantes:

Código: Seleccionar todo

       CTRL_2.GREEN_BCKGRND
       CTRL_2.BLUE_BCKGRND
       CTRL_2.RED_BCKGRND
       CTRL_2.SPRITES_NOCLIP
       CTRL_2.BCKGRND_NOCLIP
       CTRL_2.DISPLAY_MONO
       CTRL_2.SPRITES_VISIBLE
       CTRL_2.BCKGRND_VISIBLE


De modo que se puede hacer una llamada tal que:

Código: Seleccionar todo

       //Sprites y background visibles
       vid_setPPU_CTRL_2(
           #(CTRL_2.SPRITES_VISIBLE|CTRL_2.BCKGRND_VISIBLE))
       
       //Activar display en monocromo
       vid_enablePPU_CTRL_2(#CTRL_2.DISPLAY_MONO)


El registro $2002 (PPU.STATUS en el framework) sirve para leer el estado de algunos flags de la NES, tal que:

76543210
||||||||
|||+++++- Bits menos significativos del ultimo valor escrito a un registro de la PPU
||| (due to register not being updated for this address)
||+------ Sprite overflow. Activo cuando hay mas de 8 sprites en una scanline,
|| parece que el comportamiento exacto es algo mas complicado, no se suele usar.
|+------- Sprite 0 Hit. Activo cuando un pixel no transparente del sprite 0 se sobrepone
| con un pixel que no es del fondo del escenario, este bit se usa para partir
| la pantalla en 2 secciones por ejemplo en Super Mario Bros. la puntuacion y el escenario.
| (ya habra algun tutorial sobre esto porque tiene miga)
+-------- VBLANK iniciado (0: no en VBLANK; 1: en VBLANK)

Hay que tener en cuenta que la lectura de $2002 tambien reinicia el latch interno de scrolling y el de la direccion de VRAM a la que escribimos y que el bit 7 (el de VBLANK) se pone a 0 una vez leido de modo que hay que guardarlos en algun buffer si se quiere usar el mismo valor varias veces.
En el framework disponemos de la funcion:

Código: Seleccionar todo

vid_getPPU_STAT()


Y de las constantes

Código: Seleccionar todo

    VBLANK_PERIOD

    SPRITE_0_HIT


Para hacer comprobaciones:

Código: Seleccionar todo

        vid_getPPU_STAT()
        and #SPRITE_0_HIT

    if(true) {

        //Hacer algo

    }


Lectura de los mandos
La lectura de los mandos se hace secuencialmente mediante lecturas a los registrso $4016 y $4017 (puertos 1 y 2)
obteniendo un bit que indica pulsado o no pulsado en el siguiente orden:

A B SELECT START ARRIBA ABAJO IZQUIERDA DERECHA

Antes de leer los pads hay que reiniciar el buffer interno para que capture el valor actual,
eso se hace escribiendo al puerto correspondiente 1 y luego 0, de la siguiente forma (para el pad 1 por ejemplo):

Código: Seleccionar todo

ldx    #01
stx    $4016
dex
stx    $4016


Y luego la lectura del estado actual seria algo tal que asi:

Código: Seleccionar todo

ldx #08
read: lda    $4016
lsr    a
rol    buttons
dex
bne    read


La lectura de pads la dejaremos aqui ya que con el framework esta totalmente cubierta:
Actualizar el estado de los pads (1 y 2):

Código: Seleccionar todo

inp_update(#(PORT_1|PORT_2))


Hacer comprobaciones con los pads:

Código: Seleccionar todo

//Si acabamos de pulsar el boton
   lda pads[CONTROLLER.PLAYER1].pressed
   and #BUTTON.A
   if(true) {
       //Hacer algo
   }
   
   //Si es por lo menos el segundo frame en el que lo tenemos pulsado
   lda pads[CONTROLLER.PLAYER1].mantained
   and #BUTTON.A
   if(true) {
       //Hacer algo
   }
   
   //Si acabamos de soltar el boton
   lda pads[CONTROLLER.PLAYER1].released
   and #BUTTON.A
   if(true) {
       //Hacer algo
   }



Mappers
La NES por si sola, sin el uso de los mappers o chips de apoyo de la mayoria de los cartuchos, no seria capaz de tener juegos de mas de 32KB de datos y 8KB de video, esta configuracion más simple es la que se denomina NROM.

En el framework voy a dar soporte a los principales mappers (concentran mas del 90% de los juegos) de la NES, estos son:
NROM, CNROM, UXROM, MMC1 y MMC3

El mapper NROM
Maximo: ROM: 32KB, CHRROM: 8KB
El "mapper" NROM o "no mapper" no lo comentare mas que lo que he dicho arriba, no tiene ninguna caracteristica extra.
Ademas el mirroring de las nametables esta fijo en la placa y no se puede cambiar desde el programa.
Juegos que lo utilizan:
Arkanoid, Balloon Fight, Battle City, Bomberman, Donkey Kong, Excitebike, Ice Climber, Kung-fu, Pac-Man, Super Mario Bros, etc

El mapper CNROM
Maximo: ROM: 32KB, CHRROM: 32KB
Este mapper permite tener mas de 1 "pagina" de graficos, en concreto 4. Podemos cambiar entre las 4 paginas con un simple comando del framework:

Código: Seleccionar todo

 lda #pageNum
   map_setCHR8KBank_a()


Y cambiaremos los graficos a la pagina pageNum.
Ademas el mirroring de las nametables esta fijo en la placa y no se puede cambiar desde el programa.
Juegos que usan este mapper: Adventure Island, City Connection, Dragon Quest, Goonies, Gradius, Paperboy, Solomon's Key, Twinbee, etc

El mapper UXROM
Maximo: ROM: 256KB, CHRROM: 0KB (8KB de CHRRAM para cargar los graficos)
En el caso del mapper UXROM ya no se usan graficos en CHRROM y hay que cargarlos en la CHRRAM antes de usarlos, este mapper no tiene mas que una pagina de graficos pero que podemos cargar a nuestro antojo los tiles que necesitemos (aunque es un proceso lento, mientras se esta jugando no se pueden cargar mas de 4-8 tiles por frame)
La ventaja de este mapper es que permite hacer bankswitching de las partes de la rom que vemos, la NES tiene 32KB de codigo visibles, que dividiremos en 2 bancos, el bajo, de $8000 a $BFFF y el alto, de $C000 a $FFFF.
Este mapper permite que en el banco bajo podamos hacer que la NES vea el trozo que nosotros queramos de nuestra ROM, por ejemplo si tenemos una ROM de 256KB, esto son 16 bancos de memoria, podemos decidir que banco se ve en $8000-$BFFF con la siguiente instrucción:

Código: Seleccionar todo

 ldx #0
   lda #pageNum
   map_setPRG16KBank_x_a()


En el banco alto siempre se ven los ultimos 16KB de nuestra ROM.
Ademas el mirroring de las nametables esta fijo en la placa y no se puede cambiar desde el programa.
Juegos que usan este mapper: 1943, Blades of Steel, Bomberman 2, Castlevania, Contra, Ducktales/2, Final Fantasy 2, Ghosts N' Goblins, Goonies 2, Mega Man, Metal Gear, Prince of Persia, Probotector, Rainbow Island, Renegade, Robowarrior, Rush N' Attack, Rygar, etc


Primera demo: cambiando el color de fondo
Lo mejor es no mezclar esta version con la anterior del framework ya que tiene varios cambios, ire actualizandolo a medida que el tutorial avance intentando no romper nada, pero en esta version descomprimir en otra carpeta.

http://www.mediafire.com/?bi6c3501zpvs9fs

main.as del proyecto backgroundColor:

Código: Seleccionar todo

interrupt.start main()
{   
   //Inicializamos el sistema   
   nes_init(#0)
   
   //Paramos el video
   vid_stop()
   
   //El background usara la tabla 0, los sprites la 1
   vid_enablePPU_CTRL_1(#(CTRL_1.BCKGRND_TABLE_0|CTRL_1.SPRITE_TABLE_1));
   //No activamos el clipping
   vid_enablePPU_CTRL_2(#CTRL_2.BCKGRND_NOCLIP);
   
   //Reseteamos el scroll
   lda #1
   sta scrX
   sta scrY
   
   //El modo de sprites sera de ignorar prioridad, para activar el OAM CYCLING     
   lda #1
   sta _vidIgnorePriority
   
   //Limpiamos los sprites
   vid_clearSprites()
   
   //Reiniciamos el video   
   vid_start();
   
   //Bucle principal, se ejecutara siempre
   forever {
      doFrame()
   }
}

inline doFrame() {
   //Leemos el joypad 1
   inp_update(#PORT_1)
   
   ldand(pads[CONTROLLER.PLAYER1].pressed, #BUTTON.UP)
   if(true) {
      ldx backColor
      inx
      cpx #$0D
      if(zero) {
         inx
      }
      stx backColor
   }
   
   ldand(pads[CONTROLLER.PLAYER1].pressed, #BUTTON.DOWN)
   if(true) {
      ldx backColor
      dex
      cpx #$0D
      if(zero) {
         dex
      }
      stx backColor
   }
   
   int_queueNmiEventStart(#EFFECT.SET_BACKCOLOR)
   int_queueNmiEventParam(backColor)
   int_queueNmiEventEnd()
     
   //Marcamos el final de la logica para que en la NMI se actualicen los cambios
   nes_setGameLogicCompleted()
   
   //Esperamos a que pase el VSYNC
   int_waitVbl()
}


Arriba y abajo cambian el color.


Segundo ejemplo: Carga de pantalla
Podemos diseñar pantallas estaticas con la herramienta NES Screen Tool y luego utilizarlas en nuestro programa.

ftp://ftp.untergrund.net/users/shiru/nesst.zip

Ejemplo 2:
http://www.mediafire.com/?8yye600uef5alot

Y el código, que es de lo mas simplón (solo pongo la parte que hace referencia a la carga):

Código: Seleccionar todo

//Cargamos la paleta titlepal
   nes_loadMemsrc(titlepal)
   vid_loadBackGrndPalette()
   
   //Copiamos los datos del titulo a la nametable 0
   vid_copyNametable0Vram(titledata);
   
   ldx #PALETTE_BLACK
   stx backColor


Con la primera linea se carga el puntero a memoria con la dirección de la paleta de colores del titulo.
Con la segunda se le dice que cargue como paleta de fondo la paleta a la que apunta el puntero.
La tercera linea copia de la rom a la nametable 0 toda la información sobre que tiles usar y los colores.
Los gráficos en si no hay que cargarlos (por ahora) porque estamos usando chrrom y ya vienen cargados.

Si queréis jugar un poco con los gráficos, abrid el archivo title.nam con el programa que hay en: \tools\gfx\nesst


Ejemplo 3: Sprites y Metasprites

La NES es capaz de manejar por hardware 64 sprites de 8x8 pixels o de 8x16 pixels. Con un solo sprite en la mayoria de juegos no es insuficiente para representar un objeto del mismo, por eso utilizaremos el concepto de Metasprite.

Un Metasprit ees un conjunto de sprites secuenciales que utilizan tiles tambien secuenciales en memoria para generar un sprite mas grande, como por ejemplo en Mario Bros, el personaje principal es un Metasprite de 2x2 y cuando esta en Super Mario es un Metasprite de 2x4

En este tutorial vamos a utilizar precisamente eso, el MetaSprite de SuperMario y moverlos un poco por la pantalla utilizando los MetaSprites tal y como estan en el framework.

Mas adelante, si consigo hacer un sistema de objetos usable, se intentara otro tutorial pero con objetos, que son conjuntos de MetaSprites (con diferentes paletas, tamaños) y la idea es que estos objetos puedan tener velocidad, aceleracion, colisiones...

Pero ahora con lo básico:
http://www.mediafire.com/?211c9s2aefcsj67
(solo incluyo las carpetas del framework y del proyecto, pegar en la carpeta donde tengais el framework)
No dejes de visitar www.RetroNES.net la mejor pagina sobre la NES 8 bits de Nintendo, ¡Y en español!

Avatar de Usuario
Diskover
##El Jefe##
Mensajes: 487
Registrado: Lun, 27 Oct 2003, 20:59
Ubicación: Planeta Tierra
Contactar:

Re: Tutorial de programacion para NES - Básico

Mensaje por Diskover » Mié, 23 Nov 2011, 22:31

Y bueno, os muestro un ejemplo de cosas que he conseguido hacer yo mismo:

Ejemplo 1: Imagen estática de Zelda 3.
- Elegí una pantalla del videojuego The Legend of Zelda: A link to the past de SNES para pasarla a la NES y ver que tal quedaría dadas sus limitaciones. Para ello hice una captura del juego y mediante la herramienta NES Screen Tool recree lo máximo posible los objetos, tiles, etc... que aparecían en la NES.
- Hay que tener en cuenta que solo podemos jugar con 4 paletas de 4 colores, uno de ellos compartido y que se usa como color predominante, fondo, etc... Ademas, la pantalla realmente esta dividida por sprites de 2x2 tiles y tienen que compartir la misma paleta, por lo cual se nos complica mucho mas las cosas. Podéis comprobarlo vosotros mismos.
- Os dejo la muestra y la rom resultante. Como está creada bajo el codigo de uno de los primero ejemplos del tutorial, se conserva el efecto de aumentar o bajar el brillo usando la cruceta arriba o abajo.

Imagen Imagen

ROM: http://www.elotrolado.net/download/file.php?id=71254


Ejemplo 2: Imagen estática de SMW
- Este ejemplo es igual que el anterior pero con el juego Super Mario World de SNES
- Fijaos en el detalle de los límites de la NES. Mientras en la SNES podemos jugar con hasta 5 planos diferentes, en la NES solo hay dos: El fondo y los sprites principales. Representar el fondo sin consumir toda la tabla de tiles que nos deja el banco de NES es complicado, por eso muchas en muchas ocasiones prefiero hacer algo parecido al original y no igual. Que de "el pego" ;-)

Imagen Imagen

ROM: http://www.elotrolado.net/download/file.php?id=71262

Imagen Imagen


Ejemplo 3: Imagen estática con metaSprite en movimiento
- Aquí la cosa se pone mas interesante. Volvemos al The Legend of Zelda: A link to the past he incluimos un sprite de Link muy rudimentario. Los sprites solo pueden llevar una paleta de 4 colores y encima uno de ellos tiene que ser transparente para que se vea bien en pantalla. Así pues, yo aquí he jugado con el transparente + negro + verde + marrón.
- Para ello he hecho uso de los metaSprites que hablamos en el tutorial.

Imagen

ROM: http://www.elotrolado.net/download/file.php?id=71440


Ejemplo 4: Jugando con los metaSprites: Dos en uno.
- Dada la limitación de colores por sprite (3 mas transparente) he hecho uso de un viejo truco que se veía en otro juegos de NES para que los sprites, sobre todo de los protagonistas, parezca que tienen mas colores. Para ello he usado dos metaSprites encajándolos uno encima del otro y ocupando las partes sin colorear de uno por los del otro. Es algo complejo dado que tienes que tener muy claro con la herramienta de creación de tiles que partes ocupara uno y cuales el otro para que cuando encajen, queden perfectos.
- Este truco se uso por ejemplo para poner el blanco de los ojos a Megaman o a Mario en Super Mario Bros. 2 entre otros. Yo aprovecho que puedo usar 3+1 para darle mas vida al sprite del Link, añadiéndole el color purpura y naranja al gorro y a la cara. El resultado es muy aceptable y mejor que el original.
- Lo malo es que consumes mas recursos de la NES pues estas moviendo mas metaSprites a la vez, no solo uno. Ademas, es muy complejo animar todos a la vez y no volverte loco. Si vais ha hacer esto, planteároslo bien desde el principio como hacerlo por que os podéis volver tarumbas.
- También he hecho que Link solo se mueva cuando pulsamos la cruceta.

Imagen

Imagen

ROM: http://www.elotrolado.net/download/file.php?id=71495
No dejes de visitar www.RetroNES.net la mejor pagina sobre la NES 8 bits de Nintendo, ¡Y en español!

Avatar de Usuario
sewave
Nintender@
Mensajes: 100
Registrado: Dom, 15 May 2005, 22:28
Contactar:

Re: Tutorial de programacion para NES - Básico

Mensaje por sewave » Mié, 23 Nov 2011, 22:47

Jeje, estupendo, a ver si aqui tenemos mas exito :)

Avatar de Usuario
Diskover
##El Jefe##
Mensajes: 487
Registrado: Lun, 27 Oct 2003, 20:59
Ubicación: Planeta Tierra
Contactar:

Re: Tutorial de programacion para NES - Básico

Mensaje por Diskover » Mié, 23 Nov 2011, 23:42

Bueno, seguimos intentando cosas. Esta vez con el Super Mario World:

- He intentado no complicarme tanto y usar solo dos metaSprites para dibujar a Mario en vez del cacao que tenia montado con Link.
- Mario tiene tres frames: Quieto, iniciando andar y andando. Animándolo parece que anda a trote. Ademas he añadido que solo lo haga hacia la izquierda y hacia la derecha y se anime solo cuando pulsamos cualquiera de estas direcciones.

Objetivos:
- Que cuando deje de pulsar se ponga en el frame de quieto. No se si habra alguna sentencia contraria a mantained que interprete esto.
- Que cuando pulse B la velocidad de desplazamiento sea mas rápida y luego vuelva a la normalidad cuando deje de pulsar.

Por ahora solo eso. Dejo la primera rom de demostración y una captura.

Imagen

ROM a: http://www.megaupload.com/?d=ZYKTJ1T8
No dejes de visitar www.RetroNES.net la mejor pagina sobre la NES 8 bits de Nintendo, ¡Y en español!

Avatar de Usuario
sewave
Nintender@
Mensajes: 100
Registrado: Dom, 15 May 2005, 22:28
Contactar:

Re: Tutorial de programacion para NES - Básico

Mensaje por sewave » Mié, 23 Nov 2011, 23:52

Diskover escribió:Bueno, seguimos intentando cosas. Esta vez con el Super Mario World:

- He intentado no complicarme tanto y usar solo dos metaSprites para dibujar a Mario en vez del cacao que tenia montado con Link.
- Mario tiene tres frames: Quieto, iniciando andar y andando. Animándolo parece que anda a trote. Ademas he añadido que solo lo haga hacia la izquierda y hacia la derecha y se anime solo cuando pulsamos cualquiera de estas direcciones.

Objetivos:
- Que cuando deje de pulsar se ponga en el frame de quieto. No se si habra alguna sentencia contraria a mantained que interprete esto.
- Que cuando pulse B la velocidad de desplazamiento sea mas rápida y luego vuelva a la normalidad cuando deje de pulsar.

Por ahora solo eso. Dejo la primera rom de demostración y una captura.

Imagen

ROM a: http://www.megaupload.com/?d=ZYKTJ1T8

Para el primer objetivo, released ;)
Y para el segundo tambien te sirve released :p

Avatar de Usuario
Jazzid
NES
Mensajes: 395
Registrado: Dom, 15 Feb 2004, 21:47
Ubicación: Tambien disponible en supositorios

Re: Tutorial de programacion para NES - Básico

Mensaje por Jazzid » Jue, 24 Nov 2011, 00:06

Enorme. Espero que pronto empieces a meterte con el sonido!.

Avatar de Usuario
Diskover
##El Jefe##
Mensajes: 487
Registrado: Lun, 27 Oct 2003, 20:59
Ubicación: Planeta Tierra
Contactar:

Re: Tutorial de programacion para NES - Básico

Mensaje por Diskover » Jue, 24 Nov 2011, 00:14

Ok, me ha servido lo de released para poner a Mario en su posición inicial cuando dejo de pulsar cualquiera de las direcciones.

Para lo siguiente también me vale, pero con peros:

He creado una variable llamada velocidad. Cuando anda normal velocidad se pone a 8 y cuando pulso B se pone a 2. Esto me ha servido para hacer la animación normal o mas rápida dependiendo de si esta o no pulsado el botón B, pero no consigo averiguar como aumentar la velocidad de desplazamiento ni por que esta puesta por defecto la que está.

Una ayuda please :-(
No dejes de visitar www.RetroNES.net la mejor pagina sobre la NES 8 bits de Nintendo, ¡Y en español!

ryumishima44
Kirby Star
Mensajes: 3601
Registrado: Sab, 23 Feb 2008, 05:11
Ubicación: Jugando Castlevania
Contactar:

Re: Tutorial de programacion para NES - Básico

Mensaje por ryumishima44 » Jue, 24 Nov 2011, 05:05

He estado codeandome con la progra hace algun tiempo, pero nunca he tocado NESHLA... pero igual unas preguntillas
¿El lenguaje no maneja variables globales?, ¿no está asignado la velocidad de desplazamiento a una estas? ¿No sabes cual es el parámetro de acceso a la velocidad de scroll?

Perdon si sueno perdido, es que tampoco soy un hacha, solo quiero ayudar
Imagen
Visita ryukenden retro gaming: http://www.ryukenden.blogspot.com
Imagen

Avatar de Usuario
sewave
Nintender@
Mensajes: 100
Registrado: Dom, 15 May 2005, 22:28
Contactar:

Re: Tutorial de programacion para NES - Básico

Mensaje por sewave » Jue, 24 Nov 2011, 08:56

Diskover escribió:Ok, me ha servido lo de released para poner a Mario en su posición inicial cuando dejo de pulsar cualquiera de las direcciones.

Para lo siguiente también me vale, pero con peros:

He creado una variable llamada velocidad. Cuando anda normal velocidad se pone a 8 y cuando pulso B se pone a 2. Esto me ha servido para hacer la animación normal o mas rápida dependiendo de si esta o no pulsado el botón B, pero no consigo averiguar como aumentar la velocidad de desplazamiento ni por que esta puesta por defecto la que está.

Una ayuda please :-(

En vez de guardar la velocidad en una variable, guardate en la variable si mario corre o no, si corre, pones la velocidad de animacion a 2, sino a 8, luego donde incrementas la posicion de los metasprites, en lugar de hacer

Código: Seleccionar todo

inc posicion

tendras que hacer:

Código: Seleccionar todo

lda posicionX
clc
adc #numerodepixelsquequieresinrementar
sta posicionX

y para restar:

Código: Seleccionar todo

lda posicionX
sec
sbc #numerodepixelsquequieresinrementar
sta posicionX


ryumishima44 escribió:He estado codeandome con la progra hace algun tiempo, pero nunca he tocado NESHLA... pero igual unas preguntillas
¿El lenguaje no maneja variables globales?, ¿no está asignado la velocidad de desplazamiento a una estas? ¿No sabes cual es el parámetro de acceso a la velocidad de scroll?

Perdon si sueno perdido, es que tampoco soy un hacha, solo quiero ayudar

Si, las variables son todas globales.

Avatar de Usuario
Diskover
##El Jefe##
Mensajes: 487
Registrado: Lun, 27 Oct 2003, 20:59
Ubicación: Planeta Tierra
Contactar:

Re: Tutorial de programacion para NES - Básico

Mensaje por Diskover » Jue, 24 Nov 2011, 11:22

sewave escribió:luego donde incrementas la posicion de los metasprites, en lugar de hacer

Código: Seleccionar todo

inc posicion

tendras que hacer:

Código: Seleccionar todo

lda posicionX
clc
adc #numerodepixelsquequieresinrementar
sta posicionX

y para restar:

Código: Seleccionar todo

lda posicionX
sec
sbc #numerodepixelsquequieresinrementar
sta posicionX


Si, pero al añadir mas código el compilador me da lo de fallo de rango. Creo que no me deja meter mas instruccion en el if

Otra cosa ¿se puede meter un if dentro de otro if?

Te muestro el código de lo que estoy haciendo para que veas:

Código: Seleccionar todo

todo lo de arriba igual
__________________

   lda #8
   sta velocidad

   //Bucle principal, se ejecutara siempre
   forever {
      doFrame()
   }
}

inline doFrame() {
   //Leemos el joypad 1
   inp_update(#PORT_1)

   ldand(pads[CONTROLLER.PLAYER1].mantained, #BUTTON.B)
   if(true){
      lda #2
      sta velocidad      
   }
   
   ldand(pads[CONTROLLER.PLAYER1].released, #BUTTON.B)
   if(true) {
      lda #8
      sta velocidad
   }

   ldand(pads[CONTROLLER.PLAYER1].mantained, #BUTTON.LEFT)
   if(true) {

      inc frames
   
      //Animacion andando de mario
      lda frames
      cmp velocidad
      if(greater) {
         lda metaSprites[0].firstTile
         switch(reg.a) {
         case #0 {
            lda #$7
            lda #16
         }
         case #16 {
            lda #$23
            lda #32
         }
         case #32 {
            lda #$39
            lda #0
         }
         
      }
      sta metaSprites[0].firstTile
      
      lda #0
      sta frames
   
      lda metaSprites[1].firstTile
      switch(reg.a) {
         case #8 {
            lda #$15
            lda #24
         }
         case #24 {
            lda #$31
            lda #40
         }
         case #40 {
            lda #$47
            lda #8
         }

      }
      sta metaSprites[1].firstTile
      
      lda #0
      sta frames      
      }

      lda metaSprites[0].attributes
      ora #SPR_ATTR.H_FLIP
      sta metaSprites[0].attributes
      dec metaSprites[0].x
      
      lda metaSprites[1].attributes
      ora #SPR_ATTR.H_FLIP
      sta metaSprites[1].attributes
      dec metaSprites[1].x
      
   }

   
   
   ldand(pads[CONTROLLER.PLAYER1].mantained, #BUTTON.RIGHT)
   if(true) {

      inc frames
   
      //Animacion andando de mario
      lda frames
      cmp velocidad
      if(greater) {
         lda metaSprites[0].firstTile
         switch(reg.a) {
         case #0 {
            lda #$7
            lda #16
         }
         case #16 {
            lda #$23
            lda #32
         }
         case #32 {
            lda #$39
            lda #0
         }         
         }
         sta metaSprites[0].firstTile      
         lda #0
         sta frames
   
         lda metaSprites[1].firstTile
         switch(reg.a) {
            case #8 {
               lda #$15
               lda #24
            }
            case #24 {
               lda #$31
               lda #40
            }
            case #40 {
               lda #$47
               lda #8
            }
         }
         sta metaSprites[1].firstTile      
         lda #0
         sta frames      
      }

      lda #SPR_ATTR.H_FLIP ^ 0xFF
      and metaSprites[0].attributes
      sta metaSprites[0].attributes
      inc metaSprites[0].x
      
      lda #SPR_ATTR.H_FLIP ^ 0xFF
      and metaSprites[1].attributes
      sta metaSprites[1].attributes
      inc metaSprites[1].x
   }

   ldand(pads[CONTROLLER.PLAYER1].released, #BUTTON.RIGHT)
   if(true) {
      lda #0
      sta metaSprites[0].firstTile
      lda #8
      sta metaSprites[1].firstTile
   }

   ldand(pads[CONTROLLER.PLAYER1].released, #BUTTON.LEFT)
   if(true) {
      lda #0
      sta metaSprites[0].firstTile
      lda #8
      sta metaSprites[1].firstTile
   }

______________________
todo lo de abajo igual
No dejes de visitar www.RetroNES.net la mejor pagina sobre la NES 8 bits de Nintendo, ¡Y en español!

Responder