Lección 05 – Sprites

Aventuras en Megadrive: Sprites

 

DATOS TÉCNICOS

  • Los sprites se colocan en el plano de Sprites.
  • La posición en pantalla de un sprite viene dada en píxels y no en tiles.
  • El plano de sprites es un plano fijo, no puede tener scroll.
  • Podemos tener un máximo de 80 sprites en pantalla (modo 320px).
  • Podemos tener hasta 20 sprites por línea horizontal (modo 320px).
  • A igualdad prioridad, los sprites creados primero aparecen dibujados sobre los últimos.
  • No obstante podemos controlar el orden en el que se dibujan los sprites.
  • Los sprites se almacenan y construyen con tiles, igual que los fondos. Por tanto podemos usar tiles tanto para fondos como para sprites.
  • Un sprite puede ser construido con un solo tile (1×1)=(8×8 píxels), pero también con varios tiles, con un máx de (4×4) tiles=(32×32 píxels).
  • SGDK nos permite utilizar multi-sprites de hasta 16×16 tiles, sprites enormes.
  • Una imagen de un sprite debe ser, por tanto, divisible por 8.

 

CABECERA

La forma de cargar un sprite en VRAM y dibujarlo es muy parecida a lo que hemos hecho con los planos/fondos. Pero hay algunas diferencias.

Para tener acceso a las funciones de manejo de sprites del SGDK añade esta cabecera:

#include "sprite.h"

 

MOTOR DE SPRITES

Has de reservar memoria para guardar los sprites en la VRAM antes de usarlos. Para ello hay que inicializar el motor de sprites:

SPR_init(max_num_sprites, tamaño_en_vram, buffer_descompresión);

Recordemos que se guardan como tiles. Si no tenemos claro los parámetros de esta función, y de momento así será, escribiremos lo siguiente (equivale a usar los valores por defecto del SGDK):

SPR_init(0,0,0)

 

Antes de cargar un sprite, deberemos cargar su paleta (igual que hacíamos con los planos), o bien usar una ya cargada. Usaremos SPR_addSprite() para cargar los sprites en memoria. Tal y como hacemos con los planos, usaremos TILE_ATTR() para indicar la paleta, si lo mostramos invertido horizontal o verticalmente, etc.

Los sprites irán cambiando su posición e incluso su aspecto, pueden estar animados. Tal y como sucedía con los tiles de los fondos, el VDP de Megadrive mantiene los sprites frame tras frame, no los borra. Si un sprite cambia de posición o de animación, esto lo debemos gestionar en el lado del procesador (el famoso Motorola 68000), y una vez hecho, debemos usar la función SPR_update() para mandar los cambios al VDP:

...
while(TRUE)
{
  ...
  SPR_update();
  VDP_waitVSync();
}
...

 

Esta función se suele poner en el bucle principal, debe llamarse una sola vez por frame, normalmente justo antes de terminar el bucle principal.

 

Finalmente, para liberar la memoria de Sprites, debemos utilizar

SPR_end();

Normalmente esto se utiliza para cargarnos la reserva de memoria de sprites y a continuación hacemos una nueva reserva.

 

PELIGRO

Si ejecutamos SPR_end() sin volver a ejecutar un SPR_init(), al final se ejecutará un   SPR_update(), que machacará la memoria que no debe y llenará la pantalla de artefactos. Ojo con esto.

 

 

ARCHIVO RES

La forma de indicar al SGDK dónde están los sprites es similar, utilizaremos de nuevo un archivo RES aunque los parámetros son diferentes:

SPRITE nombre_variable "recurso.png" tiles_ancho tiles_alto FAST veloc_ani

nombre variable: variable que usaremos en el main.c
recurso.png: el nombre del archivo (opcional: poner la ruta si está en subcarpetas)
tiles_ancho y tiles_alto: indicaremos aquí cuantos tiles mide el sprite.
FAST: tipo de compresión, lo dejamos así de momento
veloc_ani: tiempo entre cada frame del sprite

Si el sprite son p.e. 32×16 pixeles, en [tiles_ancho] y [tiles_alto] pondremos 4 y 2 respectivamente ( 32/8 = 4  ,   16/8 = 2).

Para guardar las distintas animaciones de un personaje, normalmente se utilizan  spritesheets (hojas de sprites). Se trata de una imagen con todos los posibles sprites. Ya sea quieto, corriendo, atacando, muriendo… un ejemplo con Sonic

Captura9

En este caso [tiles_ancho] y [tiles_alto] serán respectivamente 6 y 6 (en la captura hay que contar los cuadritos, cada cuadrado es un tile).

Recomiendo usar Graphics Gale Portable / Custom-Grid / 8×8

Siguiendo con la explicación, todos los sprites de Sonic estarán en un cuadro 6×6, por tanto debemos usar el mismo tamaño para todos los sprites dentro de una spritesheet.

Captura10

En realidad, internamente, la MD está manejando más de un sprite, ya que el máximo se supone que es 4×4 tiles por cada sprite. El SGDK nos ahorra ese dolor de cabeza.

 

POSICIÓN DE UN SPRITE

La posición en pantalla de un sprite viene indicada por el punto sup-izq de dicho sprite.

Los pies de Sonic no están en esa posición, hay que tener esto en cuenta a la hora de situar el sprite en pantalla.

 

ZONA VISIBLE E INVISIBLE

Desde el punto de vista del HW de la MD el plano de sprites son normalmente 512×512 píxels, siendo el punto sup-izq de la pantalla visible el punto (128,128).

EL SGDK toma esta coordenada como (0,0) para facilitarnos la vida, de esta forma las coordenadas de la pantalla visible empiezan en 0. Así el SGDK nos permite tratar los sprites sabiendo que si su x=[0 .. 320] y su y=[0 .. 224], estos estará en la zona visible de la TV (siempre que usemos ese modo de pantalla):

TV

En este blog siempre hablaremos de las coordenadas de un sprite utilizando los valores manejados por el SGDK, el punto sup-izq será por tanto (0,0) y el inf-dcha (319,223) para el modo de pantalla 320×224.

Por tanto, teóricamente, un sprite en (-20,300) no será visible. Tampoco uno situado en (450, 200).

Si un sprite sale de la pantalla visible por la izquierda, tendría valores negativos para su coordenada ‘x’ en el SGDK (internamente para la MD su coordenada ‘x’ sería un valor entre 0 y 128). Si lo hace por la derecha, tendría valores mayores a 320 (en SGDK), y mayores a 448 (internamente para la mega).

Si un sprite se sale de los 512x512px, sea por el lado que sea, se produce una cosa curiosa. Y es que el sprite vuelve a aparecer por el lado contrario.

 

Mucho ojo porque los límites de sprites (80 en total, 20 por línea) se aplican también a aquellos en la parte no-visible de la pantalla.

 

ATENCIÓN: Desde la versión 1.34 el plano por defecto será de 512x256px en vez de 512x512px , para dejar más espacio para tiles en memoria.
Se puede cambiar si así se desea.

 

 

¿Por qué tener un área más grande que la parte visible de la pantalla?

El motivo principal es que la parte no visible se utiliza para generar los planos de scroll, de forma que cuando aparecen en la parte visible ya tenemos todos los tiles en su sitio.

Además hay sprites que pueden salir y volver a entrar en la pantalla visible, si elimináramos estos sprites nada más salir implicaría tener que volver a cargarlos en memoria y un montón de trabajo extra.

Por otro lado a veces queremos cargar muchos sprites en memoria (transferir tiles tiene sus límites), así que una forma de hacer esto es ir cargando dichos tiles fuera de la pantalla y, una vez todos cargados, ya podemos utilizarlos para nuestras necesidades.

Para estos sprites fuera de pantalla se aplica el límite de 20 por línea, pero si nos pasamos, al no ser visibles, no se va a notar el «parpadeo». No obstante sí cuentan para el máximo total de sprites, 80.

 

¿Se pueden usar varios sprites para crear un super sprite?

Tendremos que utilizar varios sprites diferentes y coordinar su posición en todo momento para que visualmente parezca que es un solo sprite. Luego habría que controlar de alguna manera las colisiones, el movimiento, las animaciones, etc.

Es decir, lo mismo que el SGDK nos permite utilizar sprites de hasta 16×16 (haciendo el SGDK el trabajo sucio), nosotros manualmente podemos utilizar varios multi-sprites para simular sprites más grandes.

Al ser algo más sofisticado lo dejaremos para más adelante.

 

Ejemplo sencillo

En mi github podrás encontrar un ejemplo sencillo. Un sprite que se puede mover por la pantalla utilizando el D-pad   ¡Por fin un poco de movimiento!

captura41

En este ejemplo moveremos un sprite hecho de 1 sólo tile, un pequeño cuadro rojo, o bien un multi-sprite de Sonic, compuesto de 6×6 tiles.

Se pinta la posición en pantalla para que puedas comprobar como la posición depende de la posición sup-izq del tile, y no p.e. del centro.

Inicialmente veremos ambos sprites en pantalla, moviendo con el D-pad el cuadrado.

  • Si pulsamos B el control cambiará al sprite de Sonic.
  • Si pulsamos A volvemos a manejar el cuadrado.

Es interesante comprobar como la posición en pantalla de ambos sprites no es la esperada. Además el sprite de Sonic tiene el punto sup-izq un tanto separado de la zona pintada, por tanto hay cierta separación. La forma sencilla de hacerlo es poner el cuadrado en el punto (0,0) de la TV y luego pulsar B para que cambie a Sonic.

captura29

O bien situar Sonic pegado a la izquierda de la TV, justo donde empiezan las puas, para cambiar al cuadrado. Como el punto sup-izq de Sonic estará fuera de la zona visible, no veremos el cuadrado.

Interesante también comprobar como los pies de Sonic están en una posición, que no coinciden con la coordenada del sprite está en otra. Si por ejemplo ponemos al cuadrado en la coordenada (160,216), en la zona central-inferior de la pantalla, veremos el cuadrado dentro de la TV, si cambiamos a Sonic apenas le veremos la zona sup de la cabeza.

 

GITHUB

El código de esta lección, y de todas las demás, lo podrás encontrar en mi github:

https://github.com/danibusvlc/aventuras-en-megadrive