Pues eso, añadido nuevo tuto aquí y al hilo principal, tb he añadido el gran tutorial de Manveru Ainu y os recuerdo que si queréis hacer algún tuto o compartir código o lo que sea no os cortéis, que será bien recibido
TUTORIAL DE SGDK 04: SPRITESPues aquí andamos de nuevo con un nuevo tuto que en conjunción con el anterior, en el que tratamos los tiles, va a empezar a darle vidilla a las primeras demos que os animéis a hacer.
Eso sí, si aún no te ha quedado del todo claro el funcionamiento y uso de tiles en megadrive es MUY recomendable volver al tuto anterior y dedicarle el tiempo necesario. Tampoco hay por qué agobiarse, no tiene maś historia que:
1.Crear los tiles con cualquier método disponible
2.Mandarlos a VRAM en una determinada posición
3.Dibujarlos en pantalla usando la función pertinente Pero claro, hay que conocer las funciones necesarias y conviene tener claro también como funciona en líneas generales la memoria de vídeo. Se reitera, no tiene gran misterio, sólo requiere un poquito de paciencia y tal vez una pizca de concentración para pillar la idea de primeras.
Y tras habernos ido por las ramas para no pasar por alto que este tuto parte de la base de haber asimilado debidamente el anterior, damas y caballeros, con todos
ustedes "Los Sprites".
(Insertar FX "Ovación Multitud"

)
"Esto ... ¿y si yo no sé que es un sprite?"
Si ese es el caso, vamos con unos ejemplos visuales la mar de efectivos y así nos ahorramos la definición técnica que para eso se tira de wikipedia y listo.
Primero recordaremos que megadrive dispone de dos planos de fondo por hardware: plano A, plano B. Para por ejemplo dibujar en el B un mapa de tiles que contengan las montañas en la lejanía y en el A usamos tiles para dibujar una cordillera cercana. Llegado el momento haremos que el plano B se desplace más despacio que el A, horizontalmente los dos (tómate tus segundos para visualizar la idea, no hay prisa).
Y entonces a lo mejor, luego de visualizar mentalmente el ejemplo, piensas: "Oye, ¿se supone que ibas a hablar sobre sprites?". Sí, pero me viene al pelo el ejemplo de los mapas de tiles. Hagámonos a la idea de que los "mapas de tiles" dibujados en los planos A y B nos servirán para dibujar fondos y escenarios de nuestros juegos. Los "sprites" suelen ser los objetos, personajes o elementos que aunque lleguen a interactuar con el escenario no forman parte de éste.
Ejemplo: Sonic, Green Hill Zone
- "Mapa de tiles" en el plano A: el escenario
plataformero en primer plano
- "Mapa de tiles" en el plano B: el mar, bosquecillos
cascadas, montañas, nubes y cielo en segundo plano
(que se muevan independientemente es algo de lo
tal vez hablaremos en otro tuto)
- SPRITES: Sonic, los monitores, enemigos, anillos ...
En un juego de naves los sprites serán las propias naves, los bichejos enemigos, las explosiones, disparos y demás elementos que sean independientes del escenario.
Los sprites se dibujan sobre el "plano de sprites". Con lo cual ya tenemos que la megadrive dispone de:
- 2 planos para fondo (A y B)
- 1 plano para sprites
- 1 ?? (trollface), "plano Window", que en realidad será una parte inmóvil del plano A que sirve normalmente para meter los marcadores, etc.
Y ya por último antes de meternos en faena, enumeraremos algunas características (o puñeterías) de los sprites en megadrive.
- El plano destinado a los sprites tiene un tamaño fijo y no permite scroll. Normalmente 512x512 píxels, correspondiendo la coordenada 128,128 al primer píxel visible en la esquina superior izquierda de la pantalla. Con SGDK usaremos 0,0 como coordenada que indique el píxel de la esquina superior izquierda del plano de sprites, pero la consola en realidad no los maneja así internamente.
- Cualquier sprite fuera del área visible de pantalla no será visible. Puesto de otro modo, si sitúas un sprite de 16 píxels de ancho en la coordenada (-20, 128) dicho sprite no aparecerá en pantalla pero estar.. está.
- Si el valor dado a la coordenada X o Y del sprite excede el espacio del plano "vuelve" sobre el origen. Ejemplo: (300, 812) es lo mismo que (300, 300)
- Todos los sprites están identificados con un índice numérico y cuentan con un atributo que indica cuál es el sprite que le sigue a la hora de ser dibujado ("link"). El atributo link de nuestro último sprite ha de tomar el valor del índice que tenga el primer sprite que vayamos a dibujar. Si no lo hacemos así obtendremos el bonito número de cero sprites en pantalla. Podemos modificarlos más tarde para indicar prioridades entre los sprites... cual se dibuja encima de otros, imaginad los dragones del Space Harrier, cuyas partes cambian de prioridad según se acercan o alejan de la pantalla, para eso sirve.
- Máximo 80 sprites definidos simultáneamente.
- Máximo 20 sprites sobre una misma scanline ("en línea"), si te pasas... parpadean.
No es necesario aprendérselas de memoria, pero si tu demo o tu juego hace algo raro y no sabes por qué, dales un repaso a ver si algo de eso va a tener la culpa.
Y la idea básica y más importante:
- Los sprites están compuestos por tiles, podemos definir sprites desde 1x1 tile (8x8 píxels) hasta 4x4 tiles (32x32 píxels) usando cualquier valor intermedio, cosa que la todopoderosa snes no puede
Por ejemplo (3x2 tiles = 24x16 píxels).
En el momento en que quieras usar sprites más grandes, ten en cuenta que megadrive cuenta con esos límites, por lo que tendrás que:
. definir varios sprites hasta completar el tamaño que necesites
. y tratar las coordenadas de esos sprites de manera adecuada para que en su conjunto formen la imagen que hayan de representar.
Vamos al lío ...
****************************************************/
// LIBRERIAS ////////////////////////////////////////
#include <genesis.h>
// DATOS GLOBALES ///////////////////////////////////
Cuando programando con SGDK definamos algún dato como const ("constante", el valor con que se inicialice ya no puede variar en todo el programa) dicho dato se amacenará en ROM en lugar de en la memoria RAM de 64Kb, así que los gráficos por ejemplo DEBEN declararse así.
Vamos a crear un array de cuatro tiles (2x2) que representarán la imagen de una bola. Éste será el gráfico de nuestro primer sprite. Pero aún queda mandanga después de definir el array. Paciencia que con esto simplemente tenemos la información gráfica, que ya es algo por otro lado.
const u32 spriteTiles[4*8]=
{
0x00001111, // Tile izquierda arriba
0x00001111,
0x00111144,
0x00111144,
0x11112244,
0x11112244,
0x11112244,
0x11112244,
0x11112222, // Tile izquierda abajo
0x11112222,
0x11112222,
0x11112222,
0x00111122,
0x00111122,
0x00001111,
0x00001111,
0x11110000, // Tile derecha arriba
0x11110000,
0x44111100,
0x44111100,
0x44221111,
0x44221111,
0x44221111,
0x44221111,
0x22221111, // Tile derecha abajo
0x22221111,
0x22221111,
0x22221111,
0x22111100,
0x22111100,
0x11110000,
0x11110000
};
Del mismo modo que en el tutorial anterior tuvimos que crear un struct "genresTiles" (sí, lo hicimos, si no te acuerdas, anda, búscalo y de le das el repasito) para manejar los tiles que nos creaba la herramienta "genres" provista por SGDK, en este caso haremos lo propio creando un struct que nos permitirá hacer buen uso de los sprites. Guarda gran similitud con "genresTiles" pero una importante diferencia a tener en cuenta es que los valores que usemos vendrán dados en píxels.
Le echamos un ojo:
struct genresSprites
{
u16 *pal; // Puntero a los datos de la paleta
u32 **sprites; // Puntero a los datos de los sprites
u16 count; // Numero de sprites
u16 width; // Ancho de cada sprite en pixels
u16 height; // Ancho de cada sprite en pixels
u16 size; // como usamos el ancho/alto en pixels, informacion importante en el tamaño del sprite
};
Almacenamos los datos del exprite que nos haya generado "genres" sobre los atributos de un struct "genresSprites".
extern struct genresSprites sonic;
En el archivo "resource.rc" se habrá indicado el nombre de la variable de salida (sonic), la ruta del bitmap que contiene los frames de nuestro sprite y el tamaño en píxels en el que se han de dividir los distintos frames. En nuestro caso, de la siguiente manera:
SPRITE sonic "data/sonic.bmp" 24 32 0 7
Empecemos con el bucle principal, como en las otras ocasiones:
/////////////////////////////////////////////////////////////
// MAIN
/////////////////////////////////////////////////////////////
int main( ){
// Dibujamos la cabecera austera de turno con VDP_drawText()
VDP_drawText("Ejemplo de dibujo de SPRITES", 5, 1);
VDP_drawText("----------------------------", 5, 2);
Igual que hicimos en el tuto anterior, hay que empezar por escribir los tiles que vayamos a requerir en la VRAM de la consola usando VDP_loadTileData(). La recordamos:
// VDP_loadTileData(const u32 *data, u16 index, u16 num, u8 use_dma):
// - data: puntero a los datos de nuestro tile
// (o al primero de nuestra ristra tiles si es el caso)
// - index: en qué posición de la memoria de vídeo (VRAM) vamos a
// almacernarlo (o a partir de qué dirección si son varios)
// - num: cuántos tiles vamos a almacenar a partir de esa posición
// - use_dma: ¿usaremos o no "Acceso Directo a Memoria"?
// "spriteTiles" es el nombre que le dimos al array con la información
// gráfica de nuestro sprite-bola de 2x2 tiles.
VDP_loadTileData( (const u32 *)spriteTiles, 1, 4, 1);
La llamada a esta función es opcional, pero es recomendable usarla para asegurarnos de que ningún sprite arranque con algún valor que no le hayamos dado nosotros y el consecuente desespero
VDP_resetSprites();
Digamos que por sistema, tras mandar a VRAM los tiles de un sprite, lo siguiente por hacer es cargar su paleta. Nuestro sprite-bola usará una de las predefinidas sin embargo.
Y ahora... Y AHORA ... Y AHOOOORA... lo definimos

Lo que viene a decir que le indicaremos a la megadrive que queremos crear un flamante sprite con las características que le indiquemos y ya que ella se encargue de rellanar los registros en una tabla contenida en la VRAM con los valores pertinentes.
Cada sprite que pensemos usar hemos de definirlo antes de poder hacer uso de él en el programa. Vamos a ver dos métodos de hacerlo y luego ya decides tú cuál te parece más conveniente (*cof*, el segundo, *cof*)
Primero veamos cómo definir un sprite usando la función básica que SGDK dispone para ello de modo, VDP_setSprite() :
// void VDP_setSprite(u16 index, u16 x, u16 y, u8 size, u16 tile_attr, u8 link)
// - index: índice numérico que identifica al sprite (desde 0 a 79)
// - x: coordenada x en pantalla en la que se situará
// - y: coordenada y en pantalla en la que se situará
// - size: tamaño en tiles (desde 1x1 a 4x4 tiles). Nos valdremos de la
// macro "SPRITE_SIZE()" para indicarlo
// - tile_attr: atributos de los tile(s), (paleta, prioridad, vflip, hflip, pos_tile_VRAM)
// - link: índice del sprite que sucede al actual, cada sprite debe enlazar
// al siguiente, teniendo el último que enlazar con el primero
VDP_setSprite(0, 40, 40, SPRITE_SIZE(2,2), TILE_ATTR_FULL(PAL0,1,0,0,1), 1);
Atendiendo a los valores que hemos pasado como parámetros podemos ver que nuestro primer sprite (sprite 0), tiene un tamaño de 2x2 tiles los cuales se tomarán a partir de la posición 1 de la VRAM. Ergo, nuestro primer sprite va a mostrar el gráfico de la bola que creamos al empezar el código. Una cosa a tener en cuenta, es que mientras que para dibujar tiles en uno de las capas A y B, la consola dibuja los tiles secuencialmente de izquierda a derecha y de arriba a abajo, para los sprites lo hace de arriba a abajo y de izquierda a derecha, hay que tenerlo en cuenta a la hora de meterlo en la vram, pero vamos, que de eso se encargará el conversor gráfico que usemos al especificar que es un sprite, ya sea genres, imagenesis, etc.
Veamos ahora otra manera distinta (¿más elegante?) para definir un sprite
SpriteDef spriteBola2;
"SpriteDef" es un struct disponible en SGDK destinado a acoger los distintos datos que precisa un sprite. El primer paso de este método es proceder como en la línea de código anterior: simplemente creando un dato de tipo SpriteDef que nombraremos según nos plazca. La definición de SpriteDef en SGDK es la siguiente:
// typedef struct
// {
// s16 posx, posy;
// u16 tile_attr;
// u8 size, link;
// } SpriteDef;
// Los atributos se corresponden con cada uno de los parámetros requeridos por
// VDP_setSprite() salvando la ausencia de uno para el índice del sprite
Creado el dato, inicializamos sus atributos.
spriteBola2.posx = 0;
spriteBola2.posy = 0;
spriteBola2.size = SPRITE_SIZE(2,2);
spriteBola2.tile_attr = TILE_ATTR_FULL(PAL0,1,0,0,1);
spriteBola2.link = 2;
Y llegado este momento usamos la función VDP_setSpriteP() para definir nuestro segundo sprite:
// void VDP_setSpriteP(u16 index, const SpriteDef *sprite)
// - index: índice numérico que identifica al sprite (desde 0 a 79)
// - sprite: dirección de memoria donde se encuentra el struct
// con la información de nuestro sprite
// (nos valemos del operador & para ello)
VDP_setSpriteP(1, &spriteBola2);
Está siendo un poco tocho pero es por pretender dejar clara la forma de proceder para crear sprites. Ahora que tenemos ya definidos dos sprites, un par de bolas que se desplazarán (o no) por pantalla, vamos a recapitular a modo de resumen los pasos para crear y mostrar sprites en pantalla:
1. Cargar los tiles necesarios en VRAM
2. Cargar la paleta (si no ha sido cargada previamente)
3. Definir el sprite
4. Definir más sprites
5. Solicitarle a la megadrive que nos los dibujeVamos a ver ya por último como definir us sprite creado desde genres y en unos minutos nos metemos POR FIN en cómo animarlos por pantalla, tanto en cuanto a desplazarlos por la pantalla como crear el efecto de animación intercambiando frames. Pero antes, lo dicho: definir sprites creados con genres.
La puñeta siguiente es porque nos va a interesar conocer el número de tiles de los sprites en el "spritesheet" (el bitmap con los frames de nuestro sprite) con que genres genera los sprites, y genres, de por sí no nos lo indica. Si no lo has visto antes, abre la carpeta data y échale un ojo a sonic.bmp, ése es el "spritesheet" que estamos usando en este tuto. ¡Ah! y recuerda que si vas a usar el tuyo propio: 16 colores y dimensiones múltiplo de ocho.
u16 nbTiles = (sonic.height>>3) * (sonic.width>>3);
Si la notación binaria aún no es lo tuyo, no pasa nada. Lo que hemos hecho es tomar el valor en binario del alto (height) del sprite y ventilarle los tres últimos dígitos, lo cual equivale a dividir por ocho de una manera más rápida que haciéndolo al uso. Hemos hecho lo propio con el ancho y luego multiplicado ambos valores. Esto es porque genres guarda tanto el ancho como el ato en píxels, no en tiles, y como ya sabemos, un tile tiene un tamaño de 8x8 píxels.
Si lo de "sonic" te extraña y no caes en qué momento hemos creado ese dato, fue antes de meternos con la función main. Así:
extern struct genresSprites sonic;
Importando a nuestro código los sprites creados con "genres". Justo antes creábamos el struct genresSprites. Vuelve a echarle un vistazo y repasar sus atributos, y si te sobra tiempo y estás de buenas repara en que estamos procediendo igual que en el tuto anterior cuando sólo requeríamos de genres tiles sin más. El atributo "sprites" va a entrar en acción en breve. Estate pendiente.
Puñeta anterior realizada, vamos a definir el nuevo sprite usando el método con el que definimos el anterior.
SpriteDef spriteErizo;
Esta vez no usaremos una paleta predefinidia sino que cargamos la paleta del spritesheet. Lo hacemos sobre PAL1. Fácil, ¿no?
VDP_setPalette(PAL1, sonic.pal);
Asignamos los que serán los valores iniciales que tome nuestro tercer sprite:
spriteErizo.posx = 128;
spriteErizo.posy = 128;
spriteErizo.size = sonic.size>>8;
spriteErizo.tile_attr = TILE_ATTR_FULL(PAL1,1,1,1,5);
spriteErizo.link = 0; // Como éste va a ser el último sprite que definiremos
// lo hemos de enlazar ya con el primero cuya id es 0.
// Si se te olvida hacer esto no veremos sprite alguno.
Y tercer sprite definido:
VDP_setSpriteP(2, &spriteErizo);
Dijimos por ahí atrás que íbamos a animar un sprite frame a frame. Nuestra variable "frame" será la que indique cuál frame ha de ser mostrada.
u8 frame = 0;
¡Ah!, ábrete la carpeta "data" y déjate a la vista el spritesheet. En el archivo "resource.rc" tenemos indicado que cada frame va a ser de 24x32 píxels con lo que cada imagen queda encuadrada debidamente en un área de ese tamaño. Y lo realmente importante es que genres nos lo deja a huevo para acceder a cada frame de forma muy cómoda: usando el atributo "sprites" como un array al que especificándole un determinado índice accedemos al frame que se encuentre en dicha posición (comenzando a contar de izquierda a derecha y de arriba a abajo). Por ejemplo, sonic.sprites[6] nos ofrecerá los datos del sexto sprite/frame: el Sonic rojo.
// BUCLE PRINCIPAL ==================================================
while(1){ // Bucle infinito al canto
Movemos el sprite "bola2" incrementando su posición en un píxel ambas coordenadas...
spriteBola2.posx++;
spriteBola2.posy++;
Al usar de nuevo VDP_setSpriteP() pasándole unos valores modificados al sprite 1 (a la "bola2") lo que hacemos es actualizar sus propiedades:
VDP_setSpriteP(1, &spriteBola2);
El sprite de Sonic lo moveremos por pantalla y a la vez que lo vamos a animar cambiando su frame continuamente ...
Escribimos en la posición 5 de la VRAM tantos tiles como correspondan a un frame (nbTiles, la puñeta de más atrás), tomándolos a partir del segundo sprite/frame del spritesheet (sonic.sprites[frame + 1]). Venga, que no es tan complicado. Reléelo unas cuantas veces teniendo a la vista el spritesheet si hace falta.
VDP_loadTileData(sonic.sprites[frame + 1], 5, nbTiles, 1);
Incrementamos el frame en cada iteración:
frame++;
Y como los frames corresponidentes a la animación de correr, que son los que nos interesan, con la siguiente instrucción hacemos que cuando la variable frame vaya a tomar el valor 3 "se resetee" y vuelva a cero:
frame%=3; // solo tenemos 3 frames, volvemos al primero
Movemos el sprite, modificando su coordenada x.
spriteErizo.posx+=10;
Y registramos los cambios sobre el "sprite número 2" que no es otro que nuestro sonic presto a correr de una manera un tanto extraña.
VDP_setSpriteP(2, &spriteErizo);
VDP_updateSprites() hace que la consola tome en cuenta los nuevos valores que hayamos podido dar a los sprites. Vamos, que actualiza los sprites...
VDP_updateSprites();
Sincronizamos el dibujo con el retrazo horizontal de pantalla... unas cuantas veces, sí. Es un apaño para procurarnos un retardo conveniente y que nuestros sprites no vayan demasiado lanzados cuando se desplacen. Eres libre de comentar e invalidar tantos como te apetezca (conveniente también es dejar siempre uno al menos).
VDP_waitVSync();
VDP_waitVSync();
VDP_waitVSync();
VDP_waitVSync();
VDP_waitVSync();
VDP_waitVSync();
VDP_waitVSync();
VDP_waitVSync();
VDP_waitVSync();
}
return (0); // Return cero, y hasta aquí hemos llegado en esta ocasión.
}
Y con esto y un bizcocho ya sabemos dibujar tiles en las 2 capas y sprites.
Aquí os dejo el fuente del tutorial, recordad cambiar la ruta de vuestro makefile si no es como la mia.
https://dl.dropbox.com/u/33369593/tuto_ ... prites.rar
Como deberes... cambiar los tiles de los sprites por algunos vuestros y hacer que se muevan con el pad estaría bien. También dejo el código de la demo que hice hace siglos del SF2, por si quereis cacharrear con ella, deberíais poder entender todo, lo único que no hemos visto es la función para dibujar mapas de tiles usada para dibujar el escenario de la que tratará un futuro tutorial.
https://dl.dropbox.com/u/33369593/tuto_ ... ve/sf2.rarDe nuevo agradecer a Bruce las curradas que se mete comentando estos tutos, que sin él tendrian una periodicidad anual por lo menos xD
El próximo tuto va a ser muy sencillo para desatascar un poco las neuronas después de tanto tile, capas y sprites... va a ser como dibujar texto de forma bonita, cambiando la fuente que trae por defecto las SGDK por una bitmap!!