1. TUTORIAL SNES:
USAR SPRITES
Por David Senabre, Junio 2005
INTRODUCCIÓN Y EL POR QUÉ DE ESTE TUTORIAL.
En primer lugar, escribo este tutorial porque no he visto ningún
documento hasta la fecha que explique adecuadamente como usar sprites
en la SNES, ni en español, ni en ingles. Y a mi me ha dado bastante
dolores de cabeza lograr usarlos. Hay varios documentos técnicos sobre
la SNES muy buenos por internet, como los de Yoshi, pero por ejemplo,
este hombre explica a menudo las cosas de una forma muy poco clara. En
definitiva, hay temas, como el de los sprites, donde la información
está esparcida y a veces es oscura y ambigua.
Mi objetivo es explicar con detalle y suficiente claridad, el uso
sencillo de sprites en la SNES, desde cero y ordenadamente.
¿A QUIÉN VA DIRIGIDO?
A todo aquel que quiera usar sprites en la SNES, y por supuesto que
sepa programar y tenga nociones de ensamblador, aunque no sea de
ensamblador de la SNES. También se da por sabido conceptos como tile,
bitplanes, o WRAM ;)
Ah, y también saber qué es un registro y como usarlos.
(ver otros tutoriales míos).
¿QUÉ SON LOS SPRITES?
Creo que esto sobra, pero bueno, hagamos las cosas bien. En un juego,
por una parte, están los escenarios, las plataformas, etc. Que forman
parte de los planos de scroll, constituidos por tiles, y que sirven
para representar los niveles, los mapas, o lo que sea, en definitiva,
el mundo que se representa en el juego.
Y por otra parte están los sprites, que son los objetos móviles que
“viven” en ese mundo, tanto los enemigos, como el propio personaje que
controla el jugador, o los ítems. En definitiva, los sprites son los
objetos que interactúan en los juegos.
Por lo tanto es imprescindible saber usarlos, a no ser que queramos
limitarnos a programar demos con fondos, imágenes, texto, y sin
acción. Para quien tenga experiencia en programación de la NES,
resultará sencillo usarlos en la SNES, aunque aquí la cosa se complica
mucho. Yo empezaré a explicarlo todo suponiendo que el lector no tiene
esta experiencia previa.
LA TABLA DE SPRITES (OAM)
Los sprites en SNES se llaman objetos o OAM.
Para manejarlos, la SNES tiene una zona de memoria llamada tabla OAM
donde guarda información sobre todos los sprites. En esta tabla caben
128 sprites, y por lo tanto la SNES puede manejar un máximo de 128
sprites a la vez, aunque seguramente en la practica se ralentice si se
usan tantos. Aún así esto supone una mejora, porque la NES sólo podía
manejar 64.
Lo que tenemos que hacer es introducir los datos de los sprites en
esta tabla. Cada sprite ocupa 4 bytes.
Byte 1 : xxxxxxxx Posición X del sprite en la pantalla (0-255)
Byte 2 : yyyyyyyy Posición Y del sprite en la pantalla (0-255)
Byte 3 : tttttttt Número de tile que representa al sprite
Byte 4 : vhoopppt
De este ultimo byte:
2. v : mirroring vertical
h : mirroring horizontal
o : bits de prioridad
p : bits de paleta
t : bit alto de número de tile
Los 2 primeros son auto explicativos.
Por ahora hablaré de los bits de paleta y de tile.
Con el byte 3 decimos que tile se mostrará como sprite. Podemos elegir
un tile desde 0 hasta 255. Pero el bit t del byte 4 también vale para
elegir el tile, y de hecho es el bit más significativo de esta
dirección, luego:
Bit t del byte 4 = 0
Byte 3 elige un tile de 0 hasta 255
Bit t del byte 4 = 1
Byte 3 elige un tile de 256 hasta 511
Luego diré como saber qué tile es el 127 o el 63.
Por ahora añadir que los bits p sirven para elegir la paleta del
sprite, de entre 8 (ya que tenemos 3 bits para ello).
PALETAS Y COLORES.
Recuerdo que la paleta de la SNES es una lista de colores que se
almacenan en una memoria de la SNES, la CG-RAM, que guarda todos los
colores que se pueden mostrar por pantalla en un momento dado, y que
se carga por el programador con los colores que se quiera. Estos
colores se reverencian por el orden que ocupan en esta lista, desde el
0, hasta el 255.
Los colores que se usan en los sprites son los que van del 128 en
adelante (es decir, los que vienen después de los primeros 128).
Los sprites usan 16 colores. Por lo tanto, si a un sprite le ponemos
paleta 0, los 16 colores que se usarán para él serán los que van del
128 al 143, y si escoges la paleta 1, los 16 siguientes, etc.
CARGAR LA TABLA DE SPRITES (OAM)
Bueno, ahora que ya sabemos esto, es el momento de aprender a meter
esta información en la tabla de sprites.
La tabla de sprites ocupa 512 bytes (4 bytes por 128 sprites), que
sirven para almacenar la información ya explicada, pero esta tabla al
final tiene otros 32 bytes que sirven para otras 2 cosas; para hacer
de noveno bit en la posición X de los sprites, y para elegir el tamaño
del sprite entre 2 posible. Pero de momento estos 32 bytes los
pondremos a cero. Con esto seleccionaremos el tamaño por defecto, y no
afectaremos a la posición X del sprite dada con antelación.
Esta tabla se carga usando los registros $2102, $2103, $2104.
Los dos primeros sirven para dar la dirección del sprite en la tabla,
y el tercero sirve para escribir en esa dirección el byte deseado.
Cuando inicialicemos la consola, en nuestra rutina de arranque, sería
bueno que llenáramos la tabla de sprites de ceros. Esto se puede hacer
muy fácilmente usando DMA.
La siguiente rutina sirve perfectamente. Como necesitamos llenar la
tabla con ceros, yo lo que hago es copiar a la WRAM el valor cero en
alguna posición de memoria, y luego copiar el contenido de esa
posición a la tabla OAM.
En este ejemplo usaré la dirección $0020. Se puede usar otra dirección
sin problemas. Fijarse que en el registro $4300 de DMA se selecciona
3. dirección fija (Fixed address), para que siempre se transfiera el
mismo byte, que es lo que nos interesa.
Las opciones que hay que usar para trasnferir a la tabla OAM usando
DMA son:
- CPU memory -> PPU
- Absolute addressing
- 1 address write twice, o 1 address write once
- Transferir al registro $2104
Y además, en esta ocasión, como es para llenar todo su contenido de un
mismo valor:
- Fixed address
No olvidar resetar la dirección de la tabla OAM antes de copiar.
;---------------
LDX #$0
STX $2102 ; empezar desde el comienzo de la tabla OAM
LDY #$0408 ; transferir al registro $2104
STY $4300 ; con dirección fija
LDY #$0020 ; desde la dirección $20
STY $4302 ; que almacena el valor cero
LDY #544 ; transferir 544 bytes para llenar la tabla
STY $4305
LDA #$7E ; seleccionar banco de origen,
STA $4304 ; el banco 7E, comienzo de la WRAM.
LDA #$01
STA $420B ;hacer DMA!
;---------------
Listo, ya tenemos la OAM llena de ceros, incluida la tabla de 32
bytes.
¿Y LOS TILES?
Hasta ahora todo va bien, pero aunque en la tabla OAM tengamos
nuestros sprites danzando, no veremos nada en la pantalla.
Para que todo esto funcione necesitamos almacenar en la VRAM los tiles
que usarán los sprites, y decirle a la SNES en qué posición de memoria
empiezan estos tiles. Estos tiles deben usar 4 bitplanes, para ofrecer
16 colores. Por lo tanto cada uno ocupará 32 bytes (tiles de 8x8 = 64
pixels, a 4 bits por píxel, 256 bits, que son 32 bytes). El tile 0 es
el primero, el tile 1, el que está en el offset 32, el tile 2, en el
offset 64, y así sucesivamente.
Procedamos a definir la dirección base de estos tiles. Para ello
necesitaremos el registro $2101, que me ha dado casi todos los
problemas que he tenido con sprites.
$2101: sssnnbbb
Los bits b definen la dirección base de comienzo en VRAM de los tiles
que usaremos en nuestros sprites. Esta dirección se da en bloques de
8k palabras (o sea, de 16Kb).
Por ello, si ponemos los bits b a 010 (2), estaremos dando la
dirección 2*8k palabras, $4000(w), o lo que es lo mismo, la dirección
$8000 en bytes de la VRAM, aunque es mejor acostumbrarse a usar las
direcciones en palabras de 16 bits cuando nos referimos a la VRAM, en
lugar de en bytes. Como la VRAM tiene una capacidad de 32k palabras
(64Kb), si la partimos en bloques de 8k palabras veremos que sólo hay
4 posibles posiciones donde colocar los tiles de los sprites.
4. En palabras, las posibles zonas de VRAM para sprite tiles son:
VRAM bits b
------------------------------------
| $0000-1FFF | 000 |
------------------------------------
| $2000-3FFF | 001 |
------------------------------------
| $4000-5FFF | 010 |
------------------------------------
| $6000-7FFF | 011 |
------------------------------------
Para qué sirve el bit más significativo lo ignoro. Por ahora, lo
considero siempre cero.
Los bits n sirven para seleccionar una dirección base distinta dentro
de la que hemos seleccionado. Cuando un tile tiene un indice 256 o
mayor, a la dirección base se le suma la seleccionada en los bits n,
que definen un desplazamiento en bloques de de 1k palabra.
Por ejemplo, si elegimos los bits b 010, seleccionamos el bloque
------------------------------------
| $4000-5FFF | 010 |
------------------------------------
Pero si hacemos los bits n 01, para tiles de indice mayor que 255, es
decir, los que ocupan posiciones a partir de $5000 (la mitad del
bloque), la dirección base pasará a ser:
$5000 + 400 = $5400 (se le suma un bloque de 1k palabra)
Y análogamente, para bits 10 y 11, tendremos:
$5000 + 800 = $5800 (se le suma dos bloquess de 1k palabra)
$5000 + 1000 = $6000 (se le suma tres bloques de 1k palabra)
Esta técnica es muy útil para crear sprites animados de 4 frames, de
una forma sencilla. Basta con colocar los tiles de esos sprites en
posiciones 256 y superiores, y variar los bits n cada par de frames.
Así, se irán eligiendo los tiles en posiciones separadas por 1k
palabra de forma ciclica.
ACTIVAR SPRITES
Ahora tenemos que definir el modo gráfico en el que vamos a trabajar,
activar los planos de scroll que vayamos a usar, y activar los
sprites.
En primer lugar definimos modo1, por ejemplo, que es uno de los más
sencillos para aprender. Este modo tiene disponible 3 planos de
scroll, el BG1, el BG2 y el BG3. Los dos primeros usan 16 colores
(tiles de 4 bitplanes), y el BG3 de 4 colores unicamente (tiles de 2
bitplanes). El registro para seleccionar modo es el $2105, en concreto
sus 3 bits menos significativos.
5. $2105: abcdefff
Los bits f son los bits de modo que acabo de decir.
Del bit a, al d, sirven para seleccionar el tamaño de los tiles de los
planos BG4 al BG1 (0=8x8, 1=16x16). Yo nunca he usado tiles que no
sean de 8x8 así que no daré más información.
El bit e sirve para dar máxima prioridad al BG3 en modo1. Por ahora no
lo usaremos tampoco.
;-----------------------
lda #$01 ;mode 1, 8x8 tiles (16 colores para BG1 + BG2 y 4 para BG3)
sta $2105
;-----------------------
Y para activar los planos de scroll que queramos, al igual que los
sprites, está el registro $212C
$212C: 000abcde
Los 3 bits altos no se usan.
El bit a se refiere a los sprites.
El bit b al BG4
El bit c al BG3
El bit d al BG2
El bit e al BG1
Un bit a 1 significa activado, a 0, desactivado.
;-----------------------
lda #$17 ;Activar todos los planos del modo 1, y sprites
sta $212C
;-----------------------
ACTUALIZAR LA TABLA OAM
Hemos aprendido a resetar la tabla de sprites. Ahora vamos a necesitar
llenar esta tabla con información correcta sobre cada sprite, de su
posición, tile y atributos. Esto lo haremos de la siguiente manera.
En primer lugar, designaremos una zona de memoria donde almacenaremos
nuestra tabla de forma temporal, y durante la rutina de refresco de
pantalla, copiaremos esta tabla temporal a la tabla OAM, usando DMA,
como antes.
Por ejemplo, dediquemos la zona de WRAM $0040-$25F a almacenar nuestra
tabla temporal.
;---------------
.DEFINE Sprites $40 ;544 bytes, $220
;---------------
Con el identificativo Sprites, designaremos el offset de inicio.
Ahora debemos ir copiando aquí la información que se transferirá a la
tabla OAM cada frame, en el mismo formato claro, y dejando los últimos
32 bytes a cero de momento. Los primeros 512 bytes estarán disponibles
para usar hasta 128 sprites. Por ejemplo, añadamos un sprite en la
posición X = 16, Y = 191, que use el tile $80, paleta cero, y sin
atributos de prioridad o mirroring. Este será nuestro sprite cero,
luego lo copiamos al inicio de nuestra tabla en WRAM, por ejemplo de
la siguiente forma:
6. ;---------------
LDY #$BF10
STY Sprites + 0
LDY #$0080
STY Sprites + 2
;---------------
Y si queremos un segundo sprite, a continuación.
;---------------
LDY #$B710
STY Sprites+4
LDY #$0081
STY Sprites+6
;---------------
Etc.
Lo que resta es volcar esta tabla temporal a la tabla OAM cada
refresco de pantalla, como hacemos con el resto de planos de scroll,
por ejemplo, y esto se hace fácilmente usando DMA, y una rutina casi
idéntica a la de inicialización de antes, sólo que ahora usaremos como
dirección origen la dirección de nuestra tabla temporal, y no se usará
dirección fija (Fixed address), sino autoincremento, para ir copiando
uno tras otro todos los bytes de la tabla temporal. Es decir,
seleccionar:
- CPU memory -> PPU
- Absolute addressing
- 1 address write twice, o 1 address write once
- Transferir al registro $2104
- Auto address inc/decrement.
- Automatic increment.
;---------------
LDX.w #$0
STX $2102 ; resetar la dirección de la tabla OAM
LDY #$0400 ; copiar al registro $2104 (OAM)
STY $4300 ; incrementar dirección
LDY #Sprites ; copiar desde el inicio de nuestra
STY $4302 ; tabla temporal
LDY #544
STY $4305 ; numero de bytes
LDA #$7E ; banco 7E, primer banco de la WRAM
STA $4304 ;
LDA #$01
STA $420B ; DMA!
;---------------
También se puede copiar manualmente, sin usar DMA. Es muy sencillo
PERO ojo, hay que usar un registro de 8 bits para la transferencia u
obtendrás resultados indeseables. Esta rutina funciona perfectamente,
y es equivalente a la anterior:
;---------------
LDX.w #$0
STX $2102 ; resetear dirección OAM
SEP #$20 ; el registro A debe estar configurado en 8 bits
LDY #$00
CopySpr:
LDA Sprites, Y
STA $2104
INY
CPY #544
7. BNE CopySpr
;---------------
Con esto doy por concluído el tutorial. Creo que con esta información
no será problema meter un par de sprites que revoloteén por ahí, y den
alegría a un frío plano de scroll ;)
Dejo para más adelante el uso de sprites de más de 8x8 pixels, y otras
cosillas más avanzadas.
CUADRO RESUMEN:
1º copiar los tiles que usarán los sprites a un dirección de VRAM.
Esta tiene que ser una de las siguientes.
VRAM bits b
------------------------------------
| $0000-1FFF | 000 |
------------------------------------
| $2000-3FFF | 001 |
------------------------------------
| $4000-5FFF | 010 |
------------------------------------
| $6000-7FFF | 011 |
------------------------------------
2º Establecer la dirección base de estos tiles en el registro $2101
3º Seleccionar modo gráfico en el registro $2105
4º Activar los sprites en el registro $212C
5º Limpiar la tabla OAM
6º Cada refresco de pantalla actualizar la tabla
Mucha suerte.