15 mayo 2005

Primer proyecto rápido

Cuando uno empieza a programar, está ávido de ver resultados. En este caso, la sensación es la misma, así que tras un vistazo rápido a la documentación para aprender a generar gráficos en la pantalla de la consola y a detectar la pulsación de sus botones nos lanzamos a la tarea.

Lo que se me ha ocurrido ha sido hacer una conversión directa del juego de la serpiente (snake) que realicé para la competición de juegos en BASIC para Spectrum organizada por Bytemaniacos. Es una versión muy sencilla, sin incrementos de velocidad, ni gráficos. Tan solo una serpiente recogiendo frutas por la pantalla. Así que procedemos a descargar el código fuente en BASIC para proceder a la conversión.

Si miráis el código es muy sencillo. Se trata de un bucle en el que se mueve la serpiente, se comprueba si ha chocado con algo o si ha comido una fruta y se lee el teclado para ver si hay que cambiar de dirección. Este esquema ha sido adaptado de forma cutre salchichera a lenguaje C en 10 minutos, para poder ver resultados rápidamente.

La consola tiene registros mapeados en memoria. Pero para acceder a la mayoría de ellos se han definido constantes y macros en los archivos de cabecera, de forma que no tengamos que recordar las direcciones de memoria.

Los principales problemas que he encontrado han sido:

¿Cómo dibujar objetos en pantalla?
La consola tiene varios modos de vídeo. Más adelante estudiaremos todos. De momento basta con saber que el Modo 3 nos permite un acceso lineal a la memoria de vídeo, donde el color de cada pixel vendrá determinado por un valor de 16 bits. Para acceder a este modo de vídeo, lo haremos de la siguiente manera:
SetMode(MODE_3 | BG2_ENABLE);
Tanto la función como las constantes están definidas en los archivos de cabecera.

Para colorear un punto de la pantalla accederemos a la siguiente variable (definida en los archivos de cabecera):
VideoBuffer[x + y * SCREEN_WIDTH] = color;
donde SCREEN_WIDTH está definida en los archivos de cabecera (la pantalla en Modo 3 es de 240x160) y color, como hemos comentado anteriormente, es un valor entero de 16 bits, que podemos generar de la siguiente forma:
color = RGB16(componente_roja, componente_verde, componente_azul);
donde los valores de cada componente son enteros de 5 bits (o sea, entre 0 y 31).

¿Cómo leer la pulsación de los botones de la consola?

Podemos consultar el valor del registro REG_KEYS de la siguiente forma:
!(REG_KEYS & botón)
de forma que obtendremos 1 (true) si el botón indicado ha sido pulsado. Las constantes para los botones son las siguientes:
  • KEY_LEFT (pad izquierda)
  • KEY_RIGHT (pad derecha)
  • KEY_UP (pad arriba)
  • KEY_DOWN (pad abajo)
  • KEY_A (botón A)
  • KEY_B (botón B)
  • KEY_L (gatillo L)
  • KEY_R (gatillo R)
  • KEY_SELECT (botón Select)
  • KEY_START (botón Start)
¿Cómo "imprimir caracteres" en la pantalla?

Aquí no vale usar printf e imprimir una cadena en pantalla. Así que necesitaremos definirnos un juego de caracteres y construirnos las rutinas de escritura en pantalla. De momento sólo necesitamos números para el marcador, así que lo hemos hecho "a las bravas", definiendo un array de 8x8 para cada carácter numérico, poniendo un 1 en los pixels que deben ir iluminados. Por ejemplo, para el número cero (0):
    {
{0,0,0,0,0,0,0,0},
{0,0,1,1,1,0,0,0},
{0,1,0,0,0,1,0,0},
{1,0,0,0,0,0,1,0},
{1,0,1,1,1,0,1,0},
{1,0,0,0,0,0,1,0},
{0,1,0,0,0,1,0,0},
{0,0,1,1,1,0,0,0}
},

Por tanto, definimos una variable global llamada caracteres en la que almacenamos nuestro rudimentario juego de caracteres numéricos, del 0 al 9. La rutina de impresión es tan sencilla como:

void PintaCaracter(caracter,x,y,color,color_fondo)
{
unsigned char tx, ty;
for(tx = 0; tx < 8; tx ++)
{
for(ty = 0; ty < 8; ty ++)
{
if(caracteres[caracter][ty][tx] == 1)
{
VideoBuffer[(x+tx) + (y+ty) * SCREEN_WIDTH] = color;
}
else
{
VideoBuffer[(x+tx) + (y+ty) * SCREEN_WIDTH] = color_fondo;
}
}
}
}
El juego va muy rápido, ¿cómo lo ralentizamos?

La solución buena y elegante es efectuar la sincronización del movimiento mediante interrupciones. En este primer ejemplo no lo vamos a hacer así, así que emplearemos métodos de espera activa, tanto para ralentizar cada iteración (mediante un bucle for vacío), como para esperar a la pulsación de una tecla (mediante un while del que no se sale hasta que se pulsa dicha tecla). Tampoco esperamos al redibujado de la pantalla para escribir en la memoria de vídeo y así evitar molestos parpadeos. Todas estas técnicas las iremos viendo más adelante, conforme vayamos profundizando en nuestro conocimiento del hardware de la Gameboy Advance.

Y con estas salvedades y un código muy chapucero, tenemos en pantalla la primera versión de nuestro juego de la serpiente. Cuando choquemos, hay que pulsar Start para volver a empezar la partida. Aquí tenéis la ROM con el juego listo para ser ejecutado.


Ver mi primer código corriendo en la GBA no tiene precio

2 Comentarios:

Blogger sromero dijo...

Muy buenos los enlaces que das en estas 3 primeras "historias" de tu nuevo weblog. Mirando este texto y uno de los tutoriales me he dado cuenta de que la GBA es lo más parecido que hay a los tiempos de programar en modo 13H en MSDOS.

Ha sido una lástima llegar tarde al desarrollo en la GBA, porque creo que me hubiera gustado implicarme en ello. ¿Sabes si la NDS se programa igual?

Un saludo.

10:04 a. m.  
Blogger falvarez dijo...

Sí, en la página de Drunken Coders hay material acerca de NDS. De todas formas, no creo que se programe una manera muy distinta.

Habrá que estar atentos. En cualquier caso, a la GBA todavía le queda cuerda. Y programando con cuidado, no debería ser muy difícil portar los desarrollos a otras plataformas.

10:36 a. m.  

Publicar un comentario

<< Home