Tutorial de programacion de Nintendo DS. Dia 1 – Carga de fondos desde la FAT

Día 1. Carga de fondos “tileados” desde FAT.
 
 

Introduccion
 
Bueno, pasemos a las cosas serias. Como doy por supuesto que ya sabemos programar mínimamente bien en C (cuidado, que yo no soy ni de lejos un lumbreras), pasaremos a las cosas que se salen de lo normal en la DS, como la carga y manejo de fondos, sprites, interface de entrada y sonido. Lo demás como es C de toda la vida, pues casi no tiene sentido que lo explique yo aquí, habiendo cientos de tutoriales (mucho mejores de los que podría yo realizar) sobre programación en C.

El tema que tocare hoy es referente a la carga de fondos, usando uno de los modos nativos que soporta el motor 2D de la DS, los gráficos tileados. Los gráficos tileados no son más que gráficos que han sido previamente convertidos en un formato especial, compuesto de 3 partes, para ahorrar espacio. En el caso de la DS, estos gráficos se componen en el archivo de tiles, donde la imagen se ha cortado en bloques de 8×8 pixeles (1 tile) y se han guardado todos juntos. El siguiente archivo necesario es el de mapa de tiles (MAP) que indica a la DS donde colocar cada tile del otro archivo en la pantalla. Esto es útil de cara a ahorrar memoria (la DS tiene poco mas de 512kb de memoria de video para ambas pantallas), ya que si en nuestro fondo se repite mucho un tile (un ladrillo por ejemplo), el grafico de este ladrillo solo se guarda 1 vez y se carga 1 sola vez en la VRAM y luego el archivo de mapa le indica a la DS cuantas veces debe de colocar ese ladrillo en pantalla y donde, con lo cual, si en pantalla tenemos 20 ladrillos, en memoria solo habrá cargado 1, pero la ds gracias al sistema de tiles nos mostrara los 20, con el consecuente ahorro de recursos. La tercera y última parte del sistema de tileado es el archivo de paleta, que indica que colores usara nuestro fondo. Debéis de tener en cuenta que este tipo de fondos serán siempre a 256 colores (color indexado) para poder usarlos.

La última release del devKitPro incluye una fabulosa utilidad llamada GRIT, la cual convertirá nuestros archivos de imagen en los 3 archivos que necesitamos para que la DS “lo entienda”.
La mayoría de ejemplos sobre gráficos tileados se basan en integrar los gráficos una vez convertidos dentro de la propia ROM generada. Cosa buena, en una sola ROM todo solucionado y funcionando. Cosa mala, como tu proyecto ocupe mas de 4mb, olvídate de que funcione, dado que esa es la RAM que tiene disponible la DS para trabajar. Así que para evitar problemas futuros, más vale hacer las cosas ya bien desde el principio y aprender a cargar los fondos, sprites y demás desde la FAT (sistema de archivos de vuestra flashcard) y tener solo en la ROM el ejecutable, con lo cual tendremos más flexibilidad a la hora de trabajar.

Después de esta pequeña introducción, hoy vamos a aprender a:
1. Inicializar el sistema FAT y cargar archivos desde ella a la RAM
2. Inicializar el motor 2D de la DS y cargar en la VRAM un fondo
Ademas también os enseñare a convertir los graficos al formato “tileado” con la utilidad GRIT.
 
 

Codigo.

 
Este es el código de la lección de hoy:

//
// Leccion 1
// Carga de fondos "tileados" desde FAT
// Ejemplo por NightFox
//

// Includes NDS
#include <nds.h>
// Includes FAT
#include <fat.h>
// Includes comunes
#include <stdio.h>
#include <string.h>
#include <unistd.h>
 
 

// Main
int main(void) {

 // Inicializa el sistema de interrupciones
   irqInit();
   // y habilita el vblank para poder usar swiWaitForVblank()
   irqEnable(IRQ_VBLANK);

 // Inicializa el sistema FAT
 consoleDemoInit(); // Inicializa la consola de texto
 iprintf("Inicializando FAT.n");
 if (fatInitDefault()) {  // Intenta inicializar la FAT
  // Conseguido, continua
 } else {
  // Fallo. Deten el programa
  iprintf("Fallo en la inicializacion.n");
  iprintf("Programa detenido.n");
  // Bucle infinito. Fin del programa
  while(1) {
   swiWaitForVBlank();
  }
 }

 // Define los Buffers para almacenar los graficos
 char* BUFFER_TILES;  // Buffer para alamacenar los tiles
 char* BUFFER_MAP;  // Buffer para alamacenar el mapa
 char* BUFFER_PAL;  // Buffer para almacenar la paleta

 // Inicializa los buffers (asi evitamos cargar los datos vete a saber tu donde)
 BUFFER_TILES = NULL;
 BUFFER_MAP = NULL;
 BUFFER_PAL = NULL;

 // Variable para almacenar el path y el nombre de archivo
 char filename[256];

 // Carga el archivo de TILES en la RAM
 long tiles_size = 0;  // Alamcenamos el tamaño del archivos de tiles
 FILE* tiles_file;  // Referencia al archivo de tiles
 sprintf(filename, "leccion01/fondo.img"); // Guarda el path + archivo
 tiles_file = fopen(filename, "rb");    // Y abre el archivo (modo lectura)

 if (tiles_file) {  // Si el archivo existe
  // Obten el tamaño del archivo
  fseek(tiles_file, 0, SEEK_END);
  tiles_size = ftell(tiles_file);
  rewind(tiles_file);
  // Reserva el espacio en RAM
  BUFFER_TILES = (char*) calloc (tiles_size, sizeof(char));
  if (BUFFER_TILES == NULL) {  // Si no hay suficiente RAM libre
   iprintf("RAM insuficiente.n");
   iprintf("Archivo: %ld bytesn", tiles_size);
   while(1) {
    swiWaitForVBlank();
   }
  }
  // Lee el archivo y ponlo en la RAM
  fread (BUFFER_TILES, 1, tiles_size, tiles_file);

 } else { // Archivo no encontrado
  iprintf("Archivo no encontrado.n");
  while(1) {
   swiWaitForVBlank();
  }
 }

 fclose(tiles_file);  // Cierra el archivo
 swiWaitForVBlank();  // Espera al cierre del archivo

 // Carga el archivo de MAP en la RAM
 long map_size = 0;  // Alamcenamos el tamaño del archivos de tiles
 FILE* map_file;  // Referencia al archivo de tiles
 sprintf(filename, "leccion01/fondo.map"); // Guarda el path + archivo
 map_file = fopen(filename, "rb");    // Y abre el archivo (modo lectura)

 if (map_file) {  // Si el archivo existe
  // Obten el tamaño del archivo
  fseek(map_file, 0, SEEK_END);
  map_size = ftell(map_file);
  rewind(map_file);
  // Reserva el espacio en RAM
  BUFFER_MAP = (char*) calloc (map_size, sizeof(char));
  if (BUFFER_MAP == NULL) {  // Si no hay suficiente RAM libre
   iprintf("RAM insuficiente.n");
   iprintf("Archivo: %ld bytes", map_size);
   while(1) {
    swiWaitForVBlank();
   }
  }
  // Lee el archivo y ponlo en la RAM
  fread (BUFFER_MAP, 1, map_size, map_file);

 } else { // Archivo no encontrado
  iprintf("Archivo no encontrado.n");
  while(1) {
   swiWaitForVBlank();
  }
 }

 fclose(map_file);  // Cierra el archivo
 swiWaitForVBlank();  // Espera al cierre del archivo

 // Carga el archivo de PAL en la RAM
 long pal_size = 0;  // Alamcenamos el tamaño del archivos de tiles
 FILE* pal_file;  // Referencia al archivo de tiles
 sprintf(filename, "leccion01/fondo.pal"); // Guarda el path + archivo
 pal_file = fopen(filename, "rb");    // Y abre el archivo (modo lectura)

 if (pal_file) {  // Si el archivo existe
  // Obten el tamaño del archivo
  fseek(pal_file, 0, SEEK_END);
  pal_size = ftell(pal_file);
  rewind(pal_file);
  // Reserva el espacio en RAM
  BUFFER_PAL = (char*) calloc (pal_size, sizeof(char));
  if (BUFFER_PAL == NULL) {  // Si no hay suficiente RAM libre
   iprintf("RAM insuficiente.n");
   iprintf("Archivo: %ld bytes", pal_size);
   while(1) {
    swiWaitForVBlank();
   }
  }
  // Lee el archivo y ponlo en la RAM
  fread (BUFFER_PAL, 1, pal_size, pal_file);

 } else { // Archivo no encontrado
  iprintf("Archivo no encontrado.n");
  while(1) {
   swiWaitForVBlank();
  }
 }

 fclose(pal_file);  // Cierra el archivo
 swiWaitForVBlank();  // Espera al cierre del archivo

 // Debug (Informa del tamaño de los archivos)
 iprintf("Tiles: %ld bytesn", tiles_size);
 iprintf("Map: %ld bytesn", map_size);
 iprintf("Pal: %ld bytesn", pal_size);
 iprintf("Carga ok.n");

 

 // Inicializa el motor 2D en "Modo 0" en la pantalla principal y habilita el fondo nº0
 videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE);

 // Define el banco de VRAM "A" para usarlo con el fondo
 vramSetBankA(VRAM_A_MAIN_BG);

 // Habilita el fondo nº0
 // BgType_Text8bpp indica que usaremos un fondo "Tileado" a 256 colores
 // BgSize_T_256x256 indica que el tamaño del fondo es de 256×256
 // Alamcena la ID exclusiva del fondo en "fondo"
 int fondo = bgInit(0, BgType_Text8bpp, BgSize_T_256x256, 0,1);

 // Usa el memcpy para copiar los datos de los Buffers en RAM a la VRAM
 // bgGetGfxPtr(fondo) – Obten el puntero donde almacenar los TILES en VRAM
 // bgGetMapPtr(fondo) – Obten el puntero donde almacenar el MAP en VRAM
 memcpy(bgGetGfxPtr(fondo), BUFFER_TILES, tiles_size);
 memcpy(bgGetMapPtr(fondo), BUFFER_MAP, map_size);
 memcpy(BG_PALETTE, BUFFER_PAL, pal_size);

 // Bucle (repite para siempre)
 while(1) {
 
  swiWaitForVBlank();  // Espera al sincronismo vertical
 
 }

 return 0;

}
 
He comentado el código lo suficiente como que sea auto explicativo (espero), de todas maneras si tenéis dudas, me dejáis un comentario en el blog.
La primera parte del código inicializa lo básico de la DS, interrupciones y demás. A continuación intentamos iniciar y acceder al sistema FAT de nuestra flashcard, de lograrlo, el programa continua. En caso de no poder ser, nos informa del error y detiene la ejecución del programa. A continuación declaramos e inicializamos 3 Buffers de memoria para poder almacenar en RAM los 3 archivos de los que se compone nuestro fondo. Acto seguido, procedemos a la carga de los archivos en si, esta parte de código, intenta abrir el archivo, devolviendo error si no se encuentra, obtiene el tamaño de dicho archivo y lo almacena en una variable, muy importante para más tarde poder dimensionar el buffer en RAM y su posterior copia en VRAM y finalmente, lee el archivo en el buffer correspondiente. He puesto un par de rutinas extras para verificar si ha sido correcta la carga del archivo en RAM y evitar disgustos.
La ultima parte simplemente inicializa el motor 2D de la DS y le dice que queremos iniciar una capa de fondo en la pantalla superior, usando la capa 0 (la más alta) con un fondo de 256×256 pixeles a 256 colores. Llegados a este punto, debéis saber que el hardware de la DS, sin hacer “trampas” solo soporta fondos desde 256×256 pixeles hasta 512×512 pixeles y sus variantes (512×256, 256×512). Para cargar fondos más grandes, ya tendremos de rascar código, pero eso será otro día).
 
El resultado del programa del ejemplo nos mostrara en la pantalla superior el fondo de muestra que hemos cargado y un texto de debug en la inferior, indicándonos el resultado de la inicialización del sistema FAT y el tamaño en bytes de los 3 archivos cargados.
Recordar en usar el MAKEFILE incluido en mi ejemplo, que ha sido modificado para usar las librerías FAT.
 
 
Uso de la utilidad GRIT.
 
Aquí aprenderemos a convertir un archivo BMP indexado a 256 colores a los 3 archivos que necesitaremos para nuestro ejemplo. La utilidad GRIT se ejecuta desde línea de comandos, así que para simplificar la cosa, he creado un archivo BAT que le pasa los parámetros más comunes para la conversión de los archivos BMP a TILES y de paso renombra los archivos generados y limpia los más necesarios. En la carpeta GRIT del ejemplo tenéis todo lo necesario.
Para convertir el archivo “fondo.bmp” a los 3 archivos necesarios, simplemente ejecutar desde línea de comandos:
 
convert fondo
 

Esto generara en la misma carpeta 3 archivos, “fondo.img”, que contiene los tiles, “fondo.map”, que contiene el mapa y “fondo.pal” que contiene la paleta.
Si queréis trastear vosotros con las posibilidades de esta utilidad, ejecutar la aplicación GRIT.EXE y veréis todos los parámetros disponibles. Mi BAT usa los siguientes:
 
grit.exe [archivo] -g -m -p -gB8 –ftb
 
Podéis también ver como está hecho el BAT simplemente abriendo el archivo “convert.bat”
 
 
Prueba del ejercicio.
 
Si el ejemplo ha compilado bien (debería si has usado el material adjunto a este tutorial), solo queda probarlo en la DS. Por algún bug en el emulador IDEAS, de momento no es posible probarlo en el emulador, así que tocara probarlo con la consola y el flashcard.
Crea una carpeta en la raíz de tu flashcard llamada “leccion01” y copia dentro el binario compilado (leccion01.nds) y parchéalo con DLDI si es necesario (en los flashcards mas reciente, no lo es) y los 3 archivos del fondo (fondo.img, fondo.map y fondo.pal).
Si todo es correcto, aparecerá el fondo en la pantalla superior (abre en tu PC el archivo fondo.bmp para compararlo) y en la pantalla inferior texto informando de la carga y tamaño de los archivos. Te animo a probar este ejemplo con tu propio fondo, el único punto a respetar es que sean BMP de 256×256 pixeles indexados a 256 colores.
 
 
Y esto es todo por hoy, espero que os haya gustado el tutorial, haya sido claro y de provecho para vosotros.
Aquí tenéis el enlace con todo el material necesario para esta lección
Proyecto en Visual C++:  
//www.mediafire.com/?uctiqhmwfmd
GRIT con ejemplos:  //www.mediafire.com/?nnrz4mrmdgz
Tambien podeis descargar el WORD de este tutorial aquí: //www.mediafire.com/?tynmyyzouhy
 
Un cordial saludos a todos
 
NightFox