Tutorial programacion Megadrive - SGDK

Nuevas Hawkers con un 20% de dto.
1, 2, 3, 4, 532
pocket_lucho
MegaAdicto!!!
1.393 mensajes
desde ene 2004
Editado 4 veces. Última: 16/09/2012 - 20:18:09 por pocket_lucho.
Lo prometido es deuda asi que con la ayuda del usuario realbrucest vamos a ir haciendo unos tutoriales de las librerías SGDK para hacer vuestras cosillas en el lenguaje C para Megadrive. Lo creamos en un hilo nuevo para no seguir ensuciando el fantástico hilo de theelf sobre la programación en Basic.

Con este lenguaje ganaremos en versatilidad y sobre todo, velocidad, respecto al uso de Basic aunque evidentemente es necesario tener nociones de C que es más complicado que Basic. Lo primero es lo primero así que empezaremos con la instalación del entorno bajo la ide Code:Blocks.

TUTORIAL 1: INSTALACIÓN DEL ENTORNO - HOLA MUNDO

Este mismo tutorial lo tenéis en la wiki:

wiki/Uso_de_SGDK_en_CodeBlocks

Básicamente, realbrucest ha traducido el original de la web de sgdk aportando poco más de cosecha propia. Cualquier comentario o sugerencia o lo que sea será bien recibido.

A - Descarga e instalación de Code:Blocks

Code:Blocks es un entorno de desarrollo integrado para trabajar en lenguaje C++, libre y multiplataforma.

En la sección de descargas de la página oficial bajaremos la versión más reciente de éste correspondiente a nuestro sistema operativo.

http://www.codeblocks.org/

Luego se procederá a su instalación en la ubicación que se desee.

Imagen


B - Descarga e instalación de SGDK
Apartado de descargas en la web de SGDK:

http://code.google.com/p/sgdk/downloads/list

Para proceder a su instalación no hay más que descomprimir la carpeta en la ruta que se quiera. (En este ejemplo se ubicará en C:\ con el nombre sgdk, lo que vendría a ser C:\sgdk)


C - Creación de las variables de entorno

Antes de abrir siquiera Code::Blocks, lo siguiente que se precisa es crear dos variables de entorno. Para lo cual hemos de acceder a la ventana de "Propiedades del sistema" y una vez ahí nos encontraremos el botón "Variables de entorno" en la pestaña de "Opciones avanzadas".

Crearemos las siguientes variables:
GDK: dándole como valor la ruta donde hayamos guardado la carpeta del sgdk usando nomenclatura Unix.
Ej: c:/sgdk

GDK_WIN: dándole como valor la ruta donde hayamos guardado la carpeta del sgdk usando nomenclatura Unix.
Ej: c:\sgdk

Imagen


D- Configuración del compilador

Ahora ya sí hemos de ejecutar Code::Blocks. Con éste abierto, seleccionaremos la Settings en la barra de tareas y en el menú desplegable Compiler and debugger...

Imagen

Crearemos una nueva configuración de compilador a partir de una copia del cómpilador GNU GCC básico (GNU GCC Compiler). Le daremos el nombre que nos apetezca ("Sega Genesis Compiler" en el presente tutorial).

Imagen

Accederemos ahora a la pestaña "Toolchains executables" donde primeramente, en el cuadro "Compiler's installation directory" indicaremos la ruta en donde hemos guardado el sgdk. El resto de cuadros han de contener la siguiente información:
C compiler: gcc.exe
C++ compiler: gcc.exe
Linker for dynamics libs: gcc.exe
Linker for dynamics libs: ld.exe
Debugger: gdb.exe
Resource compiler: bintoc.exe
Make program: make.exe

Imagen

E - Creación de un proyecto de prueba

Llegados a este punto ya tenemos Code:Blocks configurado para poder realizar nuestros programas para Megadrive. Los siguientes pasos nos van a ayudar a crear un proyecto básico para que nos aseguremos del correcto funcionamiento de todo lo realizado hasta ahora.

Crea un nuevo proyecto vacío.

Imagen

Especifica el nombre que deseas que tenga y la ruta donde ha de guardarse.

Imagen

Selecciona el compilador que configuramos previamente ("Sega Genesis Compiler" en el tutorial).
Desmarca la casilla Create "Debug" configuration.
Renombra "Release" configuration a "default".
Cambia los directorios de salida por "out\".
Haz click en Finish.

Imagen

F - Propiedades del Proyecto
Se requiere modificar algunos valores en las propiedadeds del proyecto.

En el menú contextual vinculado al nuevo proyecto seleciona Properties...

Imagen

Hemos de indicar que vamos a utilizar el makefile incluido en el sgdk. Por tanto, especificaremos la ruta completa de este archivo y marcaremos el checkbox "This is a custom Makefile".
Puede que sea necesario salir y volver a la ficha de propiedades antes de poder ver la ventana Project build options. Si es así, cierra la actual y vuelve.

Imagen

Selecciona la configuración "default" en la columna de la izquierda.
Asegúrate de que aparece seleccionado en el menú desplegable el compilador adecuado ("Sega Genesis Compiler" en el tutorial).
En la pestaña "Make" establece los siguientes valores:

Imagen

Llegados a este punto es el momento de añadir archivos a nuestro proyecto recién creado.
En el caso de que queramos añadir un archivo ya creado sacaremos el menú contextual haciendo click con el botón derecho y seleccionando Add files....

Imagen

O bien si lo que queremos es crear un nuevo archivo con código desde cero, haciendo click en el icono New File.

Imagen

Hecho esto eligiremos la opción New empty file.
Cuando se nos pregunte, acto seguido, si deseamos vincularlo al proyecto responderemos que sí y le daremos un nombre (por ejemplo: main.c)
Para comprobar que todo se ha realizado correctamente escribiremos el siguiente código:

#include <genesis.h>
int main()
{
       VDP_drawText("Hello World!", 10, 13);
       return (0);
}


Y compilaremos utilizando el atajo de teclado F9.

G - Archivo Binario de Salida (Rom)

El archivo binario resultado de la compilación debe de haberse creado dentro de una carpeta llamada out contenida en la carpeta que corresponda a nuestro proyecto. (En el presente ejemplo: prueba\out\rom.bin).
La abriremos y testearemos con cualquier emulador de megadrive o la pasaremos a un flashcart si queremos probarla en hardware real.

H - Enlaces Interesantes

Página oficial del kit de desarrollo para Megadrive SGDK:

http://code.google.com/p/sgdk/

SpritesMind Forum Foros dedicados a la programación de las consolas de SEGA:

http://gendev.spritesmind.net/forum/

Página web de Code::Blocks (apartado Downloads):

http://www.codeblocks.org/downloads/binaries

I - Deberes
Si, la idea es que tengáis que hacer deberes para afianzar conocimientos. En este tutorial con lograr configurar bien y lograr una rom ya es suficiente aunque no estaría de más que modificarais la instrucción VDP_drawText con otro texto y posición en pantalla trasteando sus parámetros. Tambien estaría bien que dierais un vistazo a los ejemplos que trae la libreria y los compilarais para ver que hacen o aprender/refrescar el lenguaje C ya que no es el objetivo de estos tutoriales ;)

TUTORIAL SGDK 02: CONTROLES

Bueno, pues ya estamos en el segundo tutorial, vamos con los controles.

Lo primero, como siempre, incluimos las librerías SGDK:

#include <genesis.h>


A - Función MYJOYHANDLER()

En este ejemplo, aparte de la función main vamos a tener otra, la MYJOYHANDLER().

Esta función se llamará en cada ocasión en que la videoconsola detecta la pulsación de un botón (para lo cual la pasaremos como parámetro más adelante a la JOY_setEventHandler()). Su cometido es interpretar los estados de entrada del pad y modificar estados o realizar acciones en nuestro juego, de momento nos conformaremos con dibujar distintas cadenas de texto en pantalla para durante la ejecución conocer los estados de los botones y direcciones del pad ;)

void myJoyHandler( u16 joy, u16 changed, u16 state)
{
    //Si el botón pulsado corresponde al pad en el puerto 1
   if (joy == JOY_1)
   {
       //La sintaxis del código para comprobar el estado
       //del botón será la que sigue variando el valor
       //con el que se compararán sendos atributos:
       //state y change, state correspondiéndose con la
       //pulsación del botón y change con la liberación
       //del mismo
      if (state & BUTTON_START) //Si se pulsa START
      {
          //Que específicamente elijamos estas determinadas
          //coordenadas (x=5, y=13) se debe a que en la
          //función main vamos a mostrar el rótulo
          //correspondiente al estado del botón START en
          //dichas coordenadas. Lo que lo que estaremos
          //haciendo será sobreescribir el rótulo cada vez
          //que se pulse o suelte el botón, ya sea mostrando
          //"START button 1" o "START button 0" según el caso
         VDP_drawText("START button 1", 5, 13);
         //El área para disponer texto en pantalla en
         //megadrive se corresponde exactamente con el área
         //de tiles visibles en pantalla (no hay razón para
            //no considerar cara caracter un tile, que es lo
            //que también son). Por lo tanto, en el modo PAL
            //"normal" en que disponemos de una resolución de
            //320x224 tendremos un área de 40x28 caracteres;
            //La primera fila y la primera columna toma valor
            //cero, por lo que para comenzar un texto en la
            //esquina superior izquierda lo haríamos tal que
            //así: VDP_drawText("texto", 0, 0);
      }
      else if (changed & BUTTON_START) //Si se suelta
      {
         VDP_drawText("START button 0", 5, 13);
      }

      if (state & BUTTON_A) //Si se pulsa A
      {
         VDP_drawText("A button 1", 5, 14);
      }
      else if (changed & BUTTON_A) //Si se suelta A
      {
         VDP_drawText("A button 0", 5, 14);
      }

      if (state & BUTTON_B)
      {
         VDP_drawText("B button 1", 5, 15);
      }
      else if (changed & BUTTON_B)
      {
         VDP_drawText("B button 0", 5, 15);
      }

      if (state & BUTTON_C)
      {
         VDP_drawText("C button 1", 5, 16);
      }
      else if (changed & BUTTON_C)
      {
         VDP_drawText("C button 0", 5, 16);
      }

      if (state & BUTTON_X)
      {
         VDP_drawText("X button 1", 17, 14);
      }
      else if (changed & BUTTON_X)
      {
         VDP_drawText("X button 0", 17, 14);
      }

      if (state & BUTTON_Y)
      {
         VDP_drawText("Y button 1", 17, 15);
      }
      else if (changed & BUTTON_Y)
      {
         VDP_drawText("Y button 0", 17, 15);
      }

      if (state & BUTTON_Z)
      {
         VDP_drawText("Z button 1", 17, 16);
      }
      else if (changed & BUTTON_Z)
      {
         VDP_drawText("Z button 0", 17, 16);
      }


      if (state & BUTTON_UP)
      {
         VDP_drawText("UP button 1", 5, 17);
      }
      else if (changed & BUTTON_UP)
      {
         VDP_drawText("UP button 0", 5, 17);
      }

      if (state & BUTTON_DOWN)
      {
         VDP_drawText("DOWN button 1", 5, 18);
      }
      else if (changed & BUTTON_DOWN)
      {
         VDP_drawText("DOWN button 0", 5, 18);
      }

      if (state & BUTTON_LEFT)
      {
         VDP_drawText("LEFT button 1", 5, 19);
      }
      else if (changed & BUTTON_LEFT)
      {
         VDP_drawText("LEFT button 0", 5, 19);
      }

      if (state & BUTTON_RIGHT)
      {
         VDP_drawText("RIGHT button 1", 5, 20);
      }
      else if (changed & BUTTON_RIGHT)
      {
         VDP_drawText("RIGHT button 0", 5, 20);
      }
   }


   // otras funciones interesante
   // JOY_update() refresca el estado del pad, se llama en cada refresco de la pantalla
    // JOY_readJoypad( joy ) ¨devuelve el estado del pad1
    // JOY_waitPressBtn() espera a que se pulse un boton (no direcciones)
    // JOY_waitPress(joy, BUTTON_A | BUTTON_UP) espera a pulsar un boton indicado en un pad especifico

}//end myJoyHandler()


B - Función MAIN()

Ahora volvemos a la función main(), donde iniciamos el manejador de los controles, establecemos la función de control myJoyHandler() anterior que se ejecutará automáticamente y entramos en un bucle infinito.

////////////////////////////////////////////////////////////////////
// MAIN
////////////////////////////////////////////////////////////////////
int main( )
{
   // resetea el manejador y lee la informacion de los mandos conectados
   JOY_init();

    //Establecemos que el programa dará soporte al pad de 6 botones
    //conectado en el puerto 1
   JOY_setSupport(PORT_1, JOY_SUPPORT_6BTN);
    //Los otros valores correspondientes al resto de controles soportados
    //en las librerías SGDK son JOY_SUPPORT_OFF, JOY_SUPPORT_3BTN,
    //JOY_SUPPORT_MOUSE, JOY_SUPPORT_TRACKBALL, JOY_SUPPORT_TEAMPLAYER,
    //JOY_SUPPORT_EA4WAYPLAY, JOY_SUPPORT_MENACER, JOY_SUPPORT_JUSTIFIER_BLUE,
    //JOY_SUPPORT_JUSTIFIER_BOTH, JOY_SUPPORT_ANALOGJOY, JOY_SUPPORT_KEYBOARD

   //A través de la función siguiente conseguimos que nuestra funcion
   //creada anteriormente sea llamada cada vez que se cambia el estado de un boton
   JOY_setEventHandler( &myJoyHandler );

    //Dibujamos con dos líneas de texto una cabecera austera
   VDP_drawText("Ejemplo de control del PAD", 5, 10);
   VDP_drawText("--------------------------", 5, 11);

    //Y rellenamos el resto del espacio escribiendo cadenas que indiquen
    //que inicialmente todos los estados están a cero (nada pulsado)
   VDP_drawText("START button 0", 5, 13);
   VDP_drawText("A button 0", 5, 14);
   VDP_drawText("B button 0", 5, 15);
   VDP_drawText("C button 0", 5, 16);
   VDP_drawText("X button 0", 17, 14);
   VDP_drawText("Y button 0", 17, 15);
   VDP_drawText("Z button 0", 17, 16);

   VDP_drawText("UP button 0", 5, 17);
   VDP_drawText("DOWN button 0", 5, 18);
   VDP_drawText("LEFT button 0", 5, 19);
   VDP_drawText("RIGHT button 0", 5, 20);

    //BUCLE PRINCIPAL
    //En este caso sólo tendrá en cuenta el manejador del input que
    //hemos creado y establecido con JOY_setEventHandler anteriormente
    while(1){

        // Sincroniza la pantalla de modo que el barrido vertical no
        // interfiera con el dibujo de tiles, sprites o nuestro texto,
        // evitándonos algunos efectos indeseados de flickering
        // principalmente
        VDP_waitVSync();
    }

    return (0); // Sin efecto funcional en nuestro juego o programa,
                // pero de obligada aparición al final de una función
                // main que no se ha declarado como "void main()"

}//end main()


Y ya está, ya tenemos control de todos los botones del PAD1, aquí os dejo un fichero con el código por si no teneis ganas de teclear [+risas]

https://dl.dropbox.com/u/33369593/main.c

C - Deberes

Como deberes, añadir soporte para el mando del segundo jugador. En el próximo tutorial, empezamos con los gráficos, dibujaremos tiles en pantalla!

TUTORIAL SGDK 03: TILES

Para aquel que no esté familiarizado con los "tiles", vamos a partir entonces de su traducción al castellano: "azulejos", "losetas" o "baldosas", pero nos quedaremos con el término "tiles" para no liarnos.

Todo lo que vemos plasmado en pantalla cuando encendemos una megadrive está generado en base a tiles (la excepción que usa la "magia" de una técnica llamada framebuffer quedará para otro día). Esto es, que todos los fondos que vemos están formados por trocitos de ocho por ocho píxels que en su conjunto forman el escenario y los sprites.

En modo PAL "estándar" el área visible se rellena con 40x28 tiles (horizontal x vertical). En la VRAM, la memoria de vídeo de la consola, habrán de almacenarse todos los patrones distintos de tiles que vayamos a usar. Por lo tanto trabajar con tiles no es más que componer escenarios (en el presente caso, otro dia sprites) usando repeticiones bien pensadas de pedacitos gráficos de ocho por ocho píxels.

Con eso tenemos para empezar sin perdernos demasiado, así que manos a la obra...

Incluimos las librerías SGDK como en otros tutos:
#include <genesis.h>


Incluimos los tiles generados con SGDK, colocamos la imagen que queremos convertir en tiles en la carpeta "res" de nuestro proyecto. Sus dimensiones han de ser ambas múltiplos de 8 y debe ser un bitmap de 16 colores (4 bits/pixel). Momento para echar un vistazo a la carpeta del proyecto, abrir "res" y comprobar lo comentado.

#include "moon.h"


Creamos "a pelo" un tile (ahora lo explicamos). Con el permiso de los que ya se manejan con C diremos que el trocito de código que sigue es para crear un dato de tipo array (AKA "vector", AKA "tabla" ...), que no es otra cosa que un dato que contiene a su vez una ristra de datos. Nuestro array de nombre "tile" contendrá ocho elementos, ocho datos de 32 bits, y cada uno de ellos se corresponderá con cada una de las líneas horizontales que forman el tile (1 tile = 8x8 píxels).

Los datos están representados en base hexadecimal (se preceden con 0x para indicarlo) y son separados por comas.

Llegó el momento de revelar una de las características que más quebraderos de cabeza darán al grafista de turno, los tiles en megadrive están limitados a 16 colores (siendo el cero transparente, así que ... 15). Lo que va a ser un poco jodienda cuando nos metamos en materia artística, por lo menos para representar los tiles en formato de array viene a ser una ventaja pues se puede ver con relativa claridad el contenido gráfico del tile mirando su código ("relativa")

Si nos acordamos de que 0x es sólo un prefijo para indicar valor hexadecimal (base numérica en la que al llegar a 9 seguimos A, B, C, D, E, F y luego ya 10 -que equivaldría al 16 decimal-) cada valor representa en orden de izquierda a derecha y de arriba a abajo los colores de los píxels que conforman nuestro tile (color cero, color cero, color uno, color uno ...) ¿Y qué colores son esos? ¡Ah, amigo! eso depende de la paleta de color, anticiparemos que el color cero es el "color transparente", el que no se pinta, vamos; el resto en nada lo vemos.

const u32 tile[8]=
{
                0x00111100, // Línea 1: pixels 1 a 8
                0x01144110, // Línea 2
                0x11244211, // Línea 3
                0x11244211, // Línea 4
                0x11222211, // Línea 5
                0x11222211, // Línea 6
                0x01122110, // Línea 7
                0x00111100  // Línea 8: píxels 57 a 64
};


SGDK ofrece dos métodos de convertir imágenes a tiles sin tener que andarse con el trabajo de chinos que supone crear los tiles a pelo como hemos hecho anteriormente. Para ello o bien colocamos el bitmap que queramos "tilear" en la carpeta "res" de nuestro proyecto. O bien, haciendo uso de la herramienta "genres" también incluída que nos requiere editar el archivo "resource.rc" para indicar qué imágenes queremos en este caso convertir.

Si vamos a usar el método de "genres" precisamos contar con el siguiente struct para manejar los tiles. Para no alargarnos, recomiendo hacer una pausa aquí para echar una ojeada al archivo "resource.rc" de este ejemplo:

struct genresTiles
{
      u16 *pal;       // Puntero a los datos de la paleta
      u32 *tiles;      // Puntero a los datos de los tiles
      u16 width;      // Ancho en tiles
      u16 height;      // Alto en tiles
      u16 compressedSize; // 0 en esta demo, mas proximamente
};


Como con los tiles generados con SGDK:
- Las dimensiones de los bitmap han de ser múltiplos de 8
- Deben ser un bitmaps de 16 colores (4 bits/pixel)

Y con esta línea "cargamos" en nuestro código los tiles que "genres" nos haya generado. (Los cargamos en una estructura de tipo "genresTiles" que para algo la creamos justo antes ;)

extern struct genresTiles luna;


Tambien existe la herramienta imagenesis que conoceréis si mirasteis el tutorial de basic de theelf, la pega es que el formato que exporta no nos sirve pero vamos a ver como solucionarlo. Abrimos la imagen (file, open image), elegimos el tipo de conversión (si queremos optimizar los tiles -> mode, 15 color 4bpp 1 plane 8x8 tiles optimized, si es un sprite tendremos que cambiar el orden x-y... lo veremos en el tutorial siguiente sobre estos últimos), el color transparente (enable transparency, select transparent color). Pulsamos Actions -> Quantize Now y tendremos la imagen convertida a la paleta de megadrive y lista para exportar. La paltea si la podemos exportar sin modificarla con Export Palette Data y eligiendo como salida C, el mapa de tiles lo mismo, ya veremos como usarlo en posteriores tutoriales. El problema son los tiles, para usaremos el pequeño conversor que he escrito, el megagfx. Exportamos los tiles como basic y nos fijamos en el número de tiles que se han generado, lo pegamos en el fichero output.txt que viene en el rar que adjunto, pulsamos run.bat, elegimos un nombre para el vector de salida y por último la cantidad de tiles a covertir que vimos al convertirla en imagenesis. Se generará un fichero llamado tiles.txt, cuyo contenido podréis copiar sin problema en vuestro código y que será completamente compatible con las funciones de manejo de tiles de las SGDK.

Seguimos, como en el tutorial anterior, cabecera austera con VDP_drawText

////////////////////////////////////////////////////////////////////
// MAIN
////////////////////////////////////////////////////////////////////
int main( ){

    VDP_drawText("Ejemplo de dibujo de TILES", 5, 1);
   VDP_drawText("--------------------------", 5, 2);


Cargamos nuestro tile "creado a pelo" en la posicion 1 de la VRAM:

   // 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: ¿usaremo o no "Acceso Directo a Memoria"?
    VDP_loadTileData( (const u32 *)tile, 1, 1, 0);


En cuanto a la paleta de color, usaremos una de las paletas por defecto. Disponemos en megadrive de 4 paletas simultáneas de 15 colores cada una (+ transparente), por defecto la primera paleta (PAL0) contiene una escala de grises, PAL1: escala de rojos, PAL2: verdes, PAL3: azules.

Buen momento éste para regresar a donde creamos nuestro tile "a pelo", de "0 a F" van 16 valores y nuestras paletas son de quince colores más el "color transparente" (que se corresponde con el valor cero). Cada valor corresponde con el color que ha de tomar un píxel de acuerdo a los que estén disponibles en la paleta que corresponda a este tile, luego ya, si eso, puedes experimentar cambiando valores.

Si antes habíamos almacenado nuestro tile en la memoria de vídeo, ahora es el momento en el que lo sacamos de allí para ponerlo en pantalla.

Dibujamos...
... el tile que se encuentre en la POSICIÓN 1 de la VRAM
... sobre el "PLANO A"
... en las COORDENADAS (tile 5 horizontal,tile 5 vertical) (origen: 0,0)
... (con la paleta 0)

VDP_setTileMap(APLAN, 1, 5, 5);


"Plano A": la megadrive por hardware cuenta con dos planos de profundidad sobre los que, como norma general, dibujaremos los fondos rellenándolos de lustrosos tiles. El "plano A" (referenciado en SGDK con el nombre APLAN) es el más próximo, siendo BPLAN al que se le superpone éste (por norma general). En el futuro podremos modificar las prioridades de los sprites respecto a los planos o incluso usar el plano WINDOW, que no es más que un área del plano A que será fijo y no se moverá con el scroll, se suele utilizar para marcadores y cosas por el estilo.

Hacemos lo mismo pero asignándole parametros al tile ayudándonos de la macro TILE_ATTR_FULL() que insertaremos como segundo parámetro en la llamada a VDP_setTileMap().

Los valores de los atributos de la macro TILE_ATTR_FULL() son:
PAL2 = paleta predeterminada verde
0 = baja prioridad (se dibujará bajo tilemaps de alta prioridad)
1 = vflip (reflejo vertical)
0 = no hflip (no se usa reflejo horizontal)
1 = tile 1 de la VRAM

VDP_setTileMap(BPLAN, TILE_ATTR_FULL(PAL2, 0, 1, 0, 1), 6, 5);


Vamos a experimentar con las prioridades, un mismo tile puede tener distintas propiedades independientemente del plano en el que esté si tienen la misma prioridad, A prevalece sobre B:

    VDP_setTileMap(APLAN, TILE_ATTR_FULL(PAL1, 1, 0, 0, 1), 7, 7);
    VDP_setTileMap(BPLAN, TILE_ATTR_FULL(PAL2, 0, 0, 0, 1), 7, 7);
    VDP_setTileMap(APLAN, TILE_ATTR_FULL(PAL1, 0, 0, 0, 1), 8, 7);
    VDP_setTileMap(BPLAN, TILE_ATTR_FULL(PAL2, 1, 0, 0, 1), 8, 7);



Y si hasta ahora VDP_setTileMap() nos ha servido para dibujar en pantalla nuestro tile de forma independiente en las coordenadas que indicásemos... "VDP_fillTileMapRect()" va a hacer lo propio rellenando un determinado área mediante repeticiones de ese tile.

    // Rellena un cuadrado de 8x4 del tile 1 con paleta azul en (12,12)
    VDP_fillTileMapRect(BPLAN, TILE_ATTR_FULL(PAL3, 0, 0, 0, 1), 12, 12, 8, 4);


Como veis, los parámetros son los mismos que en la función anterior, con el añadido de cuántos tiles en horizontal y en vertical se dispondrán en las coordenadas dadas

A lo mejor llegado a este punto necesitas una cerveza para seguir en predisposición de seguir adquiriendo conocimiento megadrivero, sin problema, busca en el frigo y ahora seguimos con lo propio para los tiles generados por SGDK, queda poco en todo caso. Lo más normal si hemos dejado una imagen para que nos la convierta SGDK en tiles utilizables en nuestro código, es que esa conversión nos ofrezca "varios tiles", por lo que los anteriores ejemplos usando uno solo van a variar un poquito.

Casi al principio de nuestro código escribíamos la siguiente línea: #include "moon.h"

Con ello disponemos de un array "moon[]" el cual contiene todos los datos necesarios para que hagamos uso de la imagen original ya oportunamente "tiletizada"

El primer elemento del array contiene el ancho en píxels de la imagen, lo guardamos en la variable "w" (width) pasado a tiles (dividiendo entre ocho):

u16 w = moon[0] / 8;


El segundo elemento del array contiene el alto en píxels de la imagen, lo guardamos en la variable "h" (height) pasado a tiles (dividiendo entre ocho) :

u16 h = moon[1] / 8;


Cargamos sobre PAL1 la paleta de la imagen que está almacenada en moon[2 a 17]

   VDP_setPalette(PAL1, &moon[2]);


Cargamos los datos del bitmap en la VRAM usando para ello la función VDP_loadBMPTileData(const u32 *data, u16 index, u16 w, u16 h, u16 bmp_w)

Argumentos
- data: dirección de memoria a partir de la cual se encuentra la información gráfica
- index: posición en VRAM a partir de la cual se almacenarán los tiles
- w: ancho (en tiles) de la región del bitmap que mandaremos a VRAM
- h: alto (en tiles) de la región del bitmap que mandaremos a VRAM
- bmp_w: ancho (en tiles) de la imagen original; éste último argumento es necesario porque podríamos cargar solo un a parte del bitmap pero SGDK necesita el ancho como referencia.

   VDP_loadBMPTileData((u32*) &moon[18], 2, w, h, w);


Y finalmente dibujamos el bitmap en pantalla. De nuevo hacemos uso de TILE_ATTR_FULL() para indicar los atributos extras que queremos asignar a la imagen, principalmente nos interesa que haga uso de PAL1 por ser la paleta donde hemos cargado los colores de la imagen original.

(23, 6) son las coordenadas en pantalla (en tiles; "1 tile = 8 x 8 píxels")
(w, h) son el ancho y el alto (en tiles) respectivamente

VDP_fillTileMapRectInc(BPLAN, TILE_ATTR_FULL(PAL1, 0, 0, 0, 2), 23, 6, w, h);


Y ya sí, por fin. Lo propio para tiles generados por genres que habían sido incluidos en nuestro código mediante la siguiente línea "extern struct genresTiles luna;"

Guardamos la paleta de la imagen sobre PAL2, tomándola del atributo "pal"

VDP_setPalette(PAL2, luna.pal);


Cargamos en VRAM todos los tiles que han resultado de la conversión de la imagen:

Argumentos de VDP_loadTileData()
arg0 = dirección donde se encuentran los tiles a cargar
arg1 = indice (posición en VRAM) del primero de los tiles que conforman la imagen
arg2 = núero de tiles a enviar a la VRAM
arg3 = usar DMA (1) o no (0)

VDP_loadTileData(luna.tiles, 100, luna.width*luna.height, 0);


Y ya sólo nos queda dibujar la imagen en pantalla usando la misma función que en el ejemplo de tiles generados por el SGDK, VDP_fillTileMapRectInc(). En este caso usando PAL2, indicando que tomaremos tiles que se encuentran a partir de la posición 100 de la VRAM, en las coordenadas (23,12) de pantalla y con un ancho y un alto (en tiles) indicados por los atributos "width" y "heigh" del struct luna:

VDP_fillTileMapRectInc(BPLAN, TILE_ATTR_FULL(PAL2, 0, 0, 0, 100), 23, 12, luna.width, luna.height);


Bucle infinito
while(1){

        // sincroniza la pantalla
        VDP_waitVSync();
    }


Y ¡chin pon!
[img]return (0);
}[/img]

Aquí os dejo el fichero con el código y los gráficos utilizados, además otro con el comentado megagfx.
https://dl.dropbox.com/u/33369593/tuto_ ... _tiles.rar
https://dl.dropbox.com/u/33369593/tuto_ ... egagfx.rar

Imagen

Como deberes... entender el tutorial, que no es poco, trastear con él y cambiar los gráficos por unos vuestros. Lo próximo... sprites!


TUTORIAL DE SGDK 04: SPRITES

Pues 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" ZzzZZ )

"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 [poraki]

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 dibuje


Vamos 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

Imagen

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.

Imagen

https://dl.dropbox.com/u/33369593/tuto_ ... ve/sf2.rar

De nuevo agradecer a Bruce las curradas que se mete comentando estos tutos, que sin él tendrian una periodicidad anual por lo menos xD


-------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------

ANEXO - Tutorial de Manveru Ainu sobre tiles, capas y sprites:

Bueno primero de todo gracias por la confianza, no tengo mucha experiencia con hacer este tipo de tutoriales y quizás hay cosas que no entiendo al 100% aunque sepa hacerlas funcionar, pero he intentado hacerlo lo mejor posible. Espero que me ayudéis, que añadáis y me corrijáis cualquier cosa, sobre todo con algunas traducciones.
Sin más preámbulos paso a explicar lo que he aprendido estos días sobre SGDK y GenRes, de cómo crear bitmaps para ellos usando Gimp (gracias al usuario lingh del foro oficial) y respecto al manejo de bitmaps, tiles y sprites basándome en una traducción directa del tutorial en la wiki de la web oficial, más alguna cosa que me han enseñado. He dividido el tutorial en tres partes:


Parte 1: creación de bitmaps y tablas de sprite con Gimp

Antes que nada comentar algunos detalles:
  • Puede parecer un poco largo y complejo, pero la verdad es que escrito y leído parece mucho más de lo que es una vez sabes los pasos a seguir, todo se hace mucho más rápido cuando se vuelve automático pero ya sabéis lo que dicen de las primeras veces...
  • Descargar la última versión de Gimp en este enlace: Gimp 2.8.
  • Al igual que en la wiki, sólo he probado y usado archivos BMP.
  • Este tutorial se aplica tanto para bitmaps cualquiera como para sprites (usaré la palabra "imágenes" para generalizar).
  • El máximo número de colores de una imagen debe ser 16.
  • El ancho y largo de las imágenes deben ser múltiplo de 8. Los sprites tienen un tamaño máximo de altura y anchura de 32 píxeles (GenRes aún no soporta sprites mayores).
  • Las imágenes deben tener 4 bits por píxel.

Se puede aplicar el siguiente tutorial para crear bitmaps de cualquier tipo o sprites para GenRes, pero lo haré con una tabla de sprites ya que un bitmap se puede considerar una tabla de 1x1 (sin olvidar que los bitmaps no tienen la limitación de 32 píxeles).

El primer paso es crear nuesta tabla de sprites, que para los que no hayan oído antes este concepto decir que no es más que una imagen que contiene cada uno de los sprites que queremos usar colocados a modo de tabla, ordenados en filas y columnas. Normalmente una única tabla contiene todos los sprites de un personaje o enemigo, pero se puede usar para cualquier objeto.

Aclarar que en otros entornos de programación de videojuegos el hecho de cargar un único archivo de imagen que incluye a todos los sprites ofrece mejor rendimiento que cargar un archivo por sprite, pero desconozco como afecta esto a la Megadrive.


Empecemos con el Gimp:

  1. Debemos elegir el tamaño de nuestra tabla en función del número de sprites que vamos a necesitar. Por ejemplo supongamos que queremos hacer al prota de nuestro juego y hemos decidido que estará compuesto de 14 sprites distintos, entonces la elección óptima para el tamaño de la tabla sería 1x14, 14x1, 2x7 o 7x2, pero para el ejemplo vamos a escoger una tabla 3x5 (3 filas con 5 columnas -sprites- cada una) para explicar lo que pasa cuando queda alguna casilla vacía (también nos valdría una tabla de 4x4, 10x2... pero todas ocupan un espacio extra innecesario). La casilla 15 -posición 3,5- estará vacía y no tendrá sentido acceder a ella, aunque no produce error alguno. Simplemente se deja en blanco o se rellena con el color que indica transparencia (esto lo explico más adelante).

  2. Miramos el tamaño en píxeles que necesitamos para nuestros sprites. Deben ser suficientes para que quepan el sprite "más ancho" y el sprite "más largo o alto". Por ejemplo 24x32.

  3. Ahora sabemos que debemos crear una imagen de tamaño 120x96 (5 columnas por 24 píxeles de ancho y 3 filas por 32 píxeles de altura).

  4. Vamos al Gimp y le damos a Archivo->Nuevo. En la ventana ponemos el tamaño que decidimos en el paso anterior. Le damos a Opciones avanzadas y seleccionamos Color de fondo en Rellenar con. Sin cerrar esta ventana vamos a la ventana Caja de Herramientas y elegimos un color de fondo (de los 2 rectángulos superpuestos, el de debajo). Este color debe ser uno que NO aparezca en ninguno de los sprites de su tabla ya que será el color que el compilador interpretará como transparente. Volvemos a la primera ventana y pulsamos Aceptar.

    Imagen


  5. Ahora tenemos un archivo monocolor 120x96. Para situar correctamente los sprites vamos a colocar una rejilla (esta rejilla no aparecerá en la imagen final). Vamos a Vista->Mostrar rejilla. Ahora la ajustamos a nuestro caso: vamos a Imagen->Configurar rejilla.

    Imagen

    Se abre una nueva ventana donde la configuramos a nuestro gusto (es recomendable que los colores no interfieran visualmente con nuestros sprites para poder colocarlos bien). En Espaciado colocamos la resolución de nuestros sprites: 24x32. Pulsad el icono de la cadena para que el ancho y la altura puedan tomar valores distintos.

    Imagen


    Empecé haciendo el tutorial sin pensar con qué sprites haría las imágenes... finalmente elegí los sprites de Sonic y la verdad que el fondo verde claro y la rejilla roja le sienta a Sonic como a un cristo dos pistolas, por eso luego de repente cambio los colores pero recordad que no importa, a la hora de programar cualquier color vale, sólo debe cumplir que el color de fondo no aparezca en ningún sprite de la tabla y que este color no complique visualmente a la hora de colocar el sprite en la celda, como pasaría aquí con la rejilla y botas rojas.

  6. Ya podemos empezar a pegar los sprites en cada celda de la tabla. Tened en cuenta que la posición del sprite dentro de cada casilla es importante. Hay que procurar que si dos sprites se diferencian por ejemplo en el movimiento de la cabeza del personaje, que la posición relativa del resto del cuerpo dentro de la celda sea la misma en ambas casillas.

    Quedará más claro con el ejemplo de las siguientes viñetas: tenemos dos tablas de 1x2 donde en la inferior los sprites se han colocado mal horizontal y verticalmente mientras que en la superior están situados correctamente. Como los pies no se mueven deben estar en la misma posición dentro de sus respectivas casillas. En caso de que la diferencia entre dos sprites sea total (Sonic de pie y echo una bola por ejemplo) se debe procurar centrar ambas imágenes en la celda para evitar animaciones bruscas. A la derecha de ambas tablas se muestran las animaciones tal cual se verían en la Megadrive en cada caso (he dejado el color de fondo para que se vea mejor el movimiento dentro de la celda).

    Imagen


  7. Una vez tenemos ya lista nuestra tabla de sprites tenemos que guardarla en un formato que pueda leer SGDK o GenRes. Para ello vamos a Imagen->Modo->Indexado:

    Imagen


    Marcamos Generar paleta óptima, ponemos en Número máximo de colores 16 y pulsamos Convertir:

    Imagen


    Ya tenemos una imagen a 16 colores (no tiene por qué ser 16, este número indica el número máximo. En mi ejemplo se queda en 13). Ahora tenemos que ver la paleta de colores de la imagen para el tema de las transparencias. Vamos a Colores->Mapa->Reordenar el mapa de colores. En mi caso obtengo el siguiente resultado:

    Imagen

    En esta pantalla debemos apuntar el número al que corresponde el color de fondo de nuestra tabla, en mi caso el verde es el 8. En su momento diré para qué sirve este número.

  8. Nuestra tabla de sprites está construida, ahora vamos a guardarla en formato BMP. Vamos a Archivo->Exportar... ponemos el nombre que queramos a nuestro archivo + ".bmp". Luego pulsamos el botón Exportar y en la ventana que se nos abre le damos a Opciones de compatibilidad y por último marcamos la casilla de No escribir la información del espacio de colores y exportamos.

    Imagen



Parte 2: diseño y manipulación de tiles.

Tiles

Antes de meternos con los sprites y bitmaps, empecemos entendiendo un poco como maneja la Megadrive los tiles:
  • La Megadrive redibuja 2 planos en cada actualización de pantalla (más un tercero para los sprites).
  • Cada plano está compuesto por tiles de 8x8 píxeles.
  • Cada plano puede cargar en memoria tiles de entre 32x32 y 128x128 (aunque sólo hasta 40x28 son visibles en la pantalla).
  • Cada plano se rellena de izquierda a derecha y de arriba a abajo.
  • Cada tile se puede reusar varias veces en cualquier plano con distintas paletas.
  • Cada tile se puede usar con cualquiera de las 4 paletas disponibles.
  • Cada tile se puede dibujar volteada sin un consumo extra de memoria.
  • Una cantidad importante de tiles se pueden cargar usando DMA.
Otros datos interesantes a conocer:
  • Un tile está hecho de 32 bytes, 4 bytes por línea.
  • Cada píxel del tile tiene un valor de 4 bits que indica el color, entre 0x0 y 0xF.
  • El primer tile (el 0) en la VRAM se usa para colorear el fondo del tile.
  • SGDK reserva espacio en la VRAM para 1310 tiles + 96 para texto.
  • Un tile se considera un patrón o unidad de diseño.
  • Un tile en la pantalla no se borra en cada actualización, no es necesario dibujarlo cada vez.
  • Cada paleta esta compuesta de 16 colores.
Entonces para dibujar un tile en la pantalla:
  1. Cargamos el tile en la VRAM (el DMA no lo usaremos en este tutorial).
  2. Cargamos su paleta si no ha sido cargada antes.
  3. Lo dibujamos en el plano que indiquemos en (x,y) con esa paleta.

Ahora que ya sabemos más sobre los tiles, necesitamos crear uno para practicar un poco. Existen programas para convertir bitmaps de 16 colores en tiles y paletas para la Megadrive (Genitile, B2T, GenRes, Mega-Happy-Sprite...) pero vamos a empezar haciendo uno en código C mediante un array:

        const u32 tile[8]=
        {
                0x00111100,
                0x01144110,
                0x11244211,
                0x11244211,
                0x11222211,
                0x11222211,
                0x01122110,
                0x00111100
        };

Tenemos un tile de 4 colores: 0, 1, 2 y 4. Veamos el código para dibujarlo en pantalla:

        // Cargamos nuestro tile en la posición 1 de la VRAM
        // argumento 1: el tile a cargar.
        // argumento 2: posición en la VRAM.
        // argumento 3: número de tiles a cargar.
        // argumento 4: usar DMA (1) o no (0).
        VDP_loadTileData( (const u32 *)tile, 1, 1, 0);
       
        // No cargamos una paleta en este ejemplo, dejamos la paleta por defecto.
       
        // Dibujamos nuestro tile.
        // argumento 1: el plano donde se dibuja: APLAN o BPLAN.
        // argumento 2: índice del tile a dibujar.
        // argumento 3: Coordenada X.
        // argumento 4: Coordenada Y.
        VDP_setTileMap(APLAN, 1, 5, 5);
       
        while(1)
        {
                VDP_waitVSync();
        }

Esto dibujará en la pantalla nuestro tile gris degradado.
El argumento 2 de VDP_setTileMap puede ser algo más que el índice del tile. Veamos cómo sería dibujarlo en verde y volteado:

// Argumentos de TILE_ATTR_FULL:
        // argumento 1: paleta del tile (PAL2 = paleta por defecto de tonos verdes).
        // Prioridad: prioridad baja (0) o alta (1).
        // Volteo vertical (1) o no (0).
        // Volteo horizontal (1) o no (0).
        // Tile a dibujar: tile 1.
        VDP_setTileMap(BPLAN, TILE_ATTR_FULL(PAL2, 0, 1, 0, 1), 6, 5);

Imagen

Imagen
Zoom X4


La prioridad indica qué plano se dibuja sobre el otro. Esto se regula a nivel de tiles, lo que significa que por ejemplo el plano A puede estar delante del B en el punto (x,y) pero estará detrás en el punto (m,n). Esto es útil para hacer planos de scroll que se mueven a distintas velocidades. Veamos un ejemplo del uso de las prioridades:

        //El mismo tile dibujado en 2 planos y con paletas distintas, ¿cuál se superpone?
        VDP_setTileMap(APLAN, TILE_ATTR_FULL(PAL1, 1, 0, 0, 1), 7, 7);
        VDP_setTileMap(BPLAN, TILE_ATTR_FULL(PAL2, 0, 0, 0, 1), 7, 7);
        VDP_setTileMap(APLAN, TILE_ATTR_FULL(PAL1, 0, 0, 0, 1), 8, 7);
        VDP_setTileMap(BPLAN, TILE_ATTR_FULL(PAL2, 1, 0, 0, 1), 8, 7);

En (7,7) A está frente a B al tener prioridad 1 frente a 0, en cambio en (8,7) B tiene mayor prioridad por lo que se superpone a A. Si ambos tuvieran la misma prioridad se mantiene el orden por defecto: A queda por delante de B.

Imagen
De izquierda a derecha: prioridad A>B, B>A, A=B.

Imagen
Zoom X4


Ahora supongamos que queremos rellenar un gran espacio (una zona o la pantalla por ejemplo) con nuestro tile, entonces deberíamos dibujarlos uno a uno lo que sería un trabajo tedioso. Por suerte SGDK nos permite usar la función VDP_fillTileMapRect:

        // Rellena con tiles azules un cuadrado de tamaño 8x8 en la posición (12,12).
        // argumento1: dibujamos en el plano A o B.
        // argumento2: tile inicial.
        // argumento3: coordenada X.
        // argumento4: coordenada Y.
        // argumento5: anchura del rectángulo.
        // argumento6: altura del rectángulo.
        VDP_fillTileMapRect(BPLAN, TILE_ATTR_FULL(PAL3, 0, 0, 0, 1), 12, 12, 8, 8);

Imagen

Imagen
Zoom X2


Multi-Tile

Como no es muy difícil de entender, no podemos hacer un juego con un único tile. Probemos a dibujar esta luna en la pantalla:
Imagen
SGDK puede manejar varios ficheros de bits a la vez si están en la subcarpeta res. Entonces SGDK creará dos archivos para cada bitmap:
- nombre_del_archivo_bmp.o es la forma de objeto del bitmap.
- nombre_del_archivo_bmp.h permite acceder a los datos del bmp en código fuente.

El código para cargar nuestro bitmap es el siguiente:

        // Carca el código de nuestro bitmap.
        #include "moon.h"

        int main( )
        {
                // Obtiene el ancho de la imagen en píxeles (múltiplo de 8).
                u16 w = moon[0];
                // Obtiene la altura de la imagen en píxeles (múltiplo de 8).
                u16 h = moon[1];

                // Obtiene la paleta de la imagen (de 2 a 17)
                VDP_setPalette(PAL1, &moon[2]);
       
                // Carga la imagen (18....) in VRAM
                // w/8 = ancho en tiles que queremos cargar.
                // h/8 = altura en tiles que queremos cargar.
                // w/8 = Ancho en tiles del bitmap (se necesita porque se puede cargar sólo una parte del bitmap si se quiere pero[i] SGDK[/i] necesita la anchura como referencia).
                VDP_loadBMPTileData((u32*) &moon[18], 1, w / 8, h / 8, w / 8 );

                while(1)
                {
                        VDP_waitVSync();
                }
                return 0;
        }

Para dibujarlo ahora podríamos hacerlo manualmente usando VDP_setTileMap pero resulta muy trabajoso dibujar uno a uno cada tile. Por suerte SGDK trae la función VDP_fillTileMapRectInc que dibuja desde el índice del tile en el argumento hasta el último necesario para rellenar el rectángulo cuyas coordenadas y tamaño pasamos como argumentos:

        // Llena la pantalla con los tiles desde el primero hasta rellenar el rectángulo con origen (12,12) y como tamaño la anchura y altura indicadas.
        // argumento1: dibujamos en el plano A o B.
        // argumento2: tile inicial.
        // argumento3: coordenada X.
        // argumento4: coordenada Y.
        // argumento5: anchura del rectángulo (w / 8 = anchura de la imágen en tiles).
        // argumento6: altura del rectángulo (h / 8 = altura de la imágen en tiles).
        VDP_fillTileMapRectInc(BPLAN, TILE_ATTR_FULL(PAL1, 0, 0, 0, 1), 12, 12, w / 8, h / 8);

Imagen



GenRes para manejar bitmaps

SGDK trae otra manera de cargar tiles: usar GenRes. GenRes es una herramienta que convierte bitmaps, animaciones y maps para el desarrollo de juegos en la Megadrive. En este tutorial sólo hablaremos del modo BITMAP.

GenRes se basa en la declaración de un fichero de recursos en la carpeta raiz del proyecto llamado resource.rc donde cada línea define el modo de conversión, el nombre de la variable de salida, el fichero de bitmaps y otros parámetros. Usamos el modo de conversión de BITMAP:

        BITMAP variable_salida1 "directorio/fichero1.bmp" 0
        BITMAP variable_salida2 "directorio/fichero2.bmp" 0
        ....
        ....
        ....
        ; La última línea de este archivo debe ser una línea en blanco o un comentario.

Los archivos BMP no deben guardarse en la carpeta "res" ya que serían compilados por el GenRes y el compilador de bitmaps del SGDK al mismo tiempo.

Escribiendo esto en nuestro archivo resource.rc, SGDK invocará GenRes para compilar los ficheros directorio/ficheroX.bmp a un archivo enlazado resource.o.

Desde la versión 0.7d, el formato de salida de GenRes no está definido en SGDK por lo que debemos escribir nuestra propia estructura, como ésta:

        struct genresTiles
        {
                u16 *pal;               // Puntero a la paleta.
                u32 *tiles;             // Puntero a los tiles.
                u16 width;              // Anchura en tiles.
                u16 height;             // Altura en tiles.
                u16 compressedSize; // 0.
        };

A diferencia del soporte de bitmaps nativo de SGDK, GenRes no genera un archivo de cabecera pero podemos acceder a ellos declarando las variables variable_salidaX de la siguiente manera:

        extern struct genresTiles variable_salida1, variable_salida2...;


VDP_loadBMPTileData se usa para los ficheros BMP, pero como GenRes convierte un bitmap en tiles necesitamos otra función: VDP_loadTileData. Así quedaría el ejemplo anterior de la luna usando GenRes:

resource.rc
        BITMAP moon "data/moon.bmp" 0
        ; La última línea de este archivo debe ser una línea en blanco o un comentario.

main.c
        struct genresTiles
        {
                u16 *pal;
                u32 *tiles;
                u16 width;
                u16 height;
                u16 compressedSize;
        };
        extern struct genresTiles moon;

        int main( )
        {
                VDP_setPalette(PAL1, moon.pal);

                // Carga los tiles en la VRAM.
                //  argumento 1: los tiles.
                //  argumento 2: índice del primer tile.
                //  argumento 3: número de tiles a cargar.
                //  argumento 4: usa DMA (1) o no (0).
                VDP_loadTileData(moon.tiles, 1, moon.width*moon.height, 0);

                VDP_fillTileMapRectInc(BPLAN, TILE_ATTR_FULL(PAL1, 0, 0, 0, 1), 12, 12, moon.width, moon.height);

                while(1)
                {
                        VDP_waitVSync();
                }
                return 0;
        }


Sobre si es mejor usar el soporte nativo para bitmaps de SGDK o el GenRes... que cada uno pruebe ambos y se quede con el que más le guste.


Bitmaps comprimidos

Si se usa una gran cantidad de tiles es muy recomendable comprimir los bitmaps con programas como RLE, Huffman... SGDK no incluye un método para cargar bitmaps comprimidos por lo que es cosa del usuario hacerse su propio algoritmo de des/compresión. GenRes puede usar la compresión RLE, habrá más detalles en próximos tutoriales. Podéis mirar si el programa que usáis puede exportar bitmaps comprimidos.


Miscelánea

Tenemos disponibles las utilidades del emulador Gens KMod para comprobar como funcionan vuestros tiles. Podéis comprobar la VRAM y detectar cualquier posible anomalía.

Imagen



Parte 3: gestión de sprites y tablas de sprites.

Sprites

Antes que nada comentar que existe una diferencia entre los tiles de los planos y los tiles de los sprites:
  • Los planos dibujan los tiles de izquierda a derecha y luego de arriba a abajo (siguiendo las filas).
  • Los sprites dibujan sus tiles de arriba a abajo y después de izquierda a derecha (recorriendo las columnas).
Por esto no podemos usar las mismas funciones para dibujar los tiles de los planos y sprites. Por suerte la forma de cargarlas en memoria si es la misma. Ambos usan los tiles cargados en la VRAM por lo que podremos usar un mismo tile para dibujar en ambas cosas a la vez si lo necesitamos.

Información básica que debemos saber sobre los sprites:
  • El "plano" de los sprites, a diferencia de los otros dos, no es desplazable (no admite scrolling) y tiene un tamaño fijo, normalmente 512x512.
  • Esto significa que solo una parte del plano de los sprites es visible. Esta parte comienza en (128,128).
  • Un sprite fuera de este área de visualización no es visible.
  • La posición de los sprites es cíclica. Por ejemplo el punto (300, 812) es el mismo que (300, 300) ya que 812 mod 512 = 300.
  • Se puede controlar el orden en el que se dibujan los sprites mediante el atributo link.
  • Un máximo de 80 sprites se pueden definir en modo PAL.
  • Un máximo de 20 sprites se pueden dibujar en una misma línea en modo PAL.
  • Los sprites están hechos de 1x1 a 4x4 tiles (por lo que los sprites mas grandes son de hecho multi-sprites).
Los pasos para dibujar un sprite en la pantalla:
  1. Cargar los tiles en la VRAM.
  2. Cargar su paleta (si no ha sido cargada aún).
  3. Definir el sprite.
  4. Definir los demás, si existen.
  5. Solicitar dibujarlo en pantalla

Crear y dibujar un sprite definido en C

Como antes, vamos a crear primero un sprite mediante un array de tiles. Queremos un sprite de 2x2, entonces necesitamos 4 tiles:

        const u32 spriteTiles[4*8]=
        {
                0x00001111, //Tile de arriba-izquierda.
                0x00001111,
                // ...

                0x11112222, //Tile de abajo-izquierda.
                0x11112222,
                // ...

                0x11110000, //Tile de arriba-derecha.
                0x11110000,
                // ...

                0x22221111, //Tile de abajo-derecha.
                0x22221111,
                // ...

};


Este sprite tiene la misma forma que usamos para crear un tile. Este es el código para cargarlo en memoria y visualizarlo correctamente:

        // Carga los tiles en la VRAM.
        VDP_loadTileData( (const u32 *)spriteTiles, 1, 4, 0);
       
        // Usamos una paleta por defecto por ahora.
       
        // Definimos el sprite:
        // argumento 1: el índice del sprite (desde 0 a 79).
        // argumento 2: coordenada X.
        // argumento 3: coordenada Y.
        // argumento 4: tamaño (de 1x1 a 4x4 tiles).
        // argumento 5: atributos de el/los tile/s.
        // argumento 6: propiedad link.
        VDP_setSprite(0, 40, 40, SPRITE_SIZE(2,2), TILE_ATTR_FULL(PAL0,1,0,0,1), 0);

        // Solicitar que se dibuje el sprite.
        VDP_updateSprites();
       
        while(1)
        {
                VDP_waitVSync();
        }


Otros aspectos importantes:
  1. Puedes definir tantos sprites como necesites (80 máximo) antes de solicitar que se dibujen en pantalla.
  2. Usando SGDK, la posición de los sprites está basada en el área de la pantalla, no en el plano de los sprites, lo que quiere decir que (0,0) significa (0,0) en la pantalla y (128,128) en el plano de los sprites.
  3. SPRITE_SIZE es necesario para pasar un valor correcto (0000b para 1x1, 0101b para 2x2, etc...). Al parecer hay algunos problemas con la función SPRITE_SIZE, aunque yo de momento no he tenido ninguno. En caso de producirse cambiar "SPRITE_SIZE(X,X)" por "variable_salidaX.size>>8".
  4. TILE_ATTR_FULL es el mismo macro que usamos con los tiles.


Mover un sprite

Un sprite se usa principalmente para objetos que se mueven por lo que hay que actualizar la x e y del sprite constantemente. Usando la función VDP_setSprite pondremos en problemas a la Megadrive ya que tendremos que restablecer el sprite en cada actualización. Una manera útil de hacerlo es:
  1. Usar una estructura para mantener las propiedades del sprite: un objeto SpriteDef (en el tutorial en inglés aparece _spritedef porque está desactualizado).
  2. Una función para cambiar y actualizar lo que necesitemos: VDP_setSpriteP

        SpriteDef mySprite;
       
        mySprite.posx = 0;
        mySprite.posy = 0;
        mySprite.size = SPRITE_SIZE(2,2);
        mySprite.tile_attr = TILE_ATTR_FULL(PAL0,1,0,0,1);
        mySprite.link  = 0;
        VDP_setSpriteP(0, &mySprite);

.....y para hacer que se mueva:

        while(1)
        {
                mySprite.posx++;
                mySprite.posy++;
                VDP_setSpriteP(0, &mySprite);

                VDP_updateSprites();

                VDP_waitVSync();
        }

Puedes usar VDP_setSprite o VDP_setSpriteP indistintamente sin problemas, SDGK te ofrece VDP_setSpriteP para hacerlo más fácilmente.


Propiedad link

Hasta ahora no habíamos hablado de la propiedad link. Sin conocer su uso no seremos capaces de dibujar más de un sprite. Esta variable contiene el índice del siguiente sprite a dibujar (el último debe apuntar de vuelta al sprite 0). Volviendo al ejemplo anterior, sólo dibujamos el sprite 0 ya que su link vale 0.

        mySprite.link  = 0;


Para dibujar dos sprites tendremos que hacerlo de la siguiente manera:

        mySprite0.link  = 1; // Enlaza al siguiente sprite, el 1.
        VDP_setSpriteP(0, &mySprite0); // Dibuja el sprite 0.

        mySprite1.link  = 0; // Como mySprite1 es el último, volvemos al sprite 0.
        VDP_setSpriteP(1, &mySprite1); // Dibujamos el sprite 1.


La propiedad link es usa también para definir el orden de superposición de los sprites: el primero estará por debajo del siguiente, es decir, se van superponiendo a medida que se van dibujando. Entonces te permite controlar qué sprite y cuándo éste puede ser dibujado.
De la siguiente manera puedes fácilmente definir un sprite pero no dibujarlo:

        mySprite0.link  = 2; // Enlaza al sprite 2.
        VDP_setSpriteP(0, &mySprite0); // Dibuja el sprite 0.

        mySprite1.link  = 2; // Enlaza al sprite 2.
        VDP_setSpriteP(1, &mySprite1); // Dibuja el sprite 1 (esto nunca ocurre).
       
        mySprite2.link  = 0; // Vuelve al sprite 0 por lo que el sprite 1 no se dibuja.
        VDP_setSpriteP(2, &mySprite2); // Dibuja el sprite 2.

Por supuesto el sprite 0 nunca se puede evitar.

PRECAUCIÓN: un mal uso de la propiedad link es la principal razón para que aparezcan bugs en el juego. Hay que comprobar estas 3 cosas:

  1. Todos tus sprites deben enlazar a otro.
  2. Que no se forme un enlace cíclico (por ejemplo: 1->2, 2->3, 3->1 ...).
  3. El último sprite tiene que apuntar al sprite 0


Manejo de sprites con GenRes

A diferencia de los tiles para planos, SGDK no trae soporte nativo para sprites en bitmaps de 16 colores, pero podemos usar GenRes en modo SPRITE.
Como ya hemos comentado, GenRes se basa en la declaración de un fichero de recursos donde cada línea define el modo de conversión, el nombre de la variable de salida, el fichero de bitmaps y otros parámetros. Ahora con SPRITE se puede hacer de esta manera:


     SPRITE variable_salida1 "directorio/fichero1.bmp" <ancho_sprite_fichero1> <altura_sprite_fichero1> <argumento*> <posición del color de transparencia en la paleta de fichero1.bmp>
     SPRITE variable_salida2 "directorio/fichero2.bmp" <ancho_sprite_fichero2> <altura_sprite_fichero2> <argumento*> <posición del color de transparencia en la paleta de fichero2.bmp>
....
....
; La última línea de este archivo debe ser una línea en blanco o un comentario.

argumento*: desconozco la utilidad y función de este argumento. el que nos interesa es el 4ª que es el número que apuntamos antes en el Gimp, la posición donde está el color de la transparencia en nuestra paleta de colores.
Recordar de nuevo que los archivos no deben guardarse en la carpeta res.

Escribimos esto en un fichero resource.rc. SGDK invocará a GenRes para compilar los directorio/archivoX.bmp al fichero resource.o.

Desde la versión GenRes 0.7d este formato de salida de estas variables no está definido en SGDK por lo que de nuevo tendremos que crear nosotros mismos una estructura con ese formato:

    struct genresSprites
    {
        u16 *pal;    //Puntero a la paleta.
        u32 **sprites;    //Puntero hacia los sprites.
        u16 count;    //El número de sprites que ha encontrado [i]GenRes [/i]en nuestro archivo.
        u16 width;    //Anchura de cada sprite en píxeles, no en tiles.
        u16 height;    //Altura de cada sprite en píxeles, no en tiles.
        u16 size;    //Tamaño del sprite (luego veremos más acerca de [i]size[/i]).
    };

Luego para acceder a los datos usando las variable_salidaX del fichero resource.sc:

    extern struct genresSprites variable_salida1, variable_salida2, .... ;

Nos fijamos en que la variable sprites ya no es "u32 *sprites", ahora es "u32 **sprites". Esto es porque GenRes convierte tablas de sprites en un Array de sprites (incluso si la tabla sólo contiene 1 sprite), no en un único sprite.


Podemos retomar ahora nuestra tabla de sprites que hicimos con Gimp. Si te saltaste la parte 1 de este tutorial puedes seguir adelante usando esta tabla (la rejilla la pone el editor de imágenes, no aparece en el fichero):
Imagen

Para nuestro proyecto crearíamos un archivo resource.rc de la siguiente manera (suponemos que nuestro archivo se llama sonic.bmp y está guardado en una carpeta dentro de nuestro proyecto llamada data):

    SPRITE sonic "data/sonic.bmp" 24 32 0 T
    ; La última línea de este archivo debe ser una línea en blanco o un comentario.

T es la posición del color de transparencia en la paleta de nuestra tabla (en la parte 1 del tutorial, apartado 7, podéis ver cómo averiguar esta posición con el Gimp). El de la imagen anterior es la posición 7.

Ahora tenemos nuestro archivo BMP diseccionado en un array de sprites en la variable sprites. Los sprites están ordenados en el array recorriendo cada fila de la tabla hasta la última columna y luego saltando a la siguiente fila y repitiendo el proceso, así hasta terminar.
Cada posición del array contiene los datos estándar de un tile por lo que se puede acceder a ella usando la función VDP_loadTileData pasándole el número de tiles. Para dibujar uno de los sprites de nuestra tabla el código sería el siguiente:

        // Cada sprite es altura/8 * anchura/8 (porque es tamaño en píxel, no en tiles)
        // Es el número de 8x8 tiles que necesitas para dibujar un sprite.
        u16 nbTiles = (sonic.height>>3) * (sonic.width>>3);
       
        VDP_loadTileData( sonic.sprites[0], 1, nbTiles, 0);

        // Carga la paleta de sonic.bmp en PAL1.
        VDP_setPalette(PAL1, sonic.pal);

        // Es opcional pero recomendable.
        VDP_resetSprites();

        // argumento1: índice del sprite (entre 0 y 79).
        // argumento2: posición en el eje X.
        // argumento3: posición en el eje y.
        // argumento4: tamaño: tiles de 1x1 a 4x4, el nuestro es 3x4 (24x32 entre 8).
        // argumento5: atributos de el/los tile/s:
        // argumento6: propiedad link.
        VDP_setSprite(0, 0, 0, SPRITE_SIZE(3,4), TILE_ATTR_FULL(PAL1,1,0,0,1), 0);

        VDP_updateSprites();



Animaciones

Sonic no sería Sonic si no corriese. Ya sabemos como hacer que se mueva usando SpriteDef, pero nos falta algo: la animación de sprites.

Hay 2 maneras básicas de hacer una animación:
  1. Cargar cada frame y declarar el sprite desde el primer tile en cada actualización.
  2. Declarar un sprite y cargar cada frame en el primer tile en cada actualización.
El primer método necesita mucha VRAM, el segundo realiza una carga continua que puede ralentizar el juego. Vamos a usar el segundo: actualizar los frames.
Los pasos son simples: en cada actualización cargar los tiles del frame con VDP_loadTileData y luego dibujar el frame usando VDP_setSpriteP.

Hagamos que Sonic corra, usamos los sprites 1, 2 & 3:

        u8 frame = 0;
       
        // definimos el sprite (usando un SpriteDef para mover a Sonic).
        mySprite.posx = 40;
        mySprite.posy = 40;
        mySprite.size = SPRITE_SIZE(3,4);
        mySprite.tile_attr = TILE_ATTR_FULL(PAL1,1,0,0,1);
        mySprite.link  = 0;
        VDP_setSpriteP(0, &mySprite);

        while(1)
        {
                // Seguimos usando nbTiles porque todos los sprites tienen el mismo tamaño en una tabla de sprites.
                // Frame vale 0, 1, 2 por lo que carga sprites 1, 2, 3; el 0 es Sonic parado de pie.
                VDP_loadTileData( sonic.sprites[frame + 1], 1, nbTiles, 0);
                frame++; // Siguiente frame
                frame%=3; // Va rotando porque sólo necesitamos 3 frames.


                // Sonic se mueve.
                mySprite.posx+=10;
                VDP_setSpriteP(0, &mySprite);
               
                //flush
                VDP_updateSprites();
               
                VDP_waitVSync();
        }


Imagen


Miscelánea

Para probar nuestro motor de sprites es útil el emulador Gens KMod. Podemos explorar la lista de sprites y rastrear posibles problemas.

Imagen
realbrucest
MegaAdicto!!!
1.667 mensajes
y 2 fotos
desde oct 2007
en WTC7
Sí, así mejor ;)

Ya nos iréis contando, muchas cositas guapas en cartera, la preparación del entorno es el obligado comienzo.
bertobp
Clasicómano
11.139 mensajes
desde abr 2011
en Barcelona
Bien!! al fin empieza el tutorial del año!! jajaja

Auque estoy esperando con ansias las proximas lecciones XD
felisucoibi
MegaAdicto!!!
508 mensajes
desde jul 2011
en Alicante
¿alguien que lo haya conseguido en linux instalar y hacer funcionar todo?
pocket_lucho
MegaAdicto!!!
1.393 mensajes
desde ene 2004
Los compiladores y herramientas son para dos.

Puedes instalarte el virtual box, crearte un xp virtualizado y a correr, yo lo tengo así en osx, incluso en win7 alguna cosilla da problemas!
wave
MegaAdicto!!!
1.069 mensajes
desde abr 2004
en Cerdanyola del Valles
Excelente, precisamente el otro dia me configuré el entorno, a ver que mas sale por aqui :)
pocket_lucho
MegaAdicto!!!
1.393 mensajes
desde ene 2004
TUTORIAL SGDK 02: CONTROLES

Bueno, pues ya estamos en el segundo tutorial, vamos con los controles.

Lo primero, como siempre, incluimos las librerías SGDK:

#include <genesis.h>


A - Función MYJOYHANDLER()

En este ejemplo, aparte de la función main vamos a tener otra, la MYJOYHANDLER().

Esta función se llamará en cada ocasión en que la videoconsola detecta la pulsación de un botón (para lo cual la pasaremos como parámetro más adelante a la JOY_setEventHandler()). Su cometido es interpretar los estados de entrada del pad y modificar estados o realizar acciones en nuestro juego, de momento nos conformaremos con dibujar distintas cadenas de texto en pantalla para durante la ejecución conocer los estados de los botones y direcciones del pad ;)

void myJoyHandler( u16 joy, u16 changed, u16 state)
{
    //Si el botón pulsado corresponde al pad en el puerto 1
   if (joy == JOY_1)
   {
       //La sintaxis del código para comprobar el estado
       //del botón será la que sigue variando el valor
       //con el que se compararán sendos atributos:
       //state y change, state correspondiéndose con la
       //pulsación del botón y change con la liberación
       //del mismo
      if (state & BUTTON_START) //Si se pulsa START
      {
          //Que específicamente elijamos estas determinadas
          //coordenadas (x=5, y=13) se debe a que en la
          //función main vamos a mostrar el rótulo
          //correspondiente al estado del botón START en
          //dichas coordenadas. Lo que lo que estaremos
          //haciendo será sobreescribir el rótulo cada vez
          //que se pulse o suelte el botón, ya sea mostrando
          //"START button 1" o "START button 0" según el caso
         VDP_drawText("START button 1", 5, 13);
         //El área para disponer texto en pantalla en
         //megadrive se corresponde exactamente con el área
         //de tiles visibles en pantalla (no hay razón para
            //no considerar cara caracter un tile, que es lo
            //que también son). Por lo tanto, en el modo PAL
            //"normal" en que disponemos de una resolución de
            //320x224 tendremos un área de 40x28 caracteres;
            //La primera fila y la primera columna toma valor
            //cero, por lo que para comenzar un texto en la
            //esquina superior izquierda lo haríamos tal que
            //así: VDP_drawText("texto", 0, 0);
      }
      else if (changed & BUTTON_START) //Si se suelta
      {
         VDP_drawText("START button 0", 5, 13);
      }

      if (state & BUTTON_A) //Si se pulsa A
      {
         VDP_drawText("A button 1", 5, 14);
      }
      else if (changed & BUTTON_A) //Si se suelta A
      {
         VDP_drawText("A button 0", 5, 14);
      }

      if (state & BUTTON_B)
      {
         VDP_drawText("B button 1", 5, 15);
      }
      else if (changed & BUTTON_B)
      {
         VDP_drawText("B button 0", 5, 15);
      }

      if (state & BUTTON_C)
      {
         VDP_drawText("C button 1", 5, 16);
      }
      else if (changed & BUTTON_C)
      {
         VDP_drawText("C button 0", 5, 16);
      }

      if (state & BUTTON_X)
      {
         VDP_drawText("X button 1", 17, 14);
      }
      else if (changed & BUTTON_X)
      {
         VDP_drawText("X button 0", 17, 14);
      }

      if (state & BUTTON_Y)
      {
         VDP_drawText("Y button 1", 17, 15);
      }
      else if (changed & BUTTON_Y)
      {
         VDP_drawText("Y button 0", 17, 15);
      }

      if (state & BUTTON_Z)
      {
         VDP_drawText("Z button 1", 17, 16);
      }
      else if (changed & BUTTON_Z)
      {
         VDP_drawText("Z button 0", 17, 16);
      }


      if (state & BUTTON_UP)
      {
         VDP_drawText("UP button 1", 5, 17);
      }
      else if (changed & BUTTON_UP)
      {
         VDP_drawText("UP button 0", 5, 17);
      }

      if (state & BUTTON_DOWN)
      {
         VDP_drawText("DOWN button 1", 5, 18);
      }
      else if (changed & BUTTON_DOWN)
      {
         VDP_drawText("DOWN button 0", 5, 18);
      }

      if (state & BUTTON_LEFT)
      {
         VDP_drawText("LEFT button 1", 5, 19);
      }
      else if (changed & BUTTON_LEFT)
      {
         VDP_drawText("LEFT button 0", 5, 19);
      }

      if (state & BUTTON_RIGHT)
      {
         VDP_drawText("RIGHT button 1", 5, 20);
      }
      else if (changed & BUTTON_RIGHT)
      {
         VDP_drawText("RIGHT button 0", 5, 20);
      }
   }


   // otras funciones interesante
   // JOY_update() refresca el estado del pad, se llama en cada refresco de la pantalla
    // JOY_readJoypad( joy ) ¨devuelve el estado del pad1
    // JOY_waitPressBtn() espera a que se pulse un boton (no direcciones)
    // JOY_waitPress(joy, BUTTON_A | BUTTON_UP) espera a pulsar un boton indicado en un pad especifico

}//end myJoyHandler()


B - Función MAIN()

Ahora volvemos a la función main(), donde iniciamos el manejador de los controles, establecemos la función de control myJoyHandler() anterior que se ejecutará automáticamente y entramos en un bucle infinito.

////////////////////////////////////////////////////////////////////
// MAIN
////////////////////////////////////////////////////////////////////
int main( )
{
   // resetea el manejador y lee la informacion de los mandos conectados
   JOY_init();

    //Establecemos que el programa dará soporte al pad de 6 botones
    //conectado en el puerto 1
   JOY_setSupport(PORT_1, JOY_SUPPORT_6BTN);
    //Los otros valores correspondientes al resto de controles soportados
    //en las librerías SGDK son JOY_SUPPORT_OFF, JOY_SUPPORT_3BTN,
    //JOY_SUPPORT_MOUSE, JOY_SUPPORT_TRACKBALL, JOY_SUPPORT_TEAMPLAYER,
    //JOY_SUPPORT_EA4WAYPLAY, JOY_SUPPORT_MENACER, JOY_SUPPORT_JUSTIFIER_BLUE,
    //JOY_SUPPORT_JUSTIFIER_BOTH, JOY_SUPPORT_ANALOGJOY, JOY_SUPPORT_KEYBOARD

   //A través de la función siguiente conseguimos que nuestra funcion
   //creada anteriormente sea llamada cada vez que se cambia el estado de un boton
   JOY_setEventHandler( &myJoyHandler );

    //Dibujamos con dos líneas de texto una cabecera austera
   VDP_drawText("Ejemplo de control del PAD", 5, 10);
   VDP_drawText("--------------------------", 5, 11);

    //Y rellenamos el resto del espacio escribiendo cadenas que indiquen
    //que inicialmente todos los estados están a cero (nada pulsado)
   VDP_drawText("START button 0", 5, 13);
   VDP_drawText("A button 0", 5, 14);
   VDP_drawText("B button 0", 5, 15);
   VDP_drawText("C button 0", 5, 16);
   VDP_drawText("X button 0", 17, 14);
   VDP_drawText("Y button 0", 17, 15);
   VDP_drawText("Z button 0", 17, 16);

   VDP_drawText("UP button 0", 5, 17);
   VDP_drawText("DOWN button 0", 5, 18);
   VDP_drawText("LEFT button 0", 5, 19);
   VDP_drawText("RIGHT button 0", 5, 20);

    //BUCLE PRINCIPAL
    //En este caso sólo tendrá en cuenta el manejador del input que
    //hemos creado y establecido con JOY_setEventHandler anteriormente
    while(1){

        // Sincroniza la pantalla de modo que el barrido vertical no
        // interfiera con el dibujo de tiles, sprites o nuestro texto,
        // evitándonos algunos efectos indeseados de flickering
        // principalmente
        VDP_waitVSync();
    }

    return (0); // Sin efecto funcional en nuestro juego o programa,
                // pero de obligada aparición al final de una función
                // main que no se ha declarado como "void main()"

}//end main()


Y ya está, ya tenemos control de todos los botones del PAD1, aquí os dejo un fichero con el código por si no teneis ganas de teclear [+risas]

https://dl.dropbox.com/u/33369593/main.c

C - Deberes

Como deberes, añadir soporte para el mando del segundo jugador. En el próximo tutorial, empezamos con los gráficos, dibujaremos tiles en pantalla!

Dar las gracias de nuevo a realbrucest por su ayuda!
DarkRyoga
FPGA Warrior!!!
2.002 mensajes
desde sep 2011
en World 9 - Warp Zone
gran hilo. a ver si saco un poco de tiempo para seguirlo tranquilamente.
tlmx
Adicto
212 mensajes
desde abr 2008
Genial hilo, me esta sirviendo para esos ratos muertos que tengo en el curro. A ver si sale algo jugable de las chapuzas que estoy intentando hacer.
bertobp
Clasicómano
11.139 mensajes
desde abr 2011
en Barcelona
CHINCHETA!! Como debe ser ;)
1, 2, 3, 4, 532