DESARROLLO SOFTWARE - Proyectos de DarkRyoga

1, 2, 3, 4, 5
Perfectamente explicado y sencillo de entender. Voy a trastear un poco a ver que consigo ;)
OK, me alegro de que sea así. Para cualquier problema ya sabeis que podeis utilizar este mismo hilo para ir preguntando.
Donde puedo conseguir la documentación del gbdk? De gba encuentro documentación y tutos a patadas, pero de gb tela para conseguir algo en condiciones.
Yo tengo desde hace tiempo este PDF: http://gbdk.sourceforge.net/doc/gbdk-doc.pdf , que es un buen resumen pero al que le faltan bastantes explicaciones (cosa que según vaya avanzando con las entradas relacionadas con el desarrollo para GameBoy voy a intentar ir corrigiendo). Por otro lado suelo visitar bastante esta web aunque hay que tener en cuenta que tiene bastantes enlaces caidos: http://www.devrs.com/gb/ccode.php
Muy bueno el tuto, Ryoga, hasta he sido capaz de haceme mi propio contador. El problema es que eso de añadir el directorio a la variable de entorno me ha costado un poco porque no me enteraba de como iba, y luego con lo de convertir el *.c a *.gb me estaba dando fallo por lo de copiar el programa tal cual del post. Bueno, el caso es que al final me ha salido y ya me he buscado una guía para uber-n00bs en C para seguir practicando.

La duda que tengo ahora es si hay un listado con los caracteres especiales que puede mostrar la GB, es decir, a parte de /n y el /t si se pueden usar otros.

Saludos.
JSL escribió:La duda que tengo ahora es si hay un listado con los caracteres especiales que puede mostrar la GB, es decir, a parte de /n y el /t si se pueden usar otros.


No se si habrá algún listado con caracteres especiales aunque en principio no creo que haya problema con el uso de otras secuencias típicas del lenguaje C. Tened en cuenta que todo el código C que se va a escribir usando lo que nos proporciona el GBDK luego se va a traducir al lenguaje ensamblador del procesador de la GameBoy así que todo lo que permita hacer el GBDK está directamente ligado a lo que permita la máquina a más bajo nivel. De todas formas todo es probar: http://en.wikipedia.org/wiki/Escape_sequences_in_C
DarkRyoga escribió:Yo tengo desde hace tiempo este PDF: http://gbdk.sourceforge.net/doc/gbdk-doc.pdf , que es un buen resumen pero al que le faltan bastantes explicaciones (cosa que según vaya avanzando con las entradas relacionadas con el desarrollo para GameBoy voy a intentar ir corrigiendo). Por otro lado suelo visitar bastante esta web aunque hay que tener en cuenta que tiene bastantes enlaces caidos: http://www.devrs.com/gb/ccode.php


Precisamente esa documentación era la que estuve viendo ayer, todo bien hasta que llegas a la mayoría de secciones y lo único que lees es "blah" xD
tuviello escribió:
Precisamente esa documentación era la que estuve viendo ayer, todo bien hasta que llegas a la mayoría de secciones y lo único que lees es "blah" xD


Yo no encontré mucho más, sin embargo no será problema pues tengo material suficiente para hacer dos o tres entradas más relacionadas con GameBoy antes de meterme de lleno con GBA que es lo que quería desde un principio. Espero tener la siguiente entrada lista dentro de poco.
Tiene bastante buena pintaca. Voy a ver si consigo hacerlo funcionar.
No estoy desaparecido. Próximamente más.
TUTORIAL DE PROGRAMACIÓN PARA GAME BOY - PARTE #2

Espero que hayais aprovechado estos días para practicar algo y hacer vuestros propios ejemplos controlando el PAD y los botones de acción de la GameBoy así como para mejorar vuestros conocimientos del lenguaje C a nivel general. En el día de hoy vamos a hablar un poco de la entrada/salida de texto en GameBoy. En principio no tenía pensado hacer esta entrada, pero me he topado con una situación especial preparando la que será la siguiente entrada relacionada con la programación en GameBoy (la tercera que espero no se demore demasiado) que me lo ha hecho pensar dos veces. Como vimos en la primera entrada es muy sencillo sacar texto por la pantalla de la gameboy usando la función printf() que está incluida en stdio.h. ¿Pero que pasa si queremos introducir texto? ¿se puede sacar texto por la pantalla de la gameboy usando un estilo de fuente diferente al habitual? Estas y otras preguntas me asaltaron en los últimos días como consecuencia (como decía antes) de la preparación de entradas posteriores. En esta segunda entrada tengo pensado abordar la entrada de datos (en este caso texto) con un sencillo programa para la GameBoy. El tema de las fuentes los veremos un poco más adelante con un ejemplo más completo que el programa que os propongo hoy aquí. Echadle un vistazo inicial a la siguiente captura de pantalla y al código fuente:

Imagen

/*
Ejemplo de entrada y salida en GameBoy.
*/

#include <gb/gb.h>
#include <string.h>
#include <ctype.h>
#include <gb/drawing.h>
#include <stdio.h>
#include <gb/console.h>

char string[11];         
char password[]="PROGRAMMING";

void clscr(){                               
   int x, y;

     for(y = 0; y < 20; y++)       
       for(x = 0; x < 30; x++){   
            gotoxy(x, y);             
            setchar(' ');
       }   
   gotoxy(0,0);                 
}


void main(){
   char c='a';
   int x=0;

   gotoxy(1,1);
   puts("\t\t\t\t\t\t\t\tRYOGA\t\t\t\t\t\t\t");
   puts("I/O in GB");

   gotoxy(1,5);
     puts("Login Password :\n");   
     gets(string);
 
   while(c){
      c = string[x];
      c = toupper(c);
      string[x] = c;
      x++;
   }

   clscr();


   x=strcmp(string,password);
     if(x==0){
        puts("Correct Password !");
        puts("This is a simple example to control input and output");
        puts("");
     }else {
       puts("\n\n    Incorrect");
    }
}


El primer bloque son los típicos includes donde está definidas las cebeceras de algunas funciones que se utilizarán a lo largo del programa. Los nuevos includes que veremos aquí con respecto al primer ejemplo son:

#include <string.h>
#include <ctype.h>
#include <gb/drawing.h>
#include <gb/console.h>


Por el momento nos interesan los siguientes:

<string.h> - se trata de un fichero de cabecera muy conocido en el mundo de la programación en C. Al igual que con <stdio.h> aquí nos encontramos con una versión propia para el GBDK pero que incluye las funciones más utilizadas fuera del ámbito del desarrollo para gameboy. Podemos decir que se trata de una colección de funciones para manejar cadenas de caracteres. (Comparación, búsqueda, etc).

<drawing.h> - este fichero de cabecera va a dar bastante que hablar en próximas entradas porque permite dibujar sprites en pantalla, dibujar cajas de texto, formas poligonales, círculos... incluso imágenes convenientemente tratadas. No la perdais de vista.

<console.h> - para este ejemplo en concreto va a ser esencial porque gracias a ella vamos a manejar muy fácilmente la entrada de texto en nuestro programa.

Si le habeis echado un vistazo rápido al código del programa y lo habeis entendido habreis llegado a la conclusión de que se trata de un sistema muy básico de login mediante password. El programa muestra por pantalla una caja de texto con los caracteres posibles a escribir, el usuario introduce la palabra clave y si es correcta saca un mensaje por pantalla afirmativo y en caso contrario un mensaje de error. Vamos a verlo más a fondo.

char string[11];         
char password[]="PROGRAMMING";


El primer array de caracteres lo vamos a utilizar para almacenar nuestro texto introducido por el usuario, mientras que el segundo array de caracteres almacena la palabra clave password. A continuación viene una función auxiliar que he creado para este programa pero que es muy probable que use para otros más adelante.

void clscr(){                               
   int x, y;

     for(y = 0; y < 20; y++)       
       for(x = 0; x < 30; x++){   
            gotoxy(x, y);             
            setchar(' ');
       }   
   gotoxy(0,0);                 
}


¿Habeis averiguado para que sirve? ¡Exacto!, para borrar la pantalla y dejarla completamente en blanco. Puede que parezca una obviedad pero esta operación de "limpiado" de la pantalla que en MS-DOS se realiza con el comando "CLS" y que desde un programa en C para Windows o Linux se resolvería fácilmente (y de forma nativa que no es la mejor ni la más adecuada si se quiere desarrollar un software multiplataforma) haciendo uso de la llamada al sistema "system()" (system("cls") para Win y system("clear") para sistemas Unix/Linux) es un auténtico quebradero de cabeza en un sistema tan limitado como GameBoy que para empezar ni si quiera tiene un sistema operativo. No voy a pararme mucho en esta parte pero os diré que la función va posición a posición en cada linea vertical (y=20 lineas en vertical) y horizontal (x=30 lineas en horizontal) poniendo un carácter espacio en blanco o ' ' mediante la función setchar() en cada posición de la linea según los dos bucles FOR (si no sabeis que es un bucle FOR por favor mirad algún manual de C. Al igual que while es un búcle iterativo con la diferencia que el FOR lo usamos cuando sabemos desde el principio el número de veces que tiene que ejecutarse mientras que el while lo solemos usar cuando el límite de veces no se conoce a priori o dependen de alguna condición específica del programa). Finalmente la función gotoxy() se utiliza para poner el cursor de la pantalla en las coordenadas (0,0) es decir en la zona superior izquierda para volver a escribir o pintar algo desde esa posición.

A continuación viene el código de la función principal del programa o función MAIN:

void main(){
   char c='a';
   int x=0;

   gotoxy(1,1);
   puts("\t\t\t\t\t\t\t\tRYOGA\t\t\t\t\t\t\t");
   puts("I/O in GB");

   gotoxy(1,5);
     puts("Login Password :\n");   
     gets(string);
 
   while(c){
      c = string[x];
      c = toupper(c);
      string[x] = c;
      x++;
   }

   clscr();


   x=strcmp(string,password);
     if(x==0){
        puts("Correct Password !");
        puts("This is a simple example to control input and output");
        puts("");
     }else {
       puts("\n\n    Incorrect");
    }
}


De aquí lo nuevo que no ha aparecido antes en este programa son las siguientes funciones:

toupper() - función incluida en <ctype.h> y que permite obtener un carácter en MAYÚSCULAS a partir del mismo carácter en minúsculas.
puts() - función que podemos encontrar en <console.h> y que permite mostrar un texto por pantalla tal cual.
gets() - función que podemos encontrar en <console.h> y que permite mostrar en pantalla el recuadro con los caracteres que podemos seleccionar con el PAD y los botones de acción, todo de forma automática y sin realizar mayor control por nuestra parte. Tiene como parámetro de entrada el array de caracteres o el puntero a char = (char*) donde se almacenarán los caracteres introducidos por teclado por parte del usuario.

strcmp() - función incluida en <string.h> que nos permite comparar dos cadenas de caracteres. Como veis en el ejemplo la estoy usando para comparar la palabra clave fija "PROGRAMMING" con la cadena (previamente formada en el búcle WHILE) y que tiene los caracteres introducidos por el usuario. Devuelve un 0 si ambas cadenas son iguales y un número distinto de 0 sin son diferentes (-1 si la primera cadena menor que la segunda y 1 si la primera cadena es mayor que la segunda en número de caracteres).

Y finalmente los IF del final para saber en función del resultado de la comparación que texto escribir por pantalla. Como veis el programa no es especialmente complicado pero hay que tener los conceptos claros. Espero que le sigais dando caña al C por vuestra cuenta y por supuesto para cualquier duda aquí estamos. Espero volver a la carga dentro de poco con una nueva entrada más interesante que además vendrá con video.

Saludos.
Perfecto como siempre, a la espera del siguiente [360º]
Antes de ponerme con el segundo tutorial:
DarkRyoga escribió:Podeis hacer algún que otro ejercicio adicional para controlar el resto de botones de la consola, o que se muestren textos alternativos en función del botón del PAD pulsado y cosas así para ir practicando antes de la siguiente entrada.

¿Podrías poner un ejemplo de como se haría eso?
JSL escribió:Antes de ponerme con el segundo tutorial:
DarkRyoga escribió:Podeis hacer algún que otro ejercicio adicional para controlar el resto de botones de la consola, o que se muestren textos alternativos en función del botón del PAD pulsado y cosas así para ir practicando antes de la siguiente entrada.

¿Podrías poner un ejemplo de como se haría eso?


En realidad sería muy sencillo de realizar. Si recuerdas en la primera entrada dejé puestas todas las constantes del PAD de la GameBoy los cuales está incluidos en el fichero de cabecera gb.h .

J_START
J_SELECT
J_B
J_A
J_DOWN
J_UP
J_LEFT
J_RIGHT


Un programa que usando la función puts() mostrase por pantalla un mensaje diferente para cada botón del PAD pulsado podría tener la siguiente pinta:

include <gb.h>

void main(){
     int key;

     while(1){
        key=joypad();

    if(key & J_RIGHT)   {                           
        gotoxy(0,0);
        puts("Has pulsado DERECHA !  ");
  }

  if(key & J_LEFT)           
  {
  gotoxy(0,0);
  puts("Has pulsado IZQUIERDA !   ");
  }

  if(key & J_UP)             
  {
  gotoxy(0,0);
  puts("Has pulsado ARRIBA !     ");
  }

  if(key & J_DOWN)
  {
  gotoxy(0,0);
  puts("Has pulsado ABAJO !   ");
  }

  if(key & J_A)
  {
  gotoxy(0,0);
  puts("Has pulsado A !      ");
  }

  if(key & J_B)
  {
  gotoxy(0,0);
  puts("Has pulsado B !      ");
  }

  if(key & J_START)
  {
  gotoxy(0,0);
  puts("Has pulsado Start !  ");
  }

  if(key & J_SELECT)
  {
  gotoxy(0,0);
  puts("Has pulsado Select ! ");
  }
     }
}


La función joypad() comprueba que se haya pulsado algún botón del pad de la consola, y luego la comprobación en cada IF se hace mediante una operación lógica AND entre el valor devuelto por la función joypad() y la máscará de cada botón que está definida en el fichero de cabecera gb.h .

Como ves este programa para sacar un mensaje diferente en función del botón pulsado en el PAD de la consola sería muy sencillo.

Saludos.
Perfecto, muchas gracias, ahora ya sí. El tema es que hasta ahora solo conocía para input el waitpad y me estaba bloqueando. También en las guías de C que he estado viendo usan mucho el scanf, cosa que no se puede usar en GB. Sin embargo, esto que enseñas en el segundo tutorial parece que se puede usar como sustituto del scanf (con alguna limitación quizás). Bueno, con esto ya tengo material para seguir trasteando. También echaré un vistazo a las diferencias entre el printf y el puts aunque creo que ya he visto por donde van los tiros.

Saludos.
JSL escribió:Perfecto, muchas gracias, ahora ya sí. El tema es que hasta ahora solo conocía para input el waitpad y me estaba bloqueando. También en las guías de C que he estado viendo usan mucho el scanf, cosa que no se puede usar en GB. Sin embargo, esto que enseñas en el segundo tutorial parece que se puede usar como sustituto del scanf (con alguna limitación quizás). Bueno, con esto ya tengo material para seguir trasteando. También echaré un vistazo a las diferencias entre el printf y el puts aunque creo que ya he visto por donde van los tiros.

Saludos.


Ten en cuenta que el scanf() es una función que está preparada para recoger las pulsaciones del usuario en un teclado. En GameBoy no ha teclas sólo el PAD así que obviamente la recogida de pulsaciones no se puede hacer igual.

No te quedó mal el contador: https://www.youtube.com/watch?v=EnRLFY4aCGo
Gracias por los nuevos ejemplos. Que este excelente tema no decaiga.
TUTORIAL DE PROGRAMACIÓN PARA GAME BOY - PARTE #3
Imagen
Vamos a continuar con el tutorial de programación para GameBoy y para ello hoy os traigo un ejemplo que hace uso de algunas funciones interesantes recogidas en el drawing.h. Para ello hoy os traigo una ROM que maneja varias de estas funciones y que no me llevó mucho el realizarla (un par de días en mis ratos libres). Echadle un vistazo al siguiente video y al código fuente:

MAGIC SCREEN「TELESKETCH」FOR GAMEBOY - GBDK: Game Boy C Programming [GAMEBOY HOMEBREW]

https://www.youtube.com/watch?v=JhmbRf7bovo

//****************************************************
// Magic Screen for GameBoy.
// Programmed by jduranmaster a.k.a. Ryoga
//****************************************************
#include <gb/gb.h>
#include <stdio.h>
#include <ctype.h>
#include <gb/console.h>
#include <gb/drawing.h>

unsigned char x = 10;
unsigned char y = 10;
int drawing_control = 0;

void clscr();
void cursor();
void print_title();
void print_marquee();
void print_solid_marquee();
void print_message_INTRO();
void print_message_usage();
void print_messageAtXY(int x, int y, char *c);

int main(){
   
   print_marquee();
   print_title();
   print_message_INTRO();
   waitpad(J_START);
   
   clscr();
   print_solid_marquee();
   cursor();
   
   return 0;
}

void cursor(){
   
   gotoxy(10, 10);
   
   while(1){
      if(joypad() == J_UP){
         y--;
      }
      if(joypad() == J_DOWN){
         y++;
      }
      if(joypad() == J_LEFT){
         x--;
      }
      if(joypad() == J_RIGHT){
         x++;
      }
      if(joypad() == J_SELECT){
         clscr();
         print_solid_marquee();
         gotoxy(0,0);
      }
      if(joypad() == J_A){
         if(drawing_control == 0){
            drawing_control = 1;
            color(WHITE, WHITE, SOLID);
         }else{
            drawing_control = 0;
            color(BLACK, WHITE, SOLID);
         }
      }
      if(joypad() == J_B){
         if(drawing_control == 1){
            drawing_control = 0;
            color(BLACK, WHITE, SOLID);
         }else{
            drawing_control = 1;
            color(WHITE, WHITE, SOLID);
         }
      }
      if(joypad() == J_START){
         clscr();
         print_marquee();
         print_title();
         print_message_usage();
         waitpad(J_START);
   
         clscr();
         print_solid_marquee();
         cursor();
      }
      
      plot_point(x,y);
      delay(35);
   }
}

void print_marquee(){
   gotoxy(0, 0);
   color(BLACK, WHITE, SOLID);
   box(156,140,2,2,M_NOFILL);
   box(154,138,4,4,M_NOFILL);
   box(152,136,6,6,M_NOFILL);
}

void print_solid_marquee(){
   gotoxy(0, 0);
   color(BLACK, WHITE, SOLID);
   box(156,140,2,2,M_NOFILL);
   box(155,139,3,3,M_NOFILL);
   box(154,138,4,4,M_NOFILL);
   box(153,137,5,5,M_NOFILL);
   box(152,136,6,6,M_NOFILL);
}

void print_messageAtXY(int x, int y, char *c){
   gotogxy(x,y);
   gprintf(c);
}

void print_title(){
   print_messageAtXY(4,1,"Magic Screen");
}

void print_message_usage(){
   print_messageAtXY(1,3,"Controls:");
   print_messageAtXY(1,4,"");
   print_messageAtXY(1,5,"A - EN. cursor");
   print_messageAtXY(1,6,"B - DIS. cursor");
   print_messageAtXY(1,7,"SELECT - Erase SCR");
   print_messageAtXY(1,8,"START - HOW TO...");
   print_messageAtXY(4,11,"PRESS START!");
   print_messageAtXY(8,14,"2014");
   print_messageAtXY(4,15,"Programmed by");
   print_messageAtXY(8,16,"Ryoga");
}

void print_message_INTRO(){
   print_messageAtXY(1,3,"This is a homebrew");
   print_messageAtXY(1,4,"software for the");
   print_messageAtXY(1,5,"Game Boy console.");
   print_messageAtXY(1,6," ");
   print_messageAtXY(1,7,"Controls:");
   print_messageAtXY(2,8,"A - EN. cursor");
   print_messageAtXY(2,9,"B - DIS. cursor");
   print_messageAtXY(4,11,"PRESS START!");
   print_messageAtXY(8,14,"2014");
   print_messageAtXY(4,15,"Programmed by");
   print_messageAtXY(8,16,"Ryoga");
}

void clscr(){
   int x, y;
   
     for(y = 0; y < 20; y++){
       for(x = 0; x < 30; x++){
            gotoxy(x, y);
            gprintf(" ");
      }
   }
   gotoxy(0,0);
}


Después de un primer vistazo os habreis dado cuenta de que en este ejemplo se hace mucho de la función gotoxy() la cual se utiliza para posicionar el cursor en las posiciones X e Y necesarias. Ya explique un poco el funcionamiento de gotoxy(x,y) anteriormente cuando explique el funcionamiento de la función clscr() que desarrollé para la entrada anterior así que no perderé más el tiempo con ella, de hecho en esta entrada voy a limitarme simplemente a explicar el bucle principal y alguna cosa más.

Como podeis apreciar esta ROM es una especie de clón del TELESKETCH.. Existe una aplicación para móviles llamada "Magic Screen" que en esencia funciona igual que este dispositivo y de ella he tomado el nombre para esta nueva ROM para Game Boy.

A continuación vamos ir repasando las funciones que creo aportan algo nuevo a lo que ya habíamos visto con anterioridad:

void print_marquee(){
   gotoxy(0, 0);
   color(BLACK, WHITE, SOLID);
   box(156,140,2,2,M_NOFILL);
   box(154,138,4,4,M_NOFILL);
   box(152,136,6,6,M_NOFILL);
}


La función print_marquee() es utilizada como su nombre dice para mostrar por pantalla un marco con unas caracteríssitcas especiales. Lo primero que tiene es una llamada a gotoxy() para posicionar el cursor en la posición (0,0). A continuación hay una llamada a la función color() con la que voy a definir como color en primer plano el negro y el color en segundo plano el blanco (en la DMG se pueden utilizar 4 colores diferentes: BLACK, WHITE, LIGHTGREY y DARKGREY, los podeis buscar en el fichero .h de cabecera correspondiente).
A continuación se hace uso repetidas veces la función box() para pintar en pantalla una serie de recuadros en unas coordenadas deteerminadas. La constante M_NOFILL indica que el rectángulo generado no está relleno de nada y por tanto se puede pintar cualquier contenido dentro de él. La signatura de la función es la siguiente:

void box(UINT8 x1, UINT8 y1, UINT8 x2, UINT8 y2, UINT8 style);

donde x1 e y1 son las dimensiones del rectángulo a pintar y x2 e y2 son las coordenadas en pantalla. En este caso uso la función print_marquee() para mostrar un marco formado por 3 cuadrados uno dentro del otro con una separación clara para las pantallas de presentación y de ayuda.

void print_solid_marquee(){
   gotoxy(0, 0);
   color(BLACK, WHITE, SOLID);
   box(156,140,2,2,M_NOFILL);
   box(155,139,3,3,M_NOFILL);
   box(154,138,4,4,M_NOFILL);
   box(153,137,5,5,M_NOFILL);
   box(152,136,6,6,M_NOFILL);
}


Utilizo la función print_solid_marquee() de la misma forma que la anterior sólo que está vez pinto un cuadrado de borde negro sólido.

void print_messageAtXY(int x, int y, char *c){
   gotogxy(x,y);
   gprintf(c);
}


Además he creado la función print_messageAtXY(int x, int y, char *c) para facilitarme la vida a la hora de mostrar mensajes por pantalla. En este caso se hace uso de la función gotogxy(x,y) para posicionar el cursor donde me interesa mostrar el texto y la función gprintf(c) para mostrar el texto. Notad que esta función recibe un puntero al tipo char que es el equivalente a decir que la función recibe como entrada una cadena de caracteres en lugar de un único carácter. Char* no es una cadena de caracteres como tal sino que apunta a una dirección de memoria (de ahí la palabra puntero) donde hay una serie de caracteres. (Si no estais familiarizados con los punteros os recomiendo mirar algún manual de C). No me molestaré en explicar el resto de funciones que hacen uso de print_messageAtXY() porque son auxiliares para mostrar el texto que me interesa y sólo tienen llamadas a print_messageAtXY().

void cursor(){
   
   gotoxy(10, 10);
   
   while(1){
      if(joypad() == J_UP){
         y--;
      }
      if(joypad() == J_DOWN){
         y++;
      }
      if(joypad() == J_LEFT){
         x--;
      }
      if(joypad() == J_RIGHT){
         x++;
      }
      if(joypad() == J_SELECT){
         clscr();
         print_solid_marquee();
         gotoxy(0,0);
      }
      if(joypad() == J_A){
         if(drawing_control == 0){
            drawing_control = 1;
            color(WHITE, WHITE, SOLID);
         }else{
            drawing_control = 0;
            color(BLACK, WHITE, SOLID);
         }
      }
      if(joypad() == J_B){
         if(drawing_control == 1){
            drawing_control = 0;
            color(BLACK, WHITE, SOLID);
         }else{
            drawing_control = 1;
            color(WHITE, WHITE, SOLID);
         }
      }
      if(joypad() == J_START){
         clscr();
         print_marquee();
         print_title();
         print_message_usage();
         waitpad(J_START);
   
         clscr();
         print_solid_marquee();
         cursor();
      }
      
      plot_point(x,y);
      delay(35);
   }
}


La función cursor() la utilizo para controlar como se pintan las lineas en la pantalla. Básicamente lo que se hace es ir comprobando mediente varios IF que botón de la consola se ha pulsado para saber si se tienen que incrementar o decrementar las coordenadas X e Y. Como podeis apreciar cuando se pulsa A o B se cambia el color del cursor a blanco o negro para dejar de pintar. Si se pulsa START se muestra en pantalla la ayuda del programa y si se pulsa SELECT se hace un borrado total de lo que hayamos dibujado hasta entonces. Para pintar las lineas utilizo la función plot_point(x,y) que como su nombre indica, sirve para pintar puntos, pero como estamos dentro de un bucle WHILE que se ejecuta siempre y movemos el cursor continuamente da la sensación de que se pintan lineas sin más. La función delay() es una función muy típica en los lenguajes de programación y en este caso se utiliza para introducir un retardo medido en milisegundos antes de volver al inicio del bucle WHILE. Finalmente la función principal queda como sigue:

int main(){
   
   print_marquee();
   print_title();
   print_message_INTRO();
   waitpad(J_START);
   
   clscr();
   print_solid_marquee();
   cursor();
   
   return 0;
}


Como veis lo único que tiene son las diferentes llamadas a las funciones anteriores en el orden adecuado para formar el programa. No tiene nada más.

Como veis con este sencillo ejemplo nos hemos sacado de la manga un clón del famoso TELESKETCH y tampoco ha sido para tanto. Os invito a que investigueis el drawing.h pues hay más funciones interesantes para pintar circunferencias, etc. Espero traer pronto una entrada donde empezaremos a hablar de SPRITES.

Saludos.
Para ser un programa bastante sencillo la verdad es que el resultado es inmejorable. No tenía ni idea de que el gbdk incluyese funciones para mostrar circulos. La verdad es que es una pasada en lo que está derivando este hilo.
Me he quedado flipado viendo este último programa para gameboy. Si lo hubiese tenido que hacer yo desde cero seguro que la hubiese liado. Muy buenas explicaciones tio y gracias por compartir el código para que podamos practicar. Este hilo va mejorando y mira que era difícil.
AirMaster escribió:Me he quedado flipado viendo este último programa para gameboy. Si lo hubiese tenido que hacer yo desde cero seguro que la hubiese liado. Muy buenas explicaciones tio y gracias por compartir el código para que podamos practicar. Este hilo va mejorando y mira que era difícil.


Gracias. Espero poder traer un nuevo capítulo de desarrollo para GB/GBC/GBA dentro de un par de semanas. Mientras tanto me ha ido rondando por la cabeza la idea de crear un blog donde meter todo esto con el fin de que se más accesible para todo el mundo desde diferentes foros. ¿Os parecería bien? Con esto las entradas irían en el propio blog y cada vez que escribiese una nueva entrada pondría el enlace aquí (siempre y cuando a la moderación le parezca bien).

Por otro lado siempre me ha gustado la participación así que si alguien quiere poner en este mismo hilo sus cosillas realizadas para GB/GBC/GBA puede escribirlo en un post de este hilo, incluso lo pondría en el primer post del hilo junto con el resto de enlaces a los post de este hilo con la misma temática.

Saludos.
Yo estoy en stand by, voy viendo todo, pero tengo que mejorar mi "C", da un poco de vergüenza ajena xD. Así que ando con tutos y ejercicios de C.
tuviello escribió:Yo estoy en stand by, voy viendo todo, pero tengo que mejorar mi "C", da un poco de vergüenza ajena xD. Así que ando con tutos y ejercicios de C.


Bueno poco a poco. De momento para que os vayais haciendo un poco a la idea tengo pensado escribir bastantes más entradas relacionadas con Game Boy y Game Boy Color de las que tenía pensado hacer en un principio. Entre ellas: manejo de textos con formato en GB, un clón del PONG, un clón del TETRIS, manejo de audio en la gameboy, quizás un clón del típico SNAKE y para terminar como manejar y controlar los sprites de un personaje en la GB/GBC para moverlo en pantalla con colisiones y todo. Y luego por supuesto podeis añadir todo lo que hagais vosotros y querais compartir con los demás.

Saludos.
DarkRyoga escribió:Bueno poco a poco. De momento para que os vayais haciendo un poco a la idea tengo pensado escribir bastantes más entradas relacionadas con Game Boy y Game Boy Color de las que tenía pensado hacer en un principio. Entre ellas: manejo de textos con formato en GB, un clón del PONG, un clón del TETRIS, manejo de audio en la gameboy, quizás un clón del típico SNAKE y para terminar como manejar y controlar los sprites de un personaje en la GB/GBC para moverlo en pantalla con colisiones y todo. Y luego por supuesto podeis añadir todo lo que hagais vosotros y querais compartir con los demás.

Saludos.


Muy buenas noticias tio. La verdad es que yo estoy siguiendo bastante este hilo y tengo mucho interés en aprender. Espero que pronto continues con la siguiente entrada.
TUTORIAL DE PROGRAMACIÓN PARA GAME BOY - PARTE #4
Imagen

Después de un par de semanas sin novedades vuelvo de nuevo para continuar con el tutorial de programación de Game Boy. En este caso el programa que traigo es muy sencillo. Echadle un ojo al siguiente video y código fuente:

RANDOM NUMBER GENERATOR「RAND」FOR GAMEBOY - GBDK: Game Boy C Programming [GAMEBOY HOMEBREW]
https://www.youtube.com/watch?v=PhCHgO2Kaqg

//*****************************************
//      Random Generator for GameBoy.
// Programmed by jduranmaster a.k.a. Ryoga
//*****************************************
#include <gb/gb.h>
#include <stdio.h>
#include <ctype.h>
#include <rand.h>
#include <gb/console.h>
#include <gb/drawing.h>

#include "pc18966_2.h"

unsigned int r,seed,key;

void clscr();
void main_loop();
void print_title();
void print_marquee();
void print_solid_marquee();
void print_message_INTRO();
void print_message_usage();
void print_messageAtXY(int x, int y, char *c);
void plot_intro_image(unsigned char *tile_data, unsigned char *map_data);

void main(){
   
   plot_intro_image(pc18966_2_tile_data, pc18966_2_map_data);
   
   print_marquee();
   print_title();
   print_message_INTRO();
   waitpad(J_START);
   
   clscr();
   print_solid_marquee();
   main_loop();
}

void main_loop(){
   
   seed=0;
   key=0;
   r=0;
   
   while(1){
      print_messageAtXY(1,2, "Press A Button !");
      if(joypad() == J_A){
           while(joypad() == J_A){seed++; if(seed>=255)seed=1;}
           initrand(seed);
           print_messageAtXY(1,4, " ");
           gprintf("Seed = %u ",seed);
           if(!0){
               r=rand();
               print_messageAtXY(1,5, " ");
               gprintf("Rand = %u  ",r);
           }
      }
      if(joypad() == J_B){
         // do nothing
      }
      if(joypad() == J_SELECT){
         clscr();
         print_solid_marquee();
      }
      if(joypad() == J_START){
         clscr();
         print_marquee();
         print_title();
         print_message_usage();
         waitpad(J_START);
   
         clscr();
         print_solid_marquee();
      }
      delay(35);
   }
}

void print_marquee(){
   gotoxy(0, 0);
   color(BLACK, WHITE, SOLID);
   box(156,140,2,2,M_NOFILL);
   box(154,138,4,4,M_NOFILL);
   box(152,136,6,6,M_NOFILL);
}

void print_solid_marquee(){
   gotoxy(0, 0);
   color(BLACK, WHITE, SOLID);
   box(156,140,2,2,M_NOFILL);
   box(155,139,3,3,M_NOFILL);
   box(154,138,4,4,M_NOFILL);
   box(153,137,5,5,M_NOFILL);
   box(152,136,6,6,M_NOFILL);
}

void plot_intro_image(unsigned char *tile_data, unsigned char *map_data){
   set_bkg_data(0, 0, tile_data);
   set_bkg_tiles(0, 0, 20, 18, map_data);
   SHOW_BKG;
   DISPLAY_ON;
   delay(5000);
   clscr();
}

void print_messageAtXY(int x, int y, char *c){
   gotogxy(x,y);
   gprintf(c);
}

void print_title(){
   print_messageAtXY(3,1,"Rand Generator");
}

void print_message_usage(){
   print_messageAtXY(1,3,"Controls:");
   print_messageAtXY(1,4,"");
   print_messageAtXY(1,5,"A - Gen. Number");
   print_messageAtXY(1,7,"SELECT - Erase SCR");
   print_messageAtXY(1,8,"START - HOW TO...");
   print_messageAtXY(4,11,"PRESS START!");
   print_messageAtXY(8,14,"2014");
   print_messageAtXY(4,15,"Programmed by");
   print_messageAtXY(8,16,"Ryoga");
}

void print_message_INTRO(){
   print_messageAtXY(1,3,"This is a homebrew");
   print_messageAtXY(1,4,"software for the");
   print_messageAtXY(1,5,"Game Boy console.");
   print_messageAtXY(1,6," ");
   print_messageAtXY(1,7,"Controls:");
   print_messageAtXY(2,8,"A - Gen. Number");
   print_messageAtXY(4,11,"PRESS START!");
   print_messageAtXY(8,14,"2014");
   print_messageAtXY(4,15,"Programmed by");
   print_messageAtXY(8,16,"Ryoga");
}

void clscr(){
   int x, y;
     for(y = 0; y < 20; y++){
       for(x = 0; x < 30; x++){
            gotoxy(x, y);
            gprintf(" ");
      }
   }
   gotoxy(0,0);
}


Se trata de un generador de números aleatorios para Game Boy. Lo importante de este programa nuevo no es eso, ya que de hecho a esa parte no le pienso dedicar demasiado, entre otras cosas porque la generación de números aleatorios en C se puede buscar en cualquier sitio y existen miles de ejemplos. Lo importante de este nuevo programa de ejemplo es lo siguiente:

void plot_intro_image(unsigned char *tile_data, unsigned char *map_data);


Este prototipo de función sirve para lo que algunos seguro ya os estáis imaginando. Efectivamente sirve mostrar una imagen de 160x144 (como mucho) en la pantalla de nuestra gameboy. De hecho la imagen que se muestra en el video cuando se carga la ROM tiene este tamaño aunque como digo podría ser más pequeña. Pero vamos por pasos. Lo primero que debemos hacer antes de nada es obtener nuestra imagen en las dimensiones requeridas y para ello existen multitud de utilidades que con los años la escena ha ido generando para este menester. Como digo existen muchas de estas herramientas, pero debéis tener en cuenta que algunas de ellas son muy antiguas y no funcionarán en PCs con sistemas operativos actuales aunque una solución a este problema sería utilizar máquinas virtuales que simulen arquitecturas y sistemas más antiguos en los que poder ejecutar dichos programas. Sin embargo en nuestro caso no va a hacer falta nada de esto puesto que vamos a usar un generador basado en "JAVASCRIPT" y que está empotrado en una página web. La entrada a dicho generador será nuestra imagen de 160x144 y la salida puede ser un fichero de cabecera de C (fichero con extensión .h) donde se encuentra la información de los tiles de la imagen o bien su equivalente para ensamblador. Todo dependen de que SDK estemos usando. Como nosotros en los tutoriales usamos el GBDK basado en C marcaremos la opción para que nos genere el fichero ".h". Bien, la URL a la página del generador es la siguiente:

http://www.chrisantonellis.com/gameboy/gbtdg/

Este generador además se puede descargar desde el repositorio de GitHub (en enlace para ello está en la parte inferior derecha de la pantalla en.la URL que acabo de enlazar).

Una vez generado el fichero .h tendremos algo similar a lo siguiente:

// ///////////////////////
// //                   //
// //  File Attributes  //
// //                   //
// ///////////////////////

// Filename: pc18966_2.jpg
// Pixel Width: 160px
// Pixel Height: 144px

// /////////////////
// //             //
// //  Constants  //
// //             //
// /////////////////

const int pc18966_2_tile_map_size = 0x0168;
const int pc18966_2_tile_map_width = 0x14;
const int pc18966_2_tile_map_height = 0x12;

const int pc18966_2_tile_data_size = 0x0D80;
const int pc18966_2_tile_count = 0x0168;

// ////////////////
// //            //
// //  Map Data  //
// //            //
// ////////////////

const unsigned char pc18966_2_map_data[] ={
  0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0E,
  0x0E,0x0E,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,
  0x1B,0x1C,0x0E,0x0E,0x0E,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
  0x28,0x29,0x1B,0x1B,0x1B,0x1B,0x2A,0x0E,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,
  0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x1B,0x1B,0x1B,0x3A,0x1D,0x3B,0x3C,0x3D,0x3E,
  0x3F,0x40,0x41,0x42,0x41,0x1B,0x43,0x44,0x45,0x46,0x47,0x1B,0x1B,0x1B,0x48,0x49,
  0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x4F,0x53,0x54,0x55,0x56,0x57,0x58,
.....

"CONTINUA"
};

// /////////////////
// //             //
// //  Tile Data  //
// //             //
// /////////////////

const unsigned char pc18966_2_tile_data[] ={
  0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x7F,0xFF,0x7F,0xFF,
  0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x21,0xFF,0xF1,0xFF,0xF3,0xFF,
  0x00,0xFF,0x00,0xFF,0x01,0xFF,0x00,0xFF,0x00,0xFF,0x02,0xFF,0xFF,0xFF,0xF7,0xFF,
"CONTINUA"
};



El programa genera el fichero .h donde se encuentran definidos los tiles de la imagen y el mapa de bits que se van a colocar como fondo en la pantalla. Pero...¿cómo cargamos entonces la imagen? Os remito de nuevo al siguiente prototipo de función:

void plot_intro_image(unsigned char *tile_data, unsigned char *map_data);


Esta es la función que usaremos en este programa para cargar la imagen. A continuación su impementación:

void plot_intro_image(unsigned char *tile_data, unsigned char *map_data){
   set_bkg_data(0, 0, tile_data);
   set_bkg_tiles(0, 0, 20, 18, map_data);
   SHOW_BKG;
   DISPLAY_ON;
   delay(5000);
   clscr();
}


como podéis apreciar se hacen llamadas a las funciones "set_bkg_data()" y "set_bkg_tiles()". Ambas incluidas sino recuerdo mal en el fichero "gb.h".

set_bkg_data(0, 0, tile_data);


En este caso se recibe como parámetros de entrada la coordenadas X e Y que se toman como referencia para colocar los tiles, en este caso (0,0) es decir la esquina superior izquierda de la pantalla de nuestra gameboy. El siguiente parámetro es el array con los datos.

set_bkg_tiles(0, 0, 20, 18, map_data);


En este caso se recibe como parámetros de entrada la coordenadas X e Y que se toman como referencia para colocar los tiles, en este caso (0,0) es decir la esquina superior izquierda de la pantalla de nuestra gameboy. A continuación se debe especificar en los dos siguientes parámetros el tamaño de la imagen. Yo he puesto a capón 20x18 "TILES" que se corresponde con 160x144 pixels. No hace falta que lo hagáis así puesto que en el fichero .h generado con la aplicación javascript se han generado dos constantes con valor hexadecimal que son totalmente equivalentes, pero podéis hacerlo como mejor os convenga siempre y cuando hagáis el cálculo para obtener el valor entero en "TILES" del tamaño de vuestra imagen. Bien, finalmente nos encontramos con:

SHOW_BKG;
DISPLAY_ON;
delay(5000);
clscr();


SHOW_BKG es un flag definido como MACRO que carga la imagen en el fondo de la pantalla. DISPLAY_ON tres cuartas de los mismo pero en este caso para activar la pantalla en modo "BACKGROUND". Finalmente delay(5000) sirve para hacer que el programa espere 5 segundos antes de continuar (el parámetro de entrada es en milisegundos) y clscr() es la función que llevo utilizando desde el principio de los tutoriales para limpiar la pantalla de la gameboy. Como podéis apreciar en el video la imagen que muestro de fondo tiene algunos pixeles "muertos" en negro esto es porque la imagen original era de mucho mayor tamaño a 160x144 y la reduje con photoshop a 20x18 TILES con la consiguiente pérdida de resolución y a eso hay que añadirle el procesado de imagen que hace la herramienta javascript para obtener el fichero .h de la imagen. Así que mi recomendación es que desde un principio siempre trabajéis con una imagen de 160x144 y la vayas retocando con algún programa de edición de imágenes para que luego la conversión no sea tan abrupta. Y se me olvidaba, para que las estructuras de datos y constantes de nuestro fichero .h estén disponibles desde nuestro programa principal, obviamente hay que incluir el fichero como es ya costumbre:

#include "pc18966_2.h"


Actualmente ya estoy terminando mi clón de PONG para Game Boy (¡Por Fin!) con lo que he refinado un poco la función para mostrar las imágenes en el fondo de la pantalla además de añadir algunos trucos nuevos, pero aquí os dejo de momento el prototipo de la función tal cual la estoy usando ahora mismo:

void PLOT_BACKGROUND_IMAGE(int coord_X, int coord_Y, int tam_tile_X,int tam_tile_Y,unsigned char *tile_data, unsigned char *map_data)


Y por mi parte poco más que añadir, espero poder traer mi clón de PONG dentro de poco por estos parajes.

Saludos.
Como siempre espectacular tu trabajo Ryoga. A la espera de la próxima lección me quedo.
Mil gracias por tu dedicación e estos tutoriales! Estoy aprendiendo mucho gracias a ti!

Un Saludo!
Muchísimas gracias por los tutoriales, están de lujo.

Si me lo permites, dejo un enlace del taller que dio Pocket_Lucho en la Universidad de Alicante sobre programación en Game Boy, para complementar tus tutoriales.

http://vertice.cpd.ua.es/126068
¡¡¡ACTUALIZACIÓN!!!

He actualizado el post inicial del hilo con los enlaces directos a cada uno de los post relacionados con el desarrollo de juegos para GB/GBC/GBA. Podeis encontrar toda la información nueva aquí: http://www.elotrolado.net/hilo_desarrollo-software-proyectos-de-darkryoga_1901847#p1732604436

Por otro lado aprovecho para recomendaros que os bajeis esta versión del GBDK pues desarrollando el primer juego completo que voy a explicar en este hilo me tope con un problema con la versión anterior que estaba usando y es que resulta que dicha versión tenía un error ya que parece que el compilador trataba de colocar datos definidos como "CONST" en la RAM en lugar de colocarlos en la ROM. Para evitar estos problemas como digo os recomiendo que os bajeis esta otra versión más actualizada: http://sourceforge.net/projects/gbdk/files/gbdk-win32/2.95-3/ (se trata de la versión compilada para Windows).

Como ya comenté en un post anterior dentro de poco llegará la entrada dedicada al primer juego desarrollado en este hilo para la GameBoy (el Magic Screen no lo consideraría un juego como tal, sino más bien una herramienta). Se trata nada más ni nada menos que mi clón de PONG al que he decidido llamar Ping Pong Diplomacy. A continuación os dejo un video/teaser de como va el desarrollo. La lógica del juego funciona correctamente, pero me falta por añadir algún efecto extra para que nos quede una entrada redonda donde tocar varios palos.

Ping Pong Diplomacy Prototype「2014-10-12」(TEASER) (Game Boy Homebrew) - GAME BOY C PROGRAMMING

https://www.youtube.com/watch?v=OiKZdMFD7-w

Imagen

También os comento que es probable que la entrada dedicada a la explicación del clón de PONG esté ya disponible en el blog. De ser así lo que haré será avisar por aquí en un post y dejar el enlace a dicha entrada para que la podáis leer. El resto de entradas que he escrito aquí hasta ahora y las que quedan antes de la dedicada al clón de PONG estarán también disponibles en el nuevo blog.

Finalmente os indico que en próximas fechas en este hilo tendréis noticias de mis últimos avances en emulación y es más que probable que me anime a realizar una entrada específica sobre como desarrollar nuestro propio emulador de una plataforma un tanto peculiar.

Saludos.

SefirotDios2 escribió:Mil gracias por tu dedicación e estos tutoriales! Estoy aprendiendo mucho gracias a ti!

Un Saludo!


Me alegro de que la gente siga el hilo. Espero que pronto puedas compartir con nosotros en este hilo tus creaciones. Saludos.

Bossma escribió:Muchísimas gracias por los tutoriales, están de lujo. Si me lo permites, dejo un enlace del taller que dio Pocket_Lucho en la Universidad de Alicante sobre programación en Game Boy, para complementar tus tutoriales.


Gracias a ti por seguirlos. He añadido el video de Pocket_Lucho al post inicial de este hilo. Saludos.
Pedazo de actualización. Me encanta este hilo. Voy a probar ahora mismo lo de las imágenes a ver que tal se da.

¡Ese clón del pong pinta de maravilla tio!.
Excelentes explicaciones como siempre. Da gusto pasarse por este hilo. El programa para generar números aleatorios no parece muy difícil de hacer y el tema de los fondos es algo que siempre me pregunté como se implementaría de forma sencilla. El clón de Pong tiene muy buena pinta. A ver si pronto lo vemos "diseccionado" por aquí para aprender más.
TUTORIAL DE PROGRAMACIÓN PARA GAME BOY - PARTE #5

Imagen

Vamos a continuar con el tema de desarrollo para Game Boy y en esta ocasión vamos a centrarnos en el audio. Voy a dividir este tema en dos entradas diferentes. Una dedicada a los efectos de sonido (esta entrada) y otra dedicada a la música de fondo (qué será la siguiente). Echádle un ojo al siguiente video y código fuente.

https://www.youtube.com/watch?v=n2zl_iMR4jk

//*****************************************
//      SoundEffects for GameBoy.
// Programmed by jduranmaster a.k.a. Ryoga
//*****************************************

#include <gb/gb.h>
#include <gb/drawing.h>

#include "_SFX.h"

UBYTE tile_offset = 1;
UBYTE tile_height = 18;

static const unsigned char TEXT_cEMPTY[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

void PRINT_MAIN_SFX_MENU(void);
void PLAY_A_SOUND_EFFECT(void);
void PLAY_B_SOUND_EFFECT(void);
void CLEAR_SCREEN_LINE (UBYTE y);
void CLEAR_SCREEN_BACKGROUND (void);
void PLAY_CURSOR_SOUND_EFFECT(void);
void PLAY_SELECT_SOUND_EFFECT(void);
void INIT_REGISTERS_SOUND_EFECTS(void);
void PRINT_MESSAGE(int x, int y, char *c);
UBYTE PLAY_START_SOUND_EFFECT(UBYTE sfx_two_notes_on);
void PLOT_BACKGROUND_IMAGE(int coord_X, int coord_Y, int tam_tile_X, int tam_tile_Y, unsigned char *tile_data, unsigned char *map_data);

void main(){
   
   UBYTE key_pressed = 0;
   UBYTE sfx_on = 0;
   UBYTE sfx_two_notes_on = 0;
   
   PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, _SFX_tile_data, _SFX_map_data);
   PRINT_MAIN_SFX_MENU();
   INIT_REGISTERS_SOUND_EFECTS();

   while(1){
      key_pressed = joypad();
      
      if( key_pressed & (J_START) && sfx_on == 0){
         PRINT_MESSAGE(9,9,"P ");
         delay(200);
         sfx_on = 1;
         sfx_two_notes_on = PLAY_START_SOUND_EFFECT(sfx_two_notes_on);
      }else{
         PRINT_MESSAGE(9,9,"NP");
      }
      if(sfx_two_notes_on){
         sfx_two_notes_on = PLAY_START_SOUND_EFFECT(sfx_two_notes_on);
      }
      
      if( key_pressed & (J_SELECT) && sfx_on == 0){
         PRINT_MESSAGE(9,10,"P ");
         delay(200);
         PLAY_SELECT_SOUND_EFFECT();
         sfx_on = 1;
      }else{
         PRINT_MESSAGE(9,10,"NP");
      }

      if( key_pressed & (J_UP) && sfx_on == 0){
         PRINT_MESSAGE(9,5,"P ");
         delay(200);
         PLAY_CURSOR_SOUND_EFFECT();
         sfx_on = 1;
      }else{
         PRINT_MESSAGE(9,5,"NP");
      }

      if( key_pressed & (J_DOWN) && sfx_on == 0){
         PRINT_MESSAGE(9,6,"P ");
         delay(200);
         PLAY_CURSOR_SOUND_EFFECT();
         sfx_on = 1;
      }else{
         PRINT_MESSAGE(9,6,"NP");
      }

      if( key_pressed & (J_LEFT) && sfx_on == 0){
         PRINT_MESSAGE(9,7,"P ");
         delay(200);
         PLAY_CURSOR_SOUND_EFFECT();
         sfx_on = 1;
      }else{
         PRINT_MESSAGE(9,7,"NP");
      }

      if( key_pressed & (J_RIGHT) && sfx_on == 0){
         PRINT_MESSAGE(9,8,"P ");
         delay(200);
         PLAY_CURSOR_SOUND_EFFECT();
         sfx_on = 1;
      }else{
         PRINT_MESSAGE(9,8,"NP");
      }

      if( key_pressed & (J_A) && sfx_on == 0){
         PRINT_MESSAGE(9,11,"P ");
         delay(200);
         PLAY_A_SOUND_EFFECT();
         sfx_on = 1;
      }else{
         PRINT_MESSAGE(9,11,"NP");
      }

      if( key_pressed & (J_B) && sfx_on == 0){
         PRINT_MESSAGE(9,12,"P ");
         delay(200);
         PLAY_B_SOUND_EFFECT();
         sfx_on = 1;
      }else{
         PRINT_MESSAGE(9,12,"NP");
      }

      if( !( key_pressed & J_SELECT ) && !( key_pressed & J_A ) && !( key_pressed & J_START ) && !( key_pressed & J_B )
         && !( key_pressed & J_UP ) && !( key_pressed & J_DOWN ) && !( key_pressed & J_RIGHT ) && !( key_pressed & J_LEFT ) && sfx_on == 1 ){
         sfx_on = 0;
      }
   }
}

void PRINT_MAIN_SFX_MENU(void){
   PRINT_MESSAGE(1, 1, "----------------");
   PRINT_MESSAGE(1, 2, "SFX FOR GAMEBOY");
   PRINT_MESSAGE(1, 3, "----------------");
   PRINT_MESSAGE(1, 5,"Up    : NP");
   PRINT_MESSAGE(1, 6,"Down  : NP");
   PRINT_MESSAGE(1, 7,"Left  : NP");
   PRINT_MESSAGE(1, 8,"Right : NP");
   PRINT_MESSAGE(1, 9,"Start : NP");
   PRINT_MESSAGE(1, 10,"Select: NP");
   PRINT_MESSAGE(1, 11,"A     : NP");
   PRINT_MESSAGE(1, 12,"B     : NP");
   PRINT_MESSAGE(1, 14, "----------------");
   PRINT_MESSAGE(4, 15, "RYOGA 2014");
   PRINT_MESSAGE(1, 16, "----------------");
}

void INIT_REGISTERS_SOUND_EFECTS(void){
   NR52_REG = 0xF8U;
   NR51_REG = 0x00U;
   NR50_REG = 0x77U;
}

void PLAY_A_SOUND_EFFECT(void){
   NR10_REG = 0x34U;
   NR11_REG = 0x70U;
   NR12_REG = 0xF0U;
   NR13_REG = 0x0AU;
   NR14_REG = 0xC6U;
   NR51_REG |= 0x11;
}

void PLAY_B_SOUND_EFFECT(void){
   NR10_REG = 0x34U;
   NR11_REG = 0x80U;
   NR12_REG = 0xF0U;
   NR13_REG = 0x0AU;
   NR14_REG = 0xC6U;
   NR51_REG |= 0x11;
}

void PLAY_SELECT_SOUND_EFFECT(void){
   NR10_REG = 0x04U;
   NR11_REG = 0xFEU;
   NR12_REG = 0xA1U;
   NR13_REG = 0x8FU;
   NR14_REG = 0x86U;
   NR51_REG = 0xF7;
}

UBYTE PLAY_START_SOUND_EFFECT(UBYTE sfx_two_notes_on){
   if( NR52_REG & 0x02 ){
      return 0x01;
   }else if( sfx_two_notes_on ){
      //segunda nota
      NR21_REG = 0x80U;
      NR22_REG = 0x73U;
      NR23_REG = 0x9FU;
      NR24_REG = 0xC7U;
      NR51_REG |= 0x22;
      return 0x00;
   }else{
      // primera nota.
      NR21_REG = 0xAEU;
      NR22_REG = 0x68U;
      NR23_REG = 0xDBU;
      NR24_REG = 0xC7U;
      NR51_REG |= 0x22;
      return 0x01;
   }
}

void PLAY_CURSOR_SOUND_EFFECT(void){
   NR10_REG = 0x38U;
   NR11_REG = 0x70U;
   NR12_REG = 0xE0U;
   NR13_REG = 0x0AU;
   NR14_REG = 0xC6U;
   NR51_REG |= 0x11;
}

void PRINT_MESSAGE(int x, int y, char *c){
   gotogxy(x,y);
   gprintf(c);
}

void CLEAR_SCREEN_LINE ( UBYTE y ){
   UBYTE x;
   for (x = y*20 ; x < (y+1)*20 ; x++ ){
      set_bkg_data (x+tile_offset,1,(unsigned char *)TEXT_cEMPTY);
   }
}

void CLEAR_SCREEN_BACKGROUND(void){
   UBYTE x;
   for ( x = 0 ; x < tile_height ; x++ ){
      CLEAR_SCREEN_LINE ( x );
   }
}

void PLOT_BACKGROUND_IMAGE(int coord_X, int coord_Y, int tam_tile_X, int tam_tile_Y, unsigned char *tile_data, unsigned char *map_data){
   set_bkg_data(coord_X, coord_Y, tile_data);
   set_bkg_tiles(coord_X, coord_Y, tam_tile_X, tam_tile_Y, map_data);
   SHOW_BKG;
   DISPLAY_ON;
   delay(2000);
}


El programa anterior lo que hace básicamente es mostrar un menú que permite en función del botón del PAD pulsado sacar un sonido diferente. Ahora bien, la cosa no es tan fácil como parece. En realidad la dificultad en este caso no está en la programación como tal, sino que el problema está en que para sacar los sonidos que queremos tener que tocar los registros a mano. Si a eso le unís que en mi caso no tengo buen oído para hacer composiciones musicales el resultado serán una serie de sonidos simplones pero que son más que suficientes para explicar este ejemplo.

La GameBoy tiene 4 canales de sonido a saber:

Canal 1: El canal 1, es un canal de onda cuadrada con ciclo de trabajo modificable, con envolvente y portamento. Se corresponde con los registros NR10-NR14
Canal 2: Es un canal de onda cuadrada con ciclo de trabajo modificable, con envolvente. Se corresponde con los registros NR21-NR24.
Canal 3: Canal de onda programable con una tabla RAM de 32 pasos. Se corresponde con los registros NR30-NR34.
Canal 4: Canal de ruido blanco con envolvente. Se corresponde con los registros NR41-NR44.

Además tendremos que controlar los valores que cargamos en los siguientes registros:

NR50_REG, define el volumen principal de las salidas derecha e izquierda. Lo inicializaremos con el valor 0x77U. Los valores que puede tomar oscilan entre 0x77 (máximo volumen) y 0x00 (nivel mínimo).
NR51_REG, es el selector de canal. Lo inicializaremos a 0x00U. Los valores que puede tomar para seleccionar el canal deseado son los siguientes: 0x11 (canal 1), 0x22 (canal 2), 0x44 (canal 3) y 0x88 (canal 4).
NR52_REG permite definir el encendido/apagado general del sonido. Se inicializará con el 0xF8U. (Escribir el valor 00h en este registro puede suponer como mínimo un ahorro del 16% de la carga de la pilas de la consola).

No voy a centrarme demasiado en el esqueleto del programa pues el 80% del código del mismo lo hemos visto en entradas anteriores. Me refiero a código utilizado para escribir texto en pantalla, meter imágenes de fondo, mover el cursor a la posición deseada, etc,etc. Si tenéis dudas os remito a entradas anteriores donde todas estas funciones están de sobra explicadas. Me voy a centrar en las funciones nuevas para poder sacar los sonidos de la consola. Por lo demás el programa detecta el botón del PAD pulsado y en función de eso saca el sonido configurado en los canales.

void INIT_REGISTERS_SOUND_EFECTS(void){
   NR52_REG = 0xF8U;
   NR51_REG = 0x00U;
   NR50_REG = 0x77U;
}


La función void INIT_REGISTERS_SOUND_EFECTS(void) como su nombre indica sirve para incializar los registros que nos permiten controlar los efectos de sonido. Como podéis apreciar en el código completo del ejemplo está al comienzo de la función main() justo antes del búcle while(1) (ya que sólo hace falta inicializar estos registros una vez).

void PLAY_A_SOUND_EFFECT(void){
   NR10_REG = 0x34U;
   NR11_REG = 0x70U;
   NR12_REG = 0xF0U;
   NR13_REG = 0x0AU;
   NR14_REG = 0xC6U;
   NR51_REG |= 0x11;
}


La función void PLAY_A_SOUND_EFFECT(void) saca el sonido configurado en el canal de audio 1 cuando pulsamos el botón A del PAD. En este caso se trata un sonido de una única nota. En el NR10_REG el cuarto bit nos indica si la frecuencia del sonido debe aumentar o disminuir. Los bits 5 a 7 del registro NR11_REG indican el retardo de la función de barrido para el canal de audio 1.

void PLAY_B_SOUND_EFFECT(void){
   NR10_REG = 0x34U;
   NR11_REG = 0x80U;
   NR12_REG = 0xF0U;
   NR13_REG = 0x0AU;
   NR14_REG = 0xC6U;
   NR51_REG |= 0x11;
}


La función void PLAY_B_SOUND_EFFECT(void) saca el sonido configurado en el canal de audio 1 cuando pulsamos el botón B del PAD. En este caso se trata un sonido de una única nota. En el NR10_REG el cuarto bit nos indica si la frecuencia del sonido debe aumentar o disminuir. Los bits 5 a 7 del registro NR11_REG indican el retardo de la función de barrido para el canal de audio 1.

void PLAY_SELECT_SOUND_EFFECT(void){
   NR10_REG = 0x04U;
   NR11_REG = 0xFEU;
   NR12_REG = 0xA1U;
   NR13_REG = 0x8FU;
   NR14_REG = 0x86U;
   NR51_REG = 0xF7;
}


La función void PLAY_SELECT_SOUND_EFFECT(void) saca el sonido configurado en el canal de audio 1 cuando pulsamos el botón SELECT del PAD. En este caso se trata un sonido de una única nota. En el NR10_REG el cuarto bit nos indica si la frecuencia del sonido debe aumentar o disminuir. Los bits 5 a 7 del registro NR11_REG indican el retardo de la función de barrido para el canal de audio 1.

void PLAY_CURSOR_SOUND_EFFECT(void){
   NR10_REG = 0x38U;
   NR11_REG = 0x70U;
   NR12_REG = 0xE0U;
   NR13_REG = 0x0AU;
   NR14_REG = 0xC6U;
   NR51_REG |= 0x11;
}


La función void PLAY_CURSOR_SOUND_EFFECT(void) saca el sonido configurado en el canal de audio 1 cuando pulsamos los botones de la cruceta de direcciones del PAD. En este caso se trata un sonido de una única nota. En el NR10_REG el cuarto bit nos indica si la frecuencia del sonido debe aumentar o disminuir. Los bits 5 a 7 del registro NR11_REG indican el retardo de la función de barrido para el canal de audio 1. Y si, es el mismo sonido para las 4 direcciones.

Ahora llegamos a algo más interesante. ¿Qué pasa cuando se pulsa el botón START? Pues que el programa saca un sonido de dos notas.


UBYTE PLAY_START_SOUND_EFFECT(UBYTE sfx_two_notes_on){
   if(NR52_REG & 0x02){
      return 0x01;
   }else if(sfx_two_notes_on){
      //segunda nota
      NR21_REG = 0x80U;
      NR22_REG = 0x73U;
      NR23_REG = 0x9FU;
      NR24_REG = 0xC7U;
      NR51_REG |= 0x22;
      return 0x00;
   }else{
      // primera nota.
      NR21_REG = 0xAEU;
      NR22_REG = 0x68U;
      NR23_REG = 0xDBU;
      NR24_REG = 0xC7U;
      NR51_REG |= 0x22;
      return 0x01;
   }
}


Lo primero que se hace en esta función es comprobar el valor del registro NR52_REG. Si no se ha reproducido la segunda nota entonces lo que hace es configurar los registros NR_21 a NR_24 (canal 2) para sacar la segunda nota por el canal 2 y si tampoco se esta reproduciendo la segunda nota entonces configura los registros NR_21 a NR_24 pra sacar la primera nota por el canal 2.

Finalmente este bloque de código:

if( !( key_pressed & J_SELECT ) && !( key_pressed & J_A ) && !( key_pressed & J_START ) && !( key_pressed & J_B )
         && !( key_pressed & J_UP ) && !( key_pressed & J_DOWN ) && !( key_pressed & J_RIGHT ) && !( key_pressed & J_LEFT ) && sfx_on == 1 ){
         sfx_on = 0;
      }


Lo que hace es evitar que se sigan reproduciendo el sonido si mantenemos pulsado cualquiera de los botones. De esta forma garantizamos que el sonido que queremos reproducir sólo se va a reproducir una vez con cada pulsación.

Como veis este tema tiene suficiente enjundia. Os recomiendo leer tranquilamente el tutorial del usuario xzakox (usuario del foro GP32Spain y creador del flashcart para GameBoy que uso en mis videos) acerca de este tema. Aunque su tutorial está centrado en la programación de software para la GameBoy en ensablador está todo muy bien explicado y la parte dedicada a los efectos de sonido que he tratado aquí está más que de sobra documentada: http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador#hola_sonido.

La próxima entrada la dedicaré al tema de como sacar músicas de fondo con la GameBoy. Para ello utilizaré una herramienta llamada GBT PLAYER que nos va a venir muy bien para convertir ficheros .MOD de 4 canales para que puedan ser entendidos por la consola.

Saludos.
Brutal tio, como de costumbre. Voy a ponerme con esta parte, pues el tema del sonido en consolas siempre es la parte que más me ha gustado.
Buscando por ahí me he topado con esta documentación acerca de como manejar el tema del audio en Game Boy. Puede ser un buen complemento a lo ya explicado por aquí: http://sebastianmihai.com/downloads/burlygb/gbsound3.pdf
Me he quedado a cuadros con el último programa que has subido. No sabía que fuese tan complicado hacer efectos de sonido para la gameboy. Complicado en el sentido de que no parece que haya ninguna herramienta que facilite la creación de estos efectos y haya que tirar de forma manual tocando los registros.
TUTORIAL DE PROGRAMACIÓN PARA GAME BOY - PARTE #6

Imagen

Vamos a continuar con el tema de desarrollo para Game Boy. En esta entrada continuamos con el tema del sonido y más concretamente con la música de fondo. Echádle un ojo al siguiente video y código fuente.

https://www.youtube.com/watch?v=n3Jqv8t7JU0

//*****************************************
//      testSoundMusic for GameBoy.
// Programmed by jduranmaster a.k.a. Ryoga
//*****************************************

#include <gb/gb.h>
#include <gb/drawing.h>
#include "gbt_player.h"
#include "_SMUSIC.h"

UBYTE tile_offset = 1;
UBYTE tile_height = 18;

static const unsigned char TEXT_cEMPTY[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

extern const unsigned char * song_Data[];
extern const unsigned char * Song_Data[];

void PRINT_MAIN_SFX_MENU(void);
void CLEAR_SCREEN_LINE (UBYTE y);
void PLAY_SONG_FROM_MBC1_BANK_1();
void PLAY_SONG_FROM_MBC1_BANK_2();
void STOP_SONG_FROM_MBC1_BANK_X();
void CLEAR_SCREEN_BACKGROUND (void);
void PRINT_MESSAGE(int x, int y, char *c);
void PLOT_BACKGROUND_IMAGE(int coord_X, int coord_Y, int tam_tile_X, int tam_tile_Y, unsigned char *tile_data, unsigned char *map_data);

void main(){
   
   UBYTE key_pressed = 0;
   UBYTE gbt_update_released = 0;
   UBYTE MBC_BANK_ID = 0;
   
   PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, _SMUSIC_tile_data, _SMUSIC_map_data);
   PRINT_MAIN_SFX_MENU();
   
   while(1){
      key_pressed = joypad();
      
      if(key_pressed & (J_A)){
         MBC_BANK_ID = 0;
         STOP_SONG_FROM_MBC1_BANK_X();
         PLAY_SONG_FROM_MBC1_BANK_2();
         PRINT_MESSAGE(1, 6,"MBC1-Bank1 B : NP");
         PRINT_MESSAGE(1, 10,"Playing MBC1-BANK 2");
         PRINT_MESSAGE(1, 7,"MBC1-Bank2 A : P ");
         if(gbt_update_released == 0){
            gbt_update_released = 1;
            add_VBL(gbt_update);
         }
      }
      
      if(key_pressed & (J_B)){
         MBC_BANK_ID = 1;
         STOP_SONG_FROM_MBC1_BANK_X();
         PLAY_SONG_FROM_MBC1_BANK_1();
         PRINT_MESSAGE(1, 6,"MBC1-Bank1 B : P ");
         PRINT_MESSAGE(1, 10,"Playing MBC1-BANK 1");
         PRINT_MESSAGE(1, 7,"MBC1-Bank2 A : NP");
         if(gbt_update_released == 0){
            gbt_update_released = 1;
            add_VBL(gbt_update);
         }
      }
      
      if(key_pressed & (J_START)){
         STOP_SONG_FROM_MBC1_BANK_X();
         PRINT_MESSAGE(1, 6,"MBC1-Bank1 B : NP");
         PRINT_MESSAGE(1, 7,"MBC1-Bank2 A : NP");
         PRINT_MESSAGE(1, 10,"                   ");
         if(MBC_BANK_ID){
            PRINT_MESSAGE(1, 10,"Stopping MBC1-Bank1");
         }else{
            PRINT_MESSAGE(1, 10,"Stopping MBC1-Bank2");
         }
         delay(1000);
         PRINT_MESSAGE(1, 10,"                   ");
      }
      
      wait_vbl_done();
   }
}

void PLAY_SONG_FROM_MBC1_BANK_1(){
   SWITCH_ROM_MBC1(1);
   gbt_play(song_Data, 1, 7);
   gbt_loop(0);
}

void PLAY_SONG_FROM_MBC1_BANK_2(){
   SWITCH_ROM_MBC1(2);
   gbt_play(song_Data, 2, 7);
   gbt_loop(0);
}

void STOP_SONG_FROM_MBC1_BANK_X(){
   gbt_stop();
}

void PRINT_MAIN_SFX_MENU(void){
   PRINT_MESSAGE(0, 1, "-------------------");
   PRINT_MESSAGE(0, 2, " MUSIC FOR GAMEBOY");
   PRINT_MESSAGE(0, 3, "-------------------");
   PRINT_MESSAGE(1, 6,"MBC1-Bank1 B : NP");
   PRINT_MESSAGE(1, 7,"MBC1-Bank2 A : NP");
   PRINT_MESSAGE(2, 14, "----------------");
   PRINT_MESSAGE(5, 15, "RYOGA 2014");
   PRINT_MESSAGE(2, 16, "----------------");
}

void PRINT_MESSAGE(int x, int y, char *c){
   gotogxy(x,y);
   gprintf(c);
}

void CLEAR_SCREEN_LINE ( UBYTE y ){
   UBYTE x;
   for (x = y*20 ; x < (y+1)*20 ; x++ ){
      set_bkg_data (x+tile_offset,1,(unsigned char *)TEXT_cEMPTY);
   }
}

void CLEAR_SCREEN_BACKGROUND(void){
   UBYTE x;
   for ( x = 0 ; x < tile_height ; x++ ){
      CLEAR_SCREEN_LINE ( x );
   }
}

void PLOT_BACKGROUND_IMAGE(int coord_X, int coord_Y, int tam_tile_X, int tam_tile_Y, unsigned char *tile_data, unsigned char *map_data){
   set_bkg_data(coord_X, coord_Y, tile_data);
   set_bkg_tiles(coord_X, coord_Y, tam_tile_X, tam_tile_Y, map_data);
   SHOW_BKG;
   DISPLAY_ON;
   delay(2000);
}


Al igual que entradas anteriores no me voy a detener en las funciones ya explicadas con anterioridad (mostrar texto, mover cursos, mostrar imágenes de fondo, etc). Esta entrada además tiene un componente especial debido a que voy a usar la técnica conocida como "Bank Switching" lo que me permitirá poder entre dos melodias que sse van a escuchar en nuestra Game Boy. Por eso antes de entrar a explicar el código que os he puesto vamos con un poco de teoría.

BANK SWITCHING: la memoria principal de la Game Boy, mapeada en un espacio de 16 bit, nos permite direccionar directamente 64K (65536 bits). En este espacio de direcciones, tenemos que direccionar todos los bloques de memoria a los que la Game Boy necesita acceder, esto es, la RAM, la ROM del cartucho, memoria de video,la RAM interna del cartucho para los juegos nos permiten salvar partida, etc.

En la GameBoy la memoria se mapea en diferentes bloques, como la RAM interna o la memoria de video, dejando dos bloques de 16K para el acceso a la ROM de los juegos, y un bloque de 8K para el acceso a la RAM de los juegos (partidas salvadas). Debido a que muchos juegos empezaron a necesitar de más de 32K de ROM o de 8K de RAM de salvado, se empezó a emplear una técnica denominada “Banking” o "Bank Switching", que consiste en dividir la ROM del juego en diversos bloques que se puedan independizar (los gráficos o sonidos de cada pantalla por ejemplo), los cuales se van mapeando en el bloque de acceso a la memoria según sean necesarios.

Según lo anterior tendremos un bloque fijo de 16K(donde es recomendable meter todo el código asociado a la lógica del juego), y luego, mediante ciertas instrucciones (dependiendo del chip de mapping que usemos en nuestro cartucho), podemos ir intercambiando bancos de 16K en el otro bloque disponible. Es necesario por tanto ir guardando todo lo que no sea la lógica del juego, en bancos de memoria diferentes al primero, para ello usaremos el controlador de memoria "MBC1" que soporta el kit de desarrollo GBDK.

Aunque ahora en cuanto me meta con el código voy a ser algo más explicito os indico desde ya como vamos a ir cambiando de un banco a otro. Para ello usaremos la siguiente definición incluida en el fichero de cabera "gb.h".

#define SWITCH_ROM_MBC1(b) \
  *(unsigned char *)0x2000 = (b)


Por tanto para cambiar al banco de memoria 1 invocaremos desde el código SWITCH_ROM_MBC1(1); y para cambiar al banco 2 usaremos SWITCH_ROM_MBC1(2); y así hasta el número máximos de bancos que se nos permiten definir. Con el controlador de memoria MBC1 dispondremos de hasta 32 bancos de memoria (512 KB) siendo el primer banco el 1. Por otro lado a la hora de compilar será necesario añadir nuevas lineas para definir que los datos van al banco X. Por ejemplo:

lcc -Wf-bo2 -c -o  music.o music.c


Con el flag de compilación "-Wf-bo2" indicamos que los datos compilados y obtenidos en el fichero objeto music.o se guardan en el banco 2 luego en nuestro código antes de las sentencias que hagan uso de esos tendremos que poner SWITCH_ROM_MBC1(2); ¡Así de simple!. Yo os recomiendo que os vayáis poniendo las pilas en esto del "bank switching" porque es práctica habitual. A parte de lo ya mencionado luego en el compilado general hay que incluir el flag -Wl-ytX para definir el tipo de ROM a generar. Los tipos posibles son los siguientes: (el MBC5 no lo incluyo porque a pesar de que tiene definiciones en el GBDK creo que aún a día de hoy da algún que otro problema):

0. ROM ONLY
1. ROM+MBC1
2. ROM+MBC1+RAM
3. ROM+MBC1+RAM+BATTERY
5. ROM+MBC2
6. ROM+MBC2+BATTERY


Para este tutorial he hecho uso la librería GBT PLAYER desarrollada por "Antonio Niño Díaz" (https://github.com/AntonioND/gbt-player). La libreria consta de un conversor de .MOD a GBT (Game Boy Tracker) y del player que permitirá que la música suene en la consola. Como ya habréis adivinado la jugada consiste en utilizar algún tracker tipo "Impulse Tracker" o "Milcky Tracker" para crear nuestra propia música y a continuación usar el conversor de .MOD a GBT para luego usar el player y reproducir el audio. Sobre el tema de los trackers os recomiendo que a parte de los ya mencionados le echéis un ojo a este: DEFLEMASK (http://www.delek.com.ar/deflemask) el cual es muy completo y de entrada permite emular mogollón de chipsets de audio de diferentes consolas de 8 y 16 bits.

Imagen

Digo esto porque el conversor que vamos a usar no es perfecto y un fichero .MOD que en el PC suena de una forma en la GB suena de otra. así que usando DEFLEMASK podremos crear una pieza musical que haga uso del chipset de sonido del GB y escucharlo en el PC prácticamente igual a como lo haría en la GB real. A parte de eso tenemos las siguientes limitaciones: la Game Boy tiene 4 canales: 2 de onda cuadrada, 1 canal WAVE y 1 canal de ruido. Esto implica que nuestros .MOD se tienen que ajustar a tener sólo 4 canales y no más 30 notas por patrón. Parece una limitación muy grande pero os recuerdo que sólo con 4 canales la gente de KONAMI (por poner un ejemplo) se las ingenió para realizar la magnifica banda sonora del Castlevania II: Belmont´s Revenge...con eso lo digo todo. Respecto a la composición de la música con los trackers desde el PC os invito una vez más a que os busquéis unos buenos tutoriales al respecto pues la creación de música con trackers se queda fuera de este tutorial (en mi caso para este ejemplo yo he usado dos ficheros .MOD ya existentes, uno de ellos generado por el creador del GBT PLAYER y el otro lo descargué desde el sitio web The Mod Archive). Además tendremos que tener en cuenta que el GBT PLAYER no es perfecto y tiene algún error. Uno de los bugs conocidos se presenta si usamos el comando DXX, si lo usamos al final de un patrón, no saltará al siguiente como debería, sino que salta dos patrones. Por otro lado comentaros que cuando generamos el fichero de cabecera .h con los datos de la canción a partir del fichero .MOD de 4 canales usando la herramienta mod2gbt (incluida en la librería GBT PLAYER) por defecto sino especificamos nada los datos de la canción se almacenarán en el banco de memoria 2.

Dicho todo lo anterior vamos a mirar un poco el código (parece que lo escribí hace horas, con tanta explicación sobre trackers y "bank switching"...XD). El código principal de nuestro main() empieza como de costumbre con las funciones que de forma automática cargan una imagen de fondo a modo de presentación y a continuación hay otra función para mostrar el menú de la ROM (no las explicaré porque ya expliqué en su día funciones similares para otras ROMs/ejemplos que hemos realizado anteiormente). A continuación el búcle while:

while(1){
      key_pressed = joypad();
      
      if(key_pressed & (J_A)){
         MBC_BANK_ID = 0;
         STOP_SONG_FROM_MBC1_BANK_X();
         PLAY_SONG_FROM_MBC1_BANK_2();
         PRINT_MESSAGE(1, 6,"MBC1-Bank1 B : NP");
         PRINT_MESSAGE(1, 10,"Playing MBC1-BANK 2");
         PRINT_MESSAGE(1, 7,"MBC1-Bank2 A : P ");
         if(gbt_update_released == 0){
            gbt_update_released = 1;
            add_VBL(gbt_update);
         }
      }
      
      if(key_pressed & (J_B)){
         MBC_BANK_ID = 1;
         STOP_SONG_FROM_MBC1_BANK_X();
         PLAY_SONG_FROM_MBC1_BANK_1();
         PRINT_MESSAGE(1, 6,"MBC1-Bank1 B : P ");
         PRINT_MESSAGE(1, 10,"Playing MBC1-BANK 1");
         PRINT_MESSAGE(1, 7,"MBC1-Bank2 A : NP");
         if(gbt_update_released == 0){
            gbt_update_released = 1;
            add_VBL(gbt_update);
         }
      }
      
      if(key_pressed & (J_START)){
         STOP_SONG_FROM_MBC1_BANK_X();
         PRINT_MESSAGE(1, 6,"MBC1-Bank1 B : NP");
         PRINT_MESSAGE(1, 7,"MBC1-Bank2 A : NP");
         PRINT_MESSAGE(1, 10,"                   ");
         if(MBC_BANK_ID){
            PRINT_MESSAGE(1, 10,"Stopping MBC1-Bank1");
         }else{
            PRINT_MESSAGE(1, 10,"Stopping MBC1-Bank2");
         }
         delay(1000);
         PRINT_MESSAGE(1, 10,"                   ");
      }
      
      wait_vbl_done();
   }


Como veis este código lo que hace así de forma intuitiva es en función del botón pulsado (A, B o START) reproducir una canción u otra o bien detener la reproducción de la canción en curso. Vamos con el bloque relacionado con el botón A (para el botón B sería igual pero con el banco de memoria 1).

if(key_pressed & (J_A)){
         MBC_BANK_ID = 0;
         STOP_SONG_FROM_MBC1_BANK_X();
         PLAY_SONG_FROM_MBC1_BANK_2();
         PRINT_MESSAGE(1, 6,"MBC1-Bank1 B : NP");
         PRINT_MESSAGE(1, 10,"Playing MBC1-BANK 2");
         PRINT_MESSAGE(1, 7,"MBC1-Bank2 A : P ");
         if(gbt_update_released == 0){
            gbt_update_released = 1;
            add_VBL(gbt_update);
         }
      }


Del flag MBC_BANK_ID vamos a pasar "3 kilos" pues al final no le di uso, pero se ha quedado ahí en el código. La función STOP_SONG_FROM_MBC1_BANK_X(); lo que hace como su nombre indica es detener la reproducción de la canción que estuviese en curso (si es que había alguna). Internamente llama a la función del player gbt_stop().

void STOP_SONG_FROM_MBC1_BANK_X(){
   gbt_stop();
}


La función PLAY_SONG_FROM_MBC1_BANK_2(); como su nombre indica lo que hace es cambiar al banco de memoria 2 y reproducir la canción que este ahí almacenada.

void PLAY_SONG_FROM_MBC1_BANK_2(){
   SWITCH_ROM_MBC1(2);
   gbt_play(song_Data, 2, 7);
   gbt_loop(0);
}


gbt_play(song_Data, 2, 7); permite reproducir la música. Tiene por parámetros la estructura "song_Data" generada con el conversor .MOD a GBT y que está almacenada en un fichero xxxxx.c. Dicha estructura está declarada como extern al principio del programa. El siguiente parámetro, el 2, es el banco de memoria y el 7 es la velocidad de reproducción. Finalmente gbt_loop(0); permite indicar si la reproducción del tema musical es en bucle. A continuación hay una serie de instrucciones para mostrar texto y luego lo siguiente:

if(gbt_update_released == 0){
       gbt_update_released = 1;
      add_VBL(gbt_update);
}


gbt_update_released es un flag que he creado para saber cuando se tiene que llamar a la función add_VBL(gbt_update); y en que momento. Dicha función debe ser llamada una vez en cada frame con lo que se va comprando el valor del flag que he creado para saber cuando se tiene que llamar dado que al estar dentro del while() se ejecutaría siempre sino es por el if() y tendríamos errores. Y poco más. En el caso del botón B sería lo mismo pero para cambiar el banco de memoria a utilizar al 1. En el caso del botón START lo que se hace es invocar a la función STOP_SONG_FROM_MBC1_BANK_X(); para detener la reproducción de la música con independencia del banco usado. Así a bote pronto diría qué el programa propuesto puede ser un poco complicado por el tema del "bank switching" pero como os decía será mejor que os acostumbréis a usarlo. Finalmente os pongo todo lo necesario para compilar este ejemplo:

mod2gbt .\mods\cave_story.mod song -c 2
ren output.c output_bank2.c
mod2gbt .\mods\template.mod songb -c 1
ren output.c output_bank1.c
lcc -Wa-l -Wl-m -Wl-j -DUSE_SFR_FOR_REG -c -o testSoundMusic.o testSoundMusic.c
lcc -Wf-bo2 -c -o output_bank2.o output_bank2.c
lcc -Wf-bo1 -c -o output_bank1.o output_bank1.c
lcc -Wa-l -Wl-m -Wl-j -DUSE_SFR_FOR_REG -c -o gbt_player.o gbt_player.s
lcc -Wa-l -Wl-m -Wl-j -DUSE_SFR_FOR_REG -c -o gbt_player_bank1.o gbt_player_bank1.s
lcc -Wa-l -Wl-m -Wl-j -DUSE_SFR_FOR_REG -Wl-yt1 -Wl-yo4 -Wl-ya0 -o SoundMusic-simpleBankingMBC1-20141109.gb testSoundMusic.o output_bank2.o output_bank1.o gbt_player.o gbt_player_bank1.o

del *.o *.lst *.map *.sym
pause


Y poco más por mi parte. Deciros que la próxima entrada será la dedicada al primer juego que vamos a desarrollar en este hilo para la Game Boy (las explicaciones sobre sprites vendrán en una entrada futura, pero no os preocupéis por eso dado que en el clón de PONG que os voy a presentar no habrá muchos sprites y las explicaciones necesarias para que entendáis de donde vienen serán aclaradas como de costumbre).

Además del código de antes os dejo también un código sencillo para reproducir un único tema musical usando la librería GBT PLAYER para que podáis empezar con algo más sencillo y luego os metáis con el "Bank Switching".

#include <gb/gb.h>
#include <gb/drawing.h>
#include gbt_player.h"

extern const unsigned char * song_Data[];

void main(){

    gbt_play(song_Data, 2, 7);
    gbt_loop(0);

    add_VBL(gbt_update);

    PRINT_MESSAGE(1, 1,"GAMEBOY MUSIC TEST");
    PRINT_MESSAGE(1, 2,"------------------");

    while(1){
        wait_vbl_done();
    }
}

void PRINT_MESSAGE(int x, int y, char *c){
   gotogxy(x,y);
   gprintf(c);
}


Saludos y nos leemos en la próxima entrada.

PD-1: ¿alguien ha reconocido la música que suena en la ROM de esta entrada cuando se reproducen los datos del banco de memoria 2?. :cool:
PD-2: dentro de poco tendré listo en mi repositorio de GitHub con los proyectos que hasta ahora he publicado en este hilo sobre desarrollo para GB/GBC/GBA para que los podáis descargar directamente.
Menuda pasada. En principio me parece que hay que coger algo de nivel para ir haciéndolo todo correctamente pero creo que es viable usar este procedimiento para hacer música para la gameboy. ¿La otra alternativa sería usar algún tracker tipo nanoloop o bien usar la otra técnica que explicabas en la entrada anterior para los efectos FX? Es decir, ¿de otra forma habría que ir registro a registro metiendo las notas que nos interesen?

Sigue así tio.
Impresionante. Y si, yo creo que la música que suena cuando cambias al banco de memoria 2 es la del Cave Story en una versión de 4 canales. Una vez más gracias por los tutoriales tio. Están cojonudos.
Spartark escribió:Menuda pasada. En principio me parece que hay que coger algo de nivel para ir haciéndolo todo correctamente pero creo que es viable usar este procedimiento para hacer música para la gameboy. ¿La otra alternativa sería usar algún tracker tipo nanoloop o bien usar la otra técnica que explicabas en la entrada anterior para los efectos FX? Es decir, ¿de otra forma habría que ir registro a registro metiendo las notas que nos interesen?

Sigue así tio.


No he usado el nanoloop. Conozco algo más el LSDJ, pero no sabría decirte si es posible coger el .SAV del LSDJ para poder cargar la canción generada para usarla aquí. Si es posible hacerlo y alguien tiene más información lo podría compartir por aquí. La otra alternativa a usar el MOD2GBT como dices es ir con los registros a pelo e ir cargando las notas a mano.

AirMaster escribió:Impresionante. Y si, yo creo que la música que suena cuando cambias al banco de memoria 2 es la del Cave Story en una versión de 4 canales. Una vez más gracias por los tutoriales tio. Están cojonudos.


Efectivamente se trata de la melodía del CAVE STORY.

¡¡¡ACTUALIZACIÓN!!!

Ping Pong Diplomacy Prototype「2014-10-28」(TEASER) (Game Boy Homebrew) - GAME BOY C PROGRAMMING

https://www.youtube.com/watch?v=h24eEueGxVI

Imagen

Bueno aquí dejo un nuevo teaser relacionado con la próxima entrada asociada al desarrollo para GameBoy. Se trata de otro video sobre el avance del clón del PONG que estoy desarrollando. En este y aunque no se note en el video ahora hay efectos de sonido cuando la pelota rebota en la palas o bien en los muros superior e inferior. Estoy intentando componer alguna melodía pegadiza para la intro del juego y por otro lado en el video se parecia también el uso de una función de texto específica para mostrar texto con formato. Al juego le quedan unos detalles más por pulir y estará disponible próximamente para su descarga (ROM + fuentes), y por supuesto habrá entrada en este hilo para explicar todo el proceso. A pesar de que el diseño de gráficos y el formateo de texto las tengo planificadas como entradas posteriores no os preocupéis porque daré las explicaciones oportunas para que todo se pueda entender. Espero poder la nueva entrada en poco tiempo.

Saludos.
Genial. Ya hay ganas de que llegue la nueva entrada.
AirMaster escribió:Genial. Ya hay ganas de que llegue la nueva entrada.


Espero no tardar mucho tiempo. Antes de final de año es seguro que este la nueva entrada con el desarrollo del primer juego para GameBoy de este tutorial.

Saludos.
Hay ganas de ver todo lo aprendido en las lecciones anteriores en una misma rom.
Buenas a todos. Ya he terminado el primer juego para Game Boy que va a salir de este hilo. El video de cómo hacer el juego está disponible en mi canal de YT, de momento lo dejo enlazado por aquí, pero no os preocupéis porque a lo largo de la semana me curraré una entrada completa en este hilo para ir comentando paso por paso lo que considero más importante del mismo como he hecho hasta ahora.



En lo que queda de año no me dará tiempo a crear el blog para ir añadiendo todo el contenido generado hasta ahora, pero se queda pendiente hasta que termine un par de entradas más.

Saludos.
Pues ha quedado un clón del pong muy resultón. Esperando por esa entrada explicativa.
TUTORIAL DE PROGRAMACIÓN PARA GAME BOY - PARTE #7: (Juego 1) PING PONG DIPLOMACY

Imagen

Después de la parada navideña vamos a continuar con el tema de desarrollo para Game Boy y en esta ocasión vamos a centrarnos en el primer juego completo que traigo a este hilo. Si recordáis ya dejé hace un par de semanas un video con todo el proceso de desarrollo y pruebas del juego. Echadle un ojo de nuevo al video así como al código fuente que dejo aquí a continuación.

PING-PONG DIPLOMACY (GAME BOY) - GBDK: Game Boy C Programming [GAMEBOY HOMEBREW]


//*****************************************
//   Ping Pong Diplomacy for GameBoy.
// Programmed by jduranmaster a.k.a. Ryoga
//*****************************************

#include <gb/gb.h>
#include <stdio.h>
#include <ctype.h>
#include <gb/console.h>
#include <gb/drawing.h>

#include "images/_blank_text.h"
#include "images/x_congrats_win.h"
#include "images/hammer_keyboard_.h"
#include "images/x_production_first.h"
#include "images/x_production_second.h"
#include "images/x_pongdiplomacy_logo.h"

static const unsigned char TEXT_cEMPTY[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

unsigned char backgroundcharacters[] = {
   //esquina superior izquierda
   0xFF,0xFF,0xFF,0xFF,0xC0,0xC0,0xC0,0xC0,
   0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,
   //esquina superior derecha
   0xFF,0xFF,0xFF,0xFF,0x03,0x03,0x03,0x03,
   0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
   //esquina inferior izquierda
   0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,
   0xC0,0xC0,0xC0,0xC0,0xFF,0xFF,0xFF,0xFF,
   //esquina inferior derecha
   0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
   0x03,0x03,0x03,0x03,0xFF,0xFF,0xFF,0xFF,
   //pared de arriba
   0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,
   0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
   //pared derecha
   0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
   0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,
   //pared de abajo
   0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
   0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,
   //pared izquierda
   0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,
   0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,
   //relleno
   0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
   0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};

signed char spritetiles[] = {
   //paddle
   255,0,255,0,255,0,255,0,255,0,255,0,255,0,255,0,
   //bola
   255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
   //null
   0x00,0x00,0x18,0x18,0x24,0x24,0x24,0x24,
   0x24,0x24,0x24,0x24,0x18,0x18,0x00,0x00,
   //1
   0x00,0x00,0x08,0x08,0x18,0x18,0x28,0x28,
   0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00,
   //2
   0x00,0x00,0x18,0x18,0x24,0x24,0x08,0x08,
   0x10,0x10,0x20,0x20,0x3C,0x3C,0x00,0x00,
   //3
   0x38,0x38,0x04,0x04,0x04,0x04,0x38,0x38,
   0x04,0x04,0x04,0x04,0x38,0x38,0x00,0x00,
   //4
   0x08,0x08,0x10,0x10,0x20,0x20,0x48,0x48,
   0x7C,0x7C,0x08,0x08,0x08,0x08,0x00,0x00,
   //5
   0x00,0x00,0x3C,0x3C,0x20,0x20,0x38,0x38,
   0x04,0x04,0x04,0x04,0x38,0x38,0x00,0x00,
   //6
   0x00,0x00,0x1C,0x1C,0x20,0x20,0x20,0x20,
   0x3C,0x3C,0x24,0x24,0x3C,0x3C,0x00,0x00,
   //7
   0x00,0x00,0x3C,0x3C,0x04,0x04,0x08,0x08,
   0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
   //8
   0x18,0x18,0x24,0x24,0x24,0x24,0x18,0x18,
   0x24,0x24,0x24,0x24,0x18,0x18,0x00,0x00,
   //9
   0x18,0x18,0x24,0x24,0x24,0x24,0x1C,0x1C,
   0x04,0x04,0x04,0x04,0x18,0x18,0x00,0x00,
   //10
   0x26,0x26,0x69,0x69,0xA9,0xA9,0x29,0x29,
   0x29,0x29,0x29,0x29,0x26,0x26,0x00,0x00
};

signed char collision_left_up[] = {
   3,-1,1,0,
   3,-1,1,0,
   3,-1,1,0,
   3,-1,1,0,
   //--
   3,-1,2,-1,
   3,-1,2,-1,
   3,-1,2,-1,
   3,-1,2,-1,
   //--
   3,-2,2,-1,
   3,-2,2,-1,
   3,-2,2,-1,
   3,-2,2,-1,
   //--
   3,-2,2,-1,
   3,-2,2,-1,
   3,-2,2,-1,
   3,-2,2,-1
};

signed char collision_left_down[] = {
   3,1,1,0,
   3,1,1,0,
   3,1,1,0,
   3,1,1,0,
   //--
   3,1,2,1,
   3,1,2,1,
   3,1,2,1,
   3,1,2,1,
   //--
   3,2,2,1,
   3,2,2,1,
   3,2,2,1,
   3,2,2,1,
   //--
   3,2,3,1,
   3,2,3,1,
   3,2,3,1,
   3,2,3,1
};

signed char collision_right_up[] =  {
   -3,-1,-1,0,
   -3,-1,-1,0,
   -3,-1,-1,0,
   -3,-1,-1,0,
   //--
   -3,-1,-2,-1,
   -3,-1,-2,-1,
   -3,-1,-2,-1,
   -3,-1,-2,-1,
   //--
   -3,-2,-2,-1,
   -3,-2,-2,-1,
   -3,-2,-2,-1,
   -3,-2,-2,-1,
   //--
   -3,-2,-3,-1,
   -3,-2,-3,-1,
   -3,-2,-3,-1,
   -3,-2,-3,-1
};

signed char collision_right_down[] = {
   -3,1,-1,0,
   -3,1,-1,0,
   -3,1,-1,0,
   -3,1,-1,0,
   //--
   -3,1,-2,1,
   -3,1,-2,1,
   -3,1,-2,1,
   -3,1,-2,1,
   //--
   -3,2,-2,1,
   -3,2,-2,1,
   -3,2,-2,1,
   -3,2,-2,1,
   //--
   -3,2,-3,1,
   -3,2,-3,1,
   -3,2,-3,1,
   -3,2,-3,1
};
   
signed char bgmap[] = {
   //bgmap
   0,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,1,
   7,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,5,
   2,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,3,
   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8
};

//variables globales del programa.
UBYTE counter,y1,y2,y3,y4,y5,y6;
UBYTE ball_pos_x;
UBYTE ball_pos_y;
BYTE ball_vector_x1;
BYTE ball_vector_x2;
BYTE ball_vector_y1;
BYTE ball_vector_y2;
UBYTE vector_pointer;
UBYTE game_status;
UBYTE colx;
UBYTE coly;
BYTE temp1;
BYTE temp2;
UBYTE col0;
UBYTE col1;
BYTE* col_table;
BYTE score_p1;
BYTE score_p2;
BYTE score_counter_p1;
BYTE score_counter_p2;
int controller = 0;

// variables de control del texto.
UBYTE tile_offset = 1;
UBYTE tile_height = 18;

// funciones del programa.
void RESET_TENNIS_COURT_BACKGROUND(void);
void RESET_COORDS();
void CLEAR_SCREEN_TEXT();
void PRINT_SOLID_MARQUEE();
void PLOT_SET_BACKGROUND_LOGO();
void CLEAR_SCREEN_LINE (UBYTE y);
void CLEAR_SCREEN_BACKGROUND(void);
void PLAY_PADDLE_SOUND_EFFECT(void);
void PLAY_UL_WALL_SOUND_EFFECT(void);
void PLAY_LR_WALL_SOUND_EFFECT(void);
void INIT_REGISTERS_SOUND_EFECTS(void);
void PRINT_MESSAGE(int x, int y, char *c);
UBYTE CHECK_COLLISION_BALL_PADDLE(UBYTE paddleNr);
void PLOT_BACKGROUND_IMAGE(int coord_X, int coord_Y, int tam_tile_X, int tam_tile_Y, unsigned char *tile_data, unsigned char *map_data, int millis);

void main(){
   PLOT_SET_BACKGROUND_LOGO();
   PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, x_pongdiplomacy_logo_tile_data, x_pongdiplomacy_logo_map_data, 2000);
   waitpad(J_START);
   
   ///// COMIENZA EL BLOQUE BITMAP DONDE SE MUESTRA EL TEXTO DEL JUEGO.
   PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, _blank_text_tile_data, _blank_text_map_data, 2000);
   waitpad(J_START);
   ///// TERMINA EL BLOQUE BITMAP DONDE SE MUESTRA EL TEXTO DEL JUEGO.
   
   disable_interrupts();
   DISPLAY_OFF;
   
   RESET_COORDS();
   
   //fijar las puntuaciones a cero.
   score_p1 = 0;
   score_p2 = 0;
   score_counter_p1 = 0;
   score_counter_p2 = 0;

   //background
   set_bkg_data(0,10,backgroundcharacters);
   //background
   set_bkg_tiles(0, 0, 20, 1, bgmap);
   //background
   for(counter=1;counter<=13;counter++) set_bkg_tiles(0,counter,20,1,bgmap+20);
   set_bkg_tiles(0,14,20,1,bgmap+40);
   
   for(counter=15;counter<=17;counter++) set_bkg_tiles(0,counter,20,1,bgmap+20);
   set_bkg_tiles(0,14,20,1,bgmap+40);
   
   //cargar los sprites.
   SPRITES_8x8;
   //cargar los tiles del paddle.
   set_sprite_data(0, 1, spritetiles);
   //cargar los tiles de la bola.
   set_sprite_data(1, 1, spritetiles+16);
   //cargar los tiles de las puntuaciones.
   set_sprite_data(2, 1, spritetiles+32);
   set_sprite_data(3, 1, spritetiles+48);
   set_sprite_data(4, 1, spritetiles+64);
   set_sprite_data(5, 1, spritetiles+80);
   set_sprite_data(6, 1, spritetiles+96);
   set_sprite_data(7, 1, spritetiles+112);
   set_sprite_data(8, 1, spritetiles+128);
   set_sprite_data(9, 1, spritetiles+144);
   set_sprite_data(10, 1, spritetiles+160);
   set_sprite_data(11, 1, spritetiles+176);
   set_sprite_data(12, 1, spritetiles+192);
   
   //paddle 0. Controlada por el usuario. [China].
   set_sprite_tile(0,0);
   set_sprite_tile(1,0);
   set_sprite_tile(2,0);
   
   //paddle 1. Controlado por la CPU. [USA].
   set_sprite_tile(3,0);
   set_sprite_tile(4,0);
   set_sprite_tile(5,0);
   //bola.
   set_sprite_tile(6,1);
   
   //activar fondos y sprites.
   SHOW_BKG;
   SHOW_SPRITES;
   DISPLAY_ON;
   enable_interrupts();
   
   INIT_REGISTERS_SOUND_EFECTS();
   
   //Control del programa.
   while(1) {
      wait_vbl_done();

      set_sprite_tile(7,2+score_counter_p1);
      set_sprite_tile(8,2+score_counter_p2);
      
      if((score_p1 == 10 || score_p2 == 10) || controller == 1){
         if((score_counter_p2 == 10 || score_counter_p1 == 10)){
            HIDE_SPRITES;
            PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, x_congrats_win_tile_data, x_congrats_win_map_data, 1000);
            waitpad(J_START);
            CLEAR_SCREEN_BACKGROUND();
            RESET_COORDS();
            controller = 0;
         }
         
         if(controller == 1){
            score_p1 = 0;
            score_p2 = 0;
            set_sprite_tile(7,2+score_counter_p1);
            set_sprite_tile(8,2+score_counter_p2);
         }else{
            score_p1 = 0;
            score_p2 = 0;
            score_counter_p1 = 0;
            score_counter_p2 = 0;
            ball_vector_x1 = 1;
            ball_vector_x2 = 1;
            ball_vector_y1 = 1;
            ball_vector_y2 = 1;
            
            set_bkg_data(0,10,backgroundcharacters);
            set_bkg_tiles(0, 0, 20, 1, bgmap);
            for(counter=1;counter<=13;counter++) set_bkg_tiles(0,counter,20,1,bgmap+20);
            set_bkg_tiles(0,14,20,1,bgmap+40);
            for(counter=15;counter<=17;counter++) set_bkg_tiles(0,counter,20,1,bgmap+20);
            set_bkg_tiles(0,14,20,1,bgmap+40);
            
            SPRITES_8x8;
            set_sprite_data(0, 1, spritetiles);
            set_sprite_data(1, 1, spritetiles+16);
            set_sprite_data(2, 1, spritetiles+32);
            set_sprite_data(3, 1, spritetiles+48);
            set_sprite_data(4, 1, spritetiles+64);
            set_sprite_data(5, 1, spritetiles+80);
            set_sprite_data(6, 1, spritetiles+96);
            set_sprite_data(7, 1, spritetiles+112);
            set_sprite_data(8, 1, spritetiles+128);
            set_sprite_data(9, 1, spritetiles+144);
            set_sprite_data(10, 1, spritetiles+160);
            set_sprite_data(11, 1, spritetiles+176);
            set_sprite_data(12, 1, spritetiles+192);
            SHOW_SPRITES;
            game_status = 1;
            controller = 0;
         }
      }
      
      counter = joypad();
      
      // movimiento del paddle 0.
      if(counter & J_UP && y1>=20){
         y1-=1;
         y2-=1;
         y3-=1;
         if(CHECK_COLLISION_BALL_PADDLE(0)<16){
            y1+=1;
            y2+=1;
            y3+=1;
         }
      }
      if(counter & J_DOWN && y3<=124){
         y1+=1;
         y2+=1;
         y3+=1;
         if(CHECK_COLLISION_BALL_PADDLE(0)<16){
            y1-=1;
            y2-=1;
            y3-=1;
         }
      }

      // movimiento del paddle 1.
      if(y5 > ball_pos_y && y4 >= 20){
         y4-=1;
         y5-=1;
         y6-=1;
         if(CHECK_COLLISION_BALL_PADDLE(1)<16){
            y4+=1;
            y5+=1;
            y6+=1;
         }
      }
      if(y5 < ball_pos_y && y6 <= 124){
         y4+=1;
         y5+=1;
         y6+=1;
         if(CHECK_COLLISION_BALL_PADDLE(1)<16){
            y4-=1;
            y5-=1;
            y6-=1;
         }
      }

      if(counter & J_START && game_status==0){
         //si el juego ha terminado, se reinicia.
         if(score_counter_p2 == 10 || score_counter_p1 == 10){
            score_p1 = 0;
            score_p2 = 0;
            score_counter_p2 = 0;
            score_counter_p1 = 0;
         }
         ball_vector_x1 = 1;
         ball_vector_x2 = 1;
         ball_vector_y1 = 1;
         ball_vector_y2 = 1;
         game_status = 1; //el juego comienza de nuevo.
      }
      
      // movimiento de la bola.
      if(vector_pointer==0){
         temp1 = ball_vector_x1;
         temp2 = ball_vector_y1;
         vector_pointer = 1;
      }
      else{
         temp1 = ball_vector_x2;
         temp2 = ball_vector_y2;
         vector_pointer = 0;
      }
      ball_pos_x+=temp1;
      ball_pos_y+=temp2;
      col0=CHECK_COLLISION_BALL_PADDLE(0);
      if(col0<16){
         while(temp1!=0 || temp2!=0){
            if(temp1>0){
               temp1--;
               ball_pos_x--;
            }
            if(temp1<0){
               temp1++;
               ball_pos_x++;
            }
            if(temp2>0){
               temp2--;
               ball_pos_y--;
            }
            if(temp2<0){
               temp2++;
               ball_pos_y++;
            }
            if (CHECK_COLLISION_BALL_PADDLE(0)>=16) break;
         }
         PLAY_PADDLE_SOUND_EFFECT();
      }
      
      col1=CHECK_COLLISION_BALL_PADDLE(1);
      if (col1 < 16){
         while(temp1!=0 || temp2!=0){
            if(temp1 > 0){
               temp1--;
               ball_pos_x--;
            }
            if(temp1 < 0){
               temp1++;
               ball_pos_x++;
            }
            if(temp2 > 0){
               temp2--;
               ball_pos_y--;
            }
            if(temp2 < 0){
               temp2++;
               ball_pos_y++;
            }
            if (CHECK_COLLISION_BALL_PADDLE(1)>=16) break;
         }
         PLAY_PADDLE_SOUND_EFFECT();
      }

      // detección de colisiones.
      // pared izquierda.
      if(ball_pos_x<9){
         RESET_COORDS();
         score_p2++;
         PLAY_UL_WALL_SOUND_EFFECT();
         score_counter_p2++;
         //el jugador 2 anota un tanto.
         controller = 1;
      }
      // pared derecha.
      if(ball_pos_x>163){
         RESET_COORDS();
         score_p1++;
         score_counter_p1++;
         PLAY_UL_WALL_SOUND_EFFECT();
         //el jugador 1 anota un tanto.
         controller = 1;
      }
      
      // pared superior.
      if(ball_pos_y < 19){
         ball_pos_y = 19;
         ball_vector_y1--;
         ball_vector_y1=ball_vector_y1 ^ 255;
         ball_vector_y2--;
         ball_vector_y2=ball_vector_y2 ^ 255;
         PLAY_LR_WALL_SOUND_EFFECT();
      }
      
      // pared inferior.
      if(ball_pos_y > 125){
         ball_pos_y = 125;
         ball_vector_y1--;
         ball_vector_y1=ball_vector_y1 ^ 255;
         ball_vector_y2--;
         ball_vector_y2=ball_vector_y2 ^ 255;
         PLAY_LR_WALL_SOUND_EFFECT();
      }
      
      if(col0<16 && ball_pos_x == 23){
         // la bola se desplaza arriba.
         if(ball_vector_y1<0 || ball_vector_y2<0) col_table = &collision_left_up;
         // la bola se desplaza abajo.
         else col_table = &collision_left_down;
         col_table+=col0*4;
         ball_vector_x1 = *col_table;
         ball_vector_y1 = *(col_table+1);
         ball_vector_x2 = *(col_table+2);
         ball_vector_y2 = *(col_table+3);
      }

      if(col1<16 && ball_pos_x==145){
         // la bola se desplaza arriba.
         if(ball_vector_y1<0 || ball_vector_y2<0) col_table = &collision_right_up;
         // la bola se desplaza abajo.
         else col_table = &collision_right_down;
         col_table+=col1*4;
         ball_vector_x1 = *col_table;
         ball_vector_y1 = *(col_table+1);
         ball_vector_x2 = *(col_table+2);
         ball_vector_y2 = *(col_table+3);
      }

      //caso especial col0
      if(col0<16 && ball_pos_x!=23){
         ball_vector_y1--;
         ball_vector_y2--;
         ball_vector_y1=ball_vector_y1^255;
         ball_vector_y2=ball_vector_y2^255;
      }

      //caso especial col1
      if(col1<16 && ball_pos_x!=145){
         ball_vector_y1--;
         ball_vector_y2--;
         ball_vector_y1=ball_vector_y1^255;
         ball_vector_y2=ball_vector_y2^255;
      }

      //repintar sprite del paddle0 en función de su posición actual.
      move_sprite(0,15,y1);
      move_sprite(1,15,y2);
      move_sprite(2,15,y3);
      //repintar sprite del paddle1 en función de su posición actual.
      move_sprite(3,153,y4);
      move_sprite(4,153,y5);
      move_sprite(5,153,y6);
      ////repintar bola en función de su posición actual.
      move_sprite(6,ball_pos_x,ball_pos_y);
      //repintar puntuaciones.
      move_sprite(7,15,140);
      move_sprite(8,153,140);
   }
}

void PLOT_BACKGROUND_IMAGE(int coord_X, int coord_Y, int tam_tile_X, int tam_tile_Y, unsigned char *tile_data, unsigned char *map_data, int millis){
   set_bkg_data(coord_X, coord_Y, tile_data);
   set_bkg_tiles(coord_X, coord_Y, tam_tile_X, tam_tile_Y, map_data);
   SHOW_BKG;
   DISPLAY_ON;
   delay(millis);
}

void PLOT_SET_BACKGROUND_LOGO(){
   PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, x_production_first_tile_data, x_production_first_map_data, 2000);
   CLEAR_SCREEN_BACKGROUND();
   PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, hammer_keyboard__tile_data, hammer_keyboard__map_data, 2000);
   CLEAR_SCREEN_BACKGROUND();
   PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, x_production_second_tile_data, x_production_second_map_data, 2000);
   CLEAR_SCREEN_BACKGROUND();
}

void PRINT_SOLID_MARQUEE(){
   gotoxy(0, 0);
   color(BLACK, WHITE, SOLID);
   box(156,140,2,2,M_NOFILL);
   box(155,139,3,3,M_NOFILL);
   box(154,138,4,4,M_NOFILL);
   box(153,137,5,5,M_NOFILL);
   box(152,136,6,6,M_NOFILL);
}

void INIT_REGISTERS_SOUND_EFECTS(void){
   NR52_REG = 0xF8U;
   NR51_REG = 0x00U;
   NR50_REG = 0x77U;//0xFFU;
}

void PLAY_PADDLE_SOUND_EFFECT(void){
    NR10_REG = 0x34U;
   NR11_REG = 0x80U;
   NR12_REG = 0xF0U;
   NR13_REG = 0x0AU;
   NR14_REG = 0xC6U;
   NR51_REG |= 0x11;
}

void PLAY_UL_WALL_SOUND_EFFECT(void){
    NR41_REG = 0x00;//0x1FU;
    NR42_REG = 0xE1;//0xF7U;
    NR43_REG = 0x22;//0x24U;
    NR44_REG = 0xC3;//0x80U;
    NR51_REG = 0x88U;
}

void PLAY_LR_WALL_SOUND_EFFECT(void){
   NR10_REG = 0x04U;
   NR11_REG = 0xFEU;
   NR12_REG = 0xA1U;
   NR13_REG = 0x8FU;
   NR14_REG = 0x86U;
   NR51_REG = 0xF7;
}

void RESET_COORDS(){
   //pos inicial paddle0.
   y1 = 65;
   y2 = 73;
   y3 = 81;
   //pos inicial paddle1.
   y4 = 65;
   y5 = 73;
   y6 = 81;
   //pos inicial bola.
   ball_pos_x=80;
   ball_pos_y=77;
   //inicializar el vector de movimiento.
   ball_vector_x1 = 0;
   ball_vector_x2 = 0;
   ball_vector_y1 = 0;
   ball_vector_y2 = 0;
   vector_pointer = 0;
   
   // juego en pausa.
   game_status = 0;
   return;
}

UBYTE CHECK_COLLISION_BALL_PADDLE(UBYTE paddleNr){
   //paddle0
   if(paddleNr==0){
      if(ball_pos_x>=15)
         colx = ball_pos_x - 15;   
      else
         colx = 15 - ball_pos_x;
      if (colx<8){
         if (ball_pos_y >= y2) coly = ball_pos_y - y2;
         else coly = y2 - ball_pos_y;
         return coly;
      }
      return 16;
   }
   //paddle1
   else{
      
      if(ball_pos_x>=153)
         colx = ball_pos_x - 153;
      else
         colx = 153 - ball_pos_x;
      if (colx<8){
         if (ball_pos_y >= y5) coly = ball_pos_y - y5;
         else coly = y5 - ball_pos_y;
         return coly;
      }
      return 16;
   }
}

void PRINT_MESSAGE(int x, int y, char *c){
   gotogxy(x,y);
   gprintf(c);
}

void CLEAR_SCREEN_LINE ( UBYTE y ){
   UBYTE x;
   for (x = y*20 ; x < (y+1)*20 ; x++ ){
       set_bkg_data (x+tile_offset,1,(unsigned char *)TEXT_cEMPTY);
     }
}

void CLEAR_SCREEN_BACKGROUND ( void ){
   UBYTE x;
   for ( x = 0 ; x < tile_height ; x++ ){
       CLEAR_SCREEN_LINE ( x );
     }
}

void CLEAR_SCREEN_TEXT(){
   int x, y;
   
     for(y = 0; y < 20; y++){
       for(x = 0; x < 30; x++){
            gotoxy(x, y);
            gprintf(" ");
      }
   }
   gotoxy(0,0);
}



Si le echáis un vistazo al código fuente os daréis cuenta de que he utilizado todo lo visto hasta ahora en los tutoriales anteriores para realizar este clon del PONG para Game Boy. El único efecto en si que no he introducido ha sido el de meter música de back-ground, más que nada porque no encontraba una melodía que me gustase realmente para este fin, pero si que he incluido efectos de sonido para cuando la pelota golpea las palas o cuando se anota un tanto. Igualmente he incluido funciones que ya había utilizado en entradas anteriores para definir marcos, poder incluir back-grounds, etc, etc. Por ese motivo, en esta entrada sólo me limitaré a explicar algunas funciones relacionadas con el juego en si y aquellos aspectos nuevos que no se hayan visto antes en este hilo.

A continuación podéis apreciar una serie variables que representan los sprites para las palas y la bola así como los sprites de los números que aparecen en los contadores de puntos (números del 0 al 9), etc. Estas variables (sprites) están generadas con la herramienta GBTD. Como todavía no hemos llegado a la parte de como crear sprites y fondos usando las herramientas GBTD y GBMB de momento os tendréis que creer que estás variables los representan y punto (culpa mia, por no haber hecho en su momento una entrada anterior tratando ese tema). De todas formas dado que el juego es un clón del Pong, no intervienen demasiados gráficos (sólo estos que os comento).

signed char spritetiles[] = {
   //paddle
   255,0,255,0,255,0,255,0,255,0,255,0,255,0,255,0,
   //bola
   255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
   //null
   0x00,0x00,0x18,0x18,0x24,0x24,0x24,0x24,
   0x24,0x24,0x24,0x24,0x18,0x18,0x00,0x00,
   //1
   0x00,0x00,0x08,0x08,0x18,0x18,0x28,0x28,
   0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00,
   //2
   0x00,0x00,0x18,0x18,0x24,0x24,0x08,0x08,
   0x10,0x10,0x20,0x20,0x3C,0x3C,0x00,0x00,
   //3
   0x38,0x38,0x04,0x04,0x04,0x04,0x38,0x38,
   0x04,0x04,0x04,0x04,0x38,0x38,0x00,0x00,
   //4
   0x08,0x08,0x10,0x10,0x20,0x20,0x48,0x48,
   0x7C,0x7C,0x08,0x08,0x08,0x08,0x00,0x00,
   //5
   0x00,0x00,0x3C,0x3C,0x20,0x20,0x38,0x38,
   0x04,0x04,0x04,0x04,0x38,0x38,0x00,0x00,
   //6
   0x00,0x00,0x1C,0x1C,0x20,0x20,0x20,0x20,
   0x3C,0x3C,0x24,0x24,0x3C,0x3C,0x00,0x00,
   //7
   0x00,0x00,0x3C,0x3C,0x04,0x04,0x08,0x08,
   0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
   //8
   0x18,0x18,0x24,0x24,0x24,0x24,0x18,0x18,
   0x24,0x24,0x24,0x24,0x18,0x18,0x00,0x00,
   //9
   0x18,0x18,0x24,0x24,0x24,0x24,0x1C,0x1C,
   0x04,0x04,0x04,0x04,0x18,0x18,0x00,0x00,
   //10
   0x26,0x26,0x69,0x69,0xA9,0xA9,0x29,0x29,
   0x29,0x29,0x29,0x29,0x26,0x26,0x00,0x00
};


Echadle un ojo al siguiente segmento de código. Es lo primero que hay nada más empezar la función main():


PLOT_SET_BACKGROUND_LOGO();
   PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, x_pongdiplomacy_logo_tile_data, x_pongdiplomacy_logo_map_data, 2000);
   waitpad(J_START);
   
   ///// COMIENZA EL BLOQUE BITMAP DONDE SE MUESTRA EL TEXTO DEL JUEGO.
   PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, _blank_text_tile_data, _blank_text_map_data, 2000);
   waitpad(J_START);
   ///// TERMINA EL BLOQUE BITMAP DONDE SE MUESTRA EL TEXTO DEL JUEGO.
   
   disable_interrupts();
   DISPLAY_OFF;
   
   RESET_COORDS();
   
   //fijar las puntuaciones a cero.
   score_p1 = 0;
   score_p2 = 0;
   score_counter_p1 = 0;
   score_counter_p2 = 0;

   //background
   set_bkg_data(0,10,backgroundcharacters);
   //background
   set_bkg_tiles(0, 0, 20, 1, bgmap);
   //background
   for(counter=1;counter<=13;counter++) set_bkg_tiles(0,counter,20,1,bgmap+20);
   set_bkg_tiles(0,14,20,1,bgmap+40);
   
   for(counter=15;counter<=17;counter++) set_bkg_tiles(0,counter,20,1,bgmap+20);
   set_bkg_tiles(0,14,20,1,bgmap+40);
   
   //cargar los sprites.
   SPRITES_8x8;
   //cargar los tiles del paddle.
   set_sprite_data(0, 1, spritetiles);
   //cargar los tiles de la bola.
   set_sprite_data(1, 1, spritetiles+16);
   //cargar los tiles de las puntuaciones.
   set_sprite_data(2, 1, spritetiles+32);
   set_sprite_data(3, 1, spritetiles+48);
   set_sprite_data(4, 1, spritetiles+64);
   set_sprite_data(5, 1, spritetiles+80);
   set_sprite_data(6, 1, spritetiles+96);
   set_sprite_data(7, 1, spritetiles+112);
   set_sprite_data(8, 1, spritetiles+128);
   set_sprite_data(9, 1, spritetiles+144);
   set_sprite_data(10, 1, spritetiles+160);
   set_sprite_data(11, 1, spritetiles+176);
   set_sprite_data(12, 1, spritetiles+192);
   
   //paddle 0. Controlada por el usuario. [China].
   set_sprite_tile(0,0);
   set_sprite_tile(1,0);
   set_sprite_tile(2,0);
   
   //paddle 1. Controlado por la CPU. [USA].
   set_sprite_tile(3,0);
   set_sprite_tile(4,0);
   set_sprite_tile(5,0);
   //bola.
   set_sprite_tile(6,1);
   
   //activar fondos y sprites.
   SHOW_BKG;
   SHOW_SPRITES;
   DISPLAY_ON;
   enable_interrupts();
   
   INIT_REGISTERS_SOUND_EFECTS();



Todo este bloque sirve como inicialización del juego. Por un lado las primeras instrucciones las utilizo para cargar los diferentes "backgrounds" que constituyen las pantallas de presentación del juego en si. Luego una vez que se han mostrado todos los "backgrounds" y el jugador a pulsado START se llama a la función RESET_COORDS()


void RESET_COORDS(){
   //pos inicial paddle0.
   y1 = 65;
   y2 = 73;
   y3 = 81;
   //pos inicial paddle1.
   y4 = 65;
   y5 = 73;
   y6 = 81;
   //pos inicial bola.
   ball_pos_x=80;
   ball_pos_y=77;
   //inicializar el vector de movimiento.
   ball_vector_x1 = 0;
   ball_vector_x2 = 0;
   ball_vector_y1 = 0;
   ball_vector_y2 = 0;
   vector_pointer = 0;
   
   // juego en pausa.
   game_status = 0;
   return;
}



Esta función se utiliza para inicializar en unas posiciones concretas las palas del jugador y la CPU así como las coordenadas X e Y de la bola. A continuación está definido el bloque:


//cargar los sprites.
   SPRITES_8x8;
   //cargar los tiles del paddle.
   set_sprite_data(0, 1, spritetiles);
   //cargar los tiles de la bola.
   set_sprite_data(1, 1, spritetiles+16);
   //cargar los tiles de las puntuaciones.
   set_sprite_data(2, 1, spritetiles+32);
   set_sprite_data(3, 1, spritetiles+48);
   set_sprite_data(4, 1, spritetiles+64);
   set_sprite_data(5, 1, spritetiles+80);
   set_sprite_data(6, 1, spritetiles+96);
   set_sprite_data(7, 1, spritetiles+112);
   set_sprite_data(8, 1, spritetiles+128);
   set_sprite_data(9, 1, spritetiles+144);
   set_sprite_data(10, 1, spritetiles+160);
   set_sprite_data(11, 1, spritetiles+176);
   set_sprite_data(12, 1, spritetiles+192);



En este bloque se define el tamaño de los sprites que se van a utilizar (8x8) y por medio de la función set_sprite_data() se cargan los datos de los sprites a partir de la variable "spritetiles" que había obtenido usando la herramienta GBTD. A continuación se definen los tiles de cada sprite de las palas del jugador y la CPU así como de la bola.


//paddle 0. Controlada por el usuario. [China].
   set_sprite_tile(0,0);
   set_sprite_tile(1,0);
   set_sprite_tile(2,0);
   
   //paddle 1. Controlado por la CPU. [USA].
   set_sprite_tile(3,0);
   set_sprite_tile(4,0);
   set_sprite_tile(5,0);
   //bola.
   set_sprite_tile(6,1);



Finalmente se muestran el fondo y los sprites y se llama la función INIT_REGISTERS_SOUND_EFECTS(); para inicializar los registros de sonido.


//activar fondos y sprites.
   SHOW_BKG;
   SHOW_SPRITES;
   DISPLAY_ON;
   enable_interrupts();
   
   INIT_REGISTERS_SOUND_EFECTS();



A continuación aparece le búcle while() que se va a ejecutar continuamente con la lógica del juego. Al principio está este bloque:


if((score_p1 == 10 || score_p2 == 10) || controller == 1){
         if((score_counter_p2 == 10 || score_counter_p1 == 10)){
            HIDE_SPRITES;
            PLOT_BACKGROUND_IMAGE(0, 0, 20, 18, x_congrats_win_tile_data, x_congrats_win_map_data, 1000);
            waitpad(J_START);
            CLEAR_SCREEN_BACKGROUND();
            RESET_COORDS();
            controller = 0;
         }
         
         if(controller == 1){
            score_p1 = 0;
            score_p2 = 0;
            set_sprite_tile(7,2+score_counter_p1);
            set_sprite_tile(8,2+score_counter_p2);
         }else{
            score_p1 = 0;
            score_p2 = 0;
            score_counter_p1 = 0;
            score_counter_p2 = 0;
            ball_vector_x1 = 1;
            ball_vector_x2 = 1;
            ball_vector_y1 = 1;
            ball_vector_y2 = 1;
            
            set_bkg_data(0,10,backgroundcharacters);
            set_bkg_tiles(0, 0, 20, 1, bgmap);
            for(counter=1;counter<=13;counter++) set_bkg_tiles(0,counter,20,1,bgmap+20);
            set_bkg_tiles(0,14,20,1,bgmap+40);
            for(counter=15;counter<=17;counter++) set_bkg_tiles(0,counter,20,1,bgmap+20);
            set_bkg_tiles(0,14,20,1,bgmap+40);
            
            SPRITES_8x8;
            set_sprite_data(0, 1, spritetiles);
            set_sprite_data(1, 1, spritetiles+16);
            set_sprite_data(2, 1, spritetiles+32);
            set_sprite_data(3, 1, spritetiles+48);
            set_sprite_data(4, 1, spritetiles+64);
            set_sprite_data(5, 1, spritetiles+80);
            set_sprite_data(6, 1, spritetiles+96);
            set_sprite_data(7, 1, spritetiles+112);
            set_sprite_data(8, 1, spritetiles+128);
            set_sprite_data(9, 1, spritetiles+144);
            set_sprite_data(10, 1, spritetiles+160);
            set_sprite_data(11, 1, spritetiles+176);
            set_sprite_data(12, 1, spritetiles+192);
            SHOW_SPRITES;
            game_status = 1;
            controller = 0;
         }
      }



Aquí se comprueba como va el estado del juego. En caso de que alguno de los jugadores haya logrado 10 puntos, se para la ejecución del juego y se muestra un background especial invitando al jugador a volver a echar una nueva partida. Después de pulsar el botón START el juego se reinicia con la puntuación a cero para ambos jugadores y la posición de las palas y la bola se reinicia para que todos los elementos queden centrados en pantalla.

El código que sigue son todas las comprobaciones necesarias para saber en que posición están las palas de los jugadores, en que posición está la bola, y el chequeo de las colisiones. No me voy a centrar en todos estos elementos porque sino es posible que os lie más de lo necesario y entiendo que es necesario revisar el código fuente completo tranquilamente, pero si me voy a detener específicamente en esta función:


UBYTE CHECK_COLLISION_BALL_PADDLE(UBYTE paddleNr){
   //paddle0
   if(paddleNr==0){
      if(ball_pos_x>=15)
         colx = ball_pos_x - 15;   
      else
         colx = 15 - ball_pos_x;
      if (colx<8){
         //from x point of view there might be a collision
         if (ball_pos_y >= y2) coly = ball_pos_y - y2;
         else coly = y2 - ball_pos_y;
         //check the y point of view
         return coly;
      }
      return 16;
   }
   //paddle1
   else{
      
      if(ball_pos_x>=153)
         colx = ball_pos_x - 153;
      else
         colx = 153 - ball_pos_x;
      if (colx<8){
         //from x point of view there might be a collision
         if (ball_pos_y >= y5) coly = ball_pos_y - y5;
         else coly = y5 - ball_pos_y;
         //check the y point of view
         return coly;
      }
      return 16;
   }
}UBYTE CHECK_COLLISION_BALL_PADDLE(UBYTE paddleNr){
   //paddle0
   if(paddleNr==0){
      if(ball_pos_x>=15)
         colx = ball_pos_x - 15;   
      else
         colx = 15 - ball_pos_x;
      if (colx<8){
         
         if (ball_pos_y >= y2) coly = ball_pos_y - y2;
         else coly = y2 - ball_pos_y;
         return coly;
      }
      return 16;
   }
   //paddle1
   else{
      
      if(ball_pos_x>=153)
         colx = ball_pos_x - 153;
      else
         colx = 153 - ball_pos_x;
      if (colx<8){
         if (ball_pos_y >= y5) coly = ball_pos_y - y5;
         else coly = y5 - ball_pos_y;
         return coly;
      }
      return 16;
   }
}



La función UBYTE CHECK_COLLISION_BALL_PADDLE(UBYTE paddleNr) es la que utilizo para comprobar la colisión entre la bola y las palas (la función sirve para ambas palas, tanto la controlada por el jugador como la controlada por la CPU). Si el parámetro de entrada es cero entonces la función se encarga de comprobar el estado de la colisión para la pala controlada por el usuario y si es 1 se encarga de controlar el estado de la colisión para la pala controlada por la CPU. Las variables ball_pos_x y ball_pos_y se utilizan para ir guardando la posición X e Y de la bola en pantalla. Las variables colx y coly son auxiliares y se utilizan para calcular el valor de retorno que es función de la posición actual de la bola en el eje X y en el eje Y.Básicamente lo que hace esta función es comprobar la posición de la bola con respecto a la posición de las dos palas en el juego.

Finalmente el siguiente segmento de código es el utilizado para comprobar la colisión de la bola con las 4 paredes del juego. Si ball_pos_x es menor que 9 entonces la CPU ha anotado un tanto. Igualmente si ball_pos_x
es mayor de 163 entonces es la pala del jugador la que ha anotado un tanto. Estos valores son debidos al ancho de las palas. Lo que se está comprobando es que la bola haya superado el ancho de la pala para saber si va a golpear el muro que hay detrás. En ambos casos al detectar la colisión con el muro no sólo se anota un tanto para el jugador o la CPU sino que además suena un efecto de sonido característico y se resetean las coordenadas de las palas y la bola para empezar una nueva tanda.


// detección de colisiones.
      // pared izquierda.
      if(ball_pos_x<9){
         RESET_COORDS();
         score_p2++;
         PLAY_UL_WALL_SOUND_EFFECT();
         score_counter_p2++;
         //el jugador 2 anota un tanto.
         controller = 1;
      }
      // pared derecha.
      if(ball_pos_x>163){
         RESET_COORDS();
         score_p1++;
         score_counter_p1++;
         PLAY_UL_WALL_SOUND_EFFECT();
         //el jugador 1 anota un tanto.
         controller = 1;
      }


Y poco más, como ya he comentado el código fuente está completo al comienzo de este post para que lo vayáis comprobando y hagáis vuestras pruebas, modificaciones, etc, etc... En principio me hubiese gustado modularizar un poco más el programa y sacar los tiles a ficheros .C por separado para cargarlos usando "bank switching" (como hice en el turorial #6 para cargar diferentes pistas de música usando la librería GBT Player) pero como al final el programa no era muy complejo, lo metí todo junto en el mismo fichero .C (a excepción de los backgrounds que están en ficheros .h separados). En todo caso dentro de poco tendré listos los repositorios para cada uno de estos proyectos en GitHub por lo que podréis descargaros todos los ejemplos desde ahí. Pongo por aquí también las lineas de compilación que he usado para este juego:


lcc -Wa-l -Wl-m -Wl-j -DUSE_SFR_FOR_REG -c -o PingPongDiplomacy.o PingPongDiplomacy.c
lcc -Wa-l -Wl-m -Wl-j -DUSE_SFR_FOR_REG -Wl-yt1 -Wl-yo4 -Wl-ya0 -o PingPongDiplomacy-20141214.gb PingPongDiplomacy.o

del *.o *.lst *.map *.sym
pause



Espero poder volver dentro de poco con la siguiente entrada, a ver si para entonces tengo el Blog ya operativo.

Saludos.
Genial @DarkRyoga!! ;)

Me suscribo para retomar la programacion de GBC XD
bertobp escribió:Genial @DarkRyoga!! ;)

Me suscribo para retomar la programacion de GBC XD


En el primer post de este hilo está toda la info de estos turoriales con accesos directos a cada post donde se trata cada una de las partes del tutorial.
Brutal como siempre. Ya tengo ganas de que llegue la hora de controlar los sprites en un juego de plataformas.
Buena actualización, con las fiestas y todo el rollo ni me había percatado de que el clón del "pong" ya lo habías terminado. A ver si saco un rato y voy probando.

Estuve probando el tutorial de como usar la libreria GBT player para meter sonido de 4 canales basado en ficheros MOD y lo conseguí hacer funcionar. Creo que no lo hubiese logrado de no ser por tú tutorial. Además el programa "deflemask" viene de perlas para toquetear los ficheros .gbs ya existentes y generar .mod.

Espero que no tardes mucho en subir la siguiente lección.
226 respuestas
1, 2, 3, 4, 5