Bienvenido(a), Visitante. Por favor, ingresa o regístrate.

Ingresar con nombre de usuario, contraseña y duración de la sesión
 

Autor Hilo: TUTORIAL 1985ALTERNATIVO SOBRE PROGRAMACION EN C DE LA GAMEBOY (Leído 53519 veces)

pocket_lucho

  • T-500
  • Mensajes: 1 117
A ver wi lo del Banking me ha quedado claro. Básicamente tenemos varios bloques de memoria en los cuales almacenamos datos como sprites o sonidos y los vamos intercambiando según  los vamos precisando no?

Es como si tuviera un mueble con varios cajones. En un cajón tenemos calcetines, en otro calzones, en otro bragas, etc... Y cuando queremos acceder a ese contenido lo hacemos mediante la intrucción precisa?

En resumidas cuentas... si xDD Me ha encantado el ejemplo de los cajones!

Completito, completito. Lo de los metaSprites me ha encantado, que te lo había pedido ya  ^-^

Una duda tras una lectura rápida (seguro que luego vienen más).
¿Porque declaras 4 bancos si realmente solo usas 2?
¿Los bancos se numeran a partir del 1 o el 0? Porque estás usando el 2 para los tiles, imagino que en el 1 está el main y en el 0 y el 3 (o en el 3 y el 4) ¿no hay nada?

Esta noche intentaré darle caña, aunque sea con una mano :P

Cuando compilo, si creo bancos impares me da error, asi que supongo que se deben crear de esta forma... Se numero a partir del 1, asi que hay que empezar a usar desde el 2! El ejemplo es que lo usaba para otra cosa donde si que tenia cosas en los bancos 3 y 4... de ahi el tema de los bancos, cosas del copy/paste :P

El tema de los metasprites ya lo ves, por dentro siguen siendo de 8x8/8x16, a eso me refería...

Me alegro que os haya molado, no ha sido poco curro de investigacion!
última modificación: 19 Septiembre 2014, 17:30:42 por pocket_lucho

pocket_lucho

  • T-500
  • Mensajes: 1 117
TUTORIAL 4: FONDOS

En el tutorial anterior comentamos lo que era un sprite, en este vamos a tratar el fondo o background. Se trata de una capa formada por 32x32 tiles capaz de moverse en las coordenadas X/Y, siendo la parte visible en pantalla de 20x18 tiles (160x144 pixels). En las opciones de debug de los emuladores incluidos se puede este fondo, el cuadrado rojo representa el área visible de la pantalla y como podéis ver, el mapa es algo más grande que ésta:



Anteriormente también comentamos que en la VRAM, la memoria de video, podíamos distinguir 2 zonas, una para los tiles de los sprites (hasta 256) y otra para el fondo (hasta 256) pudiendo haber un total máximo de 384, ¿qué quiere decir esto? Pues desgraciadamente, la segunda mitad del área reservada para sprites y fondos es común, quedando a nuestra elección como usarla. Es decir, desde la posición 0 a 127 de la zona de sprites, solo podremos meter tiles para sprites, en la posición 0 a 127 de la zona de fondos, solo podremos meter tiles para fondos, pero en la zona 128-255 de ambos... ¡hay que tener muy controlado que ponemos! Lo veremos mucho más claro en el ejemplo que vamos a hacer, una representación visual de la VRAM sería esta:



Vamos a empezar con algo, simplemente vamos a dibujar una imagen del tamaño de la pantalla, para eso usaremos este homenaje al aniversario de la consola hecha por el amigo Felipe Monge:



Primero habrá que convertirla al formato de la consola, usaremos en PCX2GB que puse en la carpeta TOOLS. La imagen recordad que no puede esta formada por más de 256 tiles, ser un PCX y usar la paleta siguiente, que no es la misma que usamos en el tutorial anterior para el GBTD:



Hay multitud de conversores, siempre podéis ir probando y elegir el que más os guste, pero este para empezar está bien. Este por ejemplo es en HTML5 y funciona más o menos bien:

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

Podemos convertir la imagen de dos formas diferentes, tal cual, usando la linea de comandos:

Citar
pcx2gb n d fichero.pcx tiles.c

La imagen de esta forma ocupa 380 tiles. Solo podríamos dibujarla si sobrepasamos los tiles delimitados al fondo.

O de forma optimizada, reduciendo los tiles repetidos y creando un mapa de estos con la linea:

Citar
pcx2gb o d fichero.pcx tiles.c map.c

De esta forma, ahora la imagen ocupa 131 tiles, bastantes menos como podéis observar.

Para dibujar la imagen en el primer caso, podríamos usar una de las funciones incluidas en "drawing.h", "draw_image"(data). Usando esta función, usaremos la VRAM completa, es decir, los 384 tiles, ya que las funciones de "drawing.h" usan cada tile de la VRAM simulando un framebuffer, o mejor dicho, cada tile de la memoria es una posición en la pantalla. Esto es un gran problema a la hora de usarlo en un juego, ya que por ejemplo, no nos quedan tiles libres para los sprites.

El código completo sería este:

Código: [Seleccionar]
#include <gb/gb.h>
#include <gb/drawing.h>

#include "2.c"

///////////////////////////////////////////////
//  punto de entrada
///////////////////////////////////////////////
void main(){

    draw_image( tiledata );

    // bucle infinito
    while(1) {
// sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
wait_vbl_done();
    }
}

Siendo "2.c" el nombre del fichero que nos ha generado el conversor de la imagen. Recordad poner const delante del vector que nos genera.

Para el segundo método, usando un mapa de tiles, también es muy simple, simplemente cargamos los tiles en VRAM con set_bkg_data( posición en VRAM, numero de tiles, tiles), luego el mapa con set_bkg_tiles( x, y, ancho, alto, mapa) y por último los mostramos con SHOW_BKG.

El código completo sería este:

Código: [Seleccionar]
#include <gb/gb.h>

#include "tiles.c"
#include "map.c"

///////////////////////////////////////////////
//  punto de entrada
///////////////////////////////////////////////
void main(){

    // carga los tiles - posicion, numero, tiles
    set_bkg_data( 0, 131, tiledata);

    // carga el mapa de tiles - x, y, ancho, alto y mapa
    set_bkg_tiles( 0, 0, 20, 18, tilemap);

    // muestra el fondo
    SHOW_BKG;

    // bucle infinito
    while(1) {
// sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
wait_vbl_done();
    }
}

Observar la enorme diferencia de usar mapas de tiles frente a usar la imagen tal cual convertida. Comprobad también como nuestra imagen al tener 131 tiles, se pasa 3 de los 128, por lo que pasan automáticamente a la zona común.






SCROLL DEL FONDO
Ahora que ya tenemos el fondo dibujado, ahora vamos a moverlo. Para ello podemos usar la función "move_bkg". Prácticamente el código es el mismo, creamos unas variables para el control del pad, otras para las posición del scroll, las actualizamos con el pad y se las pasamos a "move_bkg". Las librerías también incluyen la función "scroll_bkg" que realizan el scroll de forma relativa a su posición actual.

El código quedaría así:

Código: [Seleccionar]
#include <gb/gb.h>

#include "tiles.c"
#include "map.c"

///////////////////////////////////////////////
//  punto de entrada
///////////////////////////////////////////////
void main(){

    UBYTE i, bposx, bposy; // control del pad y scroll del fondo

    set_bkg_data( 0, 131, tiledata); //111
    set_bkg_tiles( 0, 0, 20, 18, tilemap);

    SHOW_BKG;

    // bucle infinito
    bposx = bposy = 0;
    while(1) {

// lee el pad
        i = joypad();

        // modificamos la posicion
        if(i & J_UP)
            bposy -= 1;
        if(i & J_DOWN)
            bposy += 1;
        if(i & J_LEFT)
            bposx -= 1;
        if(i & J_RIGHT)
            bposx += 1;

// sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
wait_vbl_done();

        // movemos el scroll
        move_bkg( bposx, bposy);
    }
}


Como se ve en el debug del emulador, hemos movido el área visible de la pantalla por el mapa y cuando llegamos al borde superior/inferior o derecho/ziquierdo, aparece por el otro extremo.




PLANO WINDOW
Antes comentaba que solo había un plano de fondo o "BACKGROUND" y esto no es del todo cierto. También existe otro plano que se superpone al que conocemos llamado ventana (WINDOW). El problema que tiene es que es opaco completamente y prácticamente se limita a su uso como marcador. Su funcionamiento es prácticamente igual al fondo normal, para ver su uso, vamos a crear unas funciones que nos permitan dibujar texto en ambas capas.

Vamos a utilizar esta bonita fuente y la convertiremos con el conversor online comentado más arriba:



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

Como únicamente queremos los tiles sin optimizar los repetidos, desmarcaremos las opciones "Generate Tile Map" y "Tile Quantization" y elegiremos como formato de salida "C Format (GBDK)". Guardamos su contenido en un fichero llamado "font.c" y ya tenemos listos nuestros tiles de la fuente en formato ASCII, si queréis añadir minúsculas o caracteres especiales, buscad la tabla ASCII y añadirlos, yo lo he dejado lo más reducido posible (64 tiles).

Creamos nuestro "main.c" y escribimos:

Código: [Seleccionar]
#include <gb/gb.h>
#include "font.c"

static const unsigned char string00[] = "CARACOLA";

// posicion de los tiles del texto
#define FONT_OFFSET 128

Añadimos los tiles de la fuente, una cadena de caracteres en ROM y la posición de la VRAM donde guardaremos los tiles de la fuente.

Código: [Seleccionar]
/////////////////////////////////////////////////////
//
//  void drawString(char str[], UBYTE x, UBYTE y){
//  dibuja la cadena str en la posicion x, y
//  en BKG si flag es 0 y en WIN si es 1
//  si una palabra no entra en la linea, la dibuja
//  en la siguiente
//
/////////////////////////////////////////////////////
void drawString( char *str, UBYTE x, UBYTE y, UBYTE flag ){

    UBYTE posx, strx, nextlen;
    UBYTE posy, e;
    UBYTE c;

    strx = 0;
    posx = x;
    posy = y;

    // mientras la cadena no termine
    while(str[strx] != 0){

        // comprueba la longitud de la palabra actual de la cadena
        nextlen = 0;
        for( e = strx; (str[e] != ' ') && (str[e] != 0); e++)
            nextlen++;

        // si no cabe en pantalla bajamos de linea
        if ( posx + nextlen > 20){
            posx = 0;
            posy++;
        }

        // dibuja la palabra en la posicion que corresponda
        for(;(str[strx] != ' ') && (str[strx] != 0); strx++){
            c = str[strx] + FONT_OFFSET - 32;
            if( !flag )
                set_bkg_tiles( posx, posy, 1, 1, (unsigned char *) &c);
            else
                set_win_tiles( posx, posy, 1, 1, (unsigned char *) &c);
            posx++;
        }

        // dibuja un espacio en blanco entre las palabras de la cadena
        if ( posx != 0 ){
            c = ' ' + FONT_OFFSET - 32;
            if( !flag )
                set_bkg_tiles( posx, posy, 1, 1, (unsigned char *) &c);
            else
                set_win_tiles( posx, posy, 1, 1, (unsigned char *) &c);
            posx++;
        }

        // si no es el final de la cadena incrementamos la posicion
        if(str[strx] != 0 )
          strx++;
    }
}

Con esta función, dibujaremos la cadena de texto que se le pase, en la posición X/Y indicada en el plano BACKGROUND si flag es "0" con "set_bkg_tiles" o en WINDOW si es "1" con "set_win_tiles". Como veis, los tiles son comunes en ambos planos.

Código: [Seleccionar]
///////////////////////////////////////////////
//  punto de entrada
///////////////////////////////////////////////
void main(){

    UBYTE i, bposx, bposy;

    // cargamos los tiles de la fuente
    set_bkg_data( FONT_OFFSET, 64,(unsigned char *)font);

    // dibujo la cadena en el fondo
    drawString( "HOLA", 1, 10, 0);

    // tb puedo dibujar cadenas desde rom
    drawString(string00, 6, 10, 0);

    // esta como no cabe entera baja a la siguiente linea
    drawString(string00, 13, 11, 0);

    // dibujo la cadena en el fondo
    drawString( "HOLA WINDOWS", 0, 0, 1);

    // muestra el BKG
    SHOW_BKG;
    SHOW_WIN; // WIN se suporpone a BKG y usa sus mismos tiles, para dejar ver a BKG hay que moverla primero

    // bucle infinito
    bposx = 8; bposy = 128;
    while(1){

        // lee el pad
        i = joypad();

        // modificamos la posicion
        if(i & J_UP)
            bposy -= 1;
        if(i & J_DOWN)
            bposy += 1;
        if(i & J_LEFT)
            bposx -= 1;
        if(i & J_RIGHT)
            bposx += 1;

        // sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
wait_vbl_done();

// movemos el scroll de la ventana
        move_win( bposx, bposy);
    }
}

Ya en el "main()", dibujamos varias cadenas en el fondo y en la ventana desde RAM y ROM. En el bucle infinito controlamos el pad y movemos la ventana para ver como se suporpone al fondo.

El código completo quedaría así:

Código: [Seleccionar]
#include <gb/gb.h>
#include "font.c"

static const unsigned char string00[] = "CARACOLA";

// posicion de los tiles del texto
#define FONT_OFFSET 128

/////////////////////////////////////////////////////
//
//  void drawString(char str[], UBYTE x, UBYTE y){
//  dibuja la cadena str en la posicion x, y
//  en BKG si flag es 0 y en WIN si es 1
//  si una palabra no entra en la linea, la dibuja
//  en la siguiente
//
/////////////////////////////////////////////////////
void drawString( char *str, UBYTE x, UBYTE y, UBYTE flag ){

    UBYTE posx, strx, nextlen;
    UBYTE posy, e;
    UBYTE c;

    strx = 0;
    posx = x;
    posy = y;

    // mientras la cadena no termine
    while(str[strx] != 0){

        // comprueba la longitud de la palabra actual de la cadena
        nextlen = 0;
        for( e = strx; (str[e] != ' ') && (str[e] != 0); e++)
            nextlen++;

        // si no cabe en pantalla bajamos de linea
        if ( posx + nextlen > 20){
            posx = 0;
            posy++;
        }

        // dibuja la palabra en la posicion que corresponda
        for(;(str[strx] != ' ') && (str[strx] != 0); strx++){
            c = str[strx] + FONT_OFFSET - 32;
            if( !flag )
                set_bkg_tiles( posx, posy, 1, 1, (unsigned char *) &c);
            else
                set_win_tiles( posx, posy, 1, 1, (unsigned char *) &c);
            posx++;
        }

        // dibuja un espacio en blanco entre las palabras de la cadena
        if ( posx != 0 ){
            c = ' ' + FONT_OFFSET - 32;
            if( !flag )
                set_bkg_tiles( posx, posy, 1, 1, (unsigned char *) &c);
            else
                set_win_tiles( posx, posy, 1, 1, (unsigned char *) &c);
            posx++;
        }

        // si no es el final de la cadena incrementamos la posicion
        if(str[strx] != 0 )
          strx++;
    }
}

///////////////////////////////////////////////
//  punto de entrada
///////////////////////////////////////////////
void main(){

    UBYTE i, bposx, bposy;

    // cargamos los tiles de la fuente
    set_bkg_data( FONT_OFFSET, 64,(unsigned char *)font);

    // dibujo la cadena en el fondo
    drawString( "HOLA", 1, 10, 0);

    // tb puedo dibujar cadenas desde rom
    drawString(string00, 6, 10, 0);

    // esta como no cabe entera baja a la siguiente linea
    drawString(string00, 13, 11, 0);

    // dibujo la cadena en el fondo
    drawString( "HOLA WINDOWS", 0, 0, 1);

    // muestra el BKG
    SHOW_BKG;
    SHOW_WIN; // WIN se suporpone a BKG y usa sus mismos tiles, para dejar ver a BKG hay que moverla primero

    // bucle infinito
    bposx = 8; bposy = 128;
    while(1){

        // lee el pad
        i = joypad();

        // modificamos la posicion
        if(i & J_UP)
            bposy -= 1;
        if(i & J_DOWN)
            bposy += 1;
        if(i & J_LEFT)
            bposx -= 1;
        if(i & J_RIGHT)
            bposx += 1;

        // sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
wait_vbl_done();

// movemos el scroll de la ventana
        move_win( bposx, bposy);
    }
}




DEBERES
Con esto doy por terminada la primera parte del tutorial de los fondos, para el próximo trataremos el color y como ejemplo os explicaré las pautas para romper la limitación de tamaño del plano de 32x32 tiles y hacer un scroll más grande a base de mapas de tiles. Como deberes os pongo que aprendais a usar los conversores listados y añadais sprites al fondo y ventana.

Victor

  • Humano
  • Mensajes: 14
Genial como siempre.
Como conversor le tengo echado el ojo al png2gb que seamos sinceros, los png están mejor soportados que los pcx en los editores modernos:
https://github.com/LuckyLights/png2gb

A ver si acabo con tu tuto y lo pruebo también.

pocket_lucho

  • T-500
  • Mensajes: 1 117
Anda, ese no lo conocía, lo tienes por ahí compilado para windows? Parece que solo está el fuente ahí. Yo la verdad es que uso sobretodo el online en html5 que puse, que excepto por el conteo de tiles que no me aclaro, funciona muy bien.

Ah, la semana que viene no está claro que haya tuto, estoy muy liado con el trabajo y debo investigar aún cosas...

Victor

  • Humano
  • Mensajes: 14
Para linux sin problemas, pero he tratado de compilarlo en Windows y me lanza fallos por todos lados. Tendré que echarle un ojo al código despacio.

Tú a tu ritmo, que lo primero es lo primero.

Además, con esto ya hay bastante para enredar ;D

Balthier

  • T-70
  • Mensajes: 100
Como si tienes que parar meses Lucho, encimq de que lo haces de manera altruista, nadie te va a exigir nada.  ;)
"...7 secrets, 7 sins, this is where the end begins..."

Victor

  • Humano
  • Mensajes: 14
No veas el pcx2gb.
Falla en Win7 32bits. Me dice que el sistema no es compatible con pantalla completa. Con PowerShell, omitiendo 3 veces consigues hacerlo funcionar pero tela...
última modificación: 28 Septiembre 2014, 17:00:24 por Victor

pocket_lucho

  • T-500
  • Mensajes: 1 117

Victor

  • Humano
  • Mensajes: 14
Vale, tengo un "bug" en el editor web.

El pcx2gb deja el tile 00 completamente en blanco, lo que hace que en el ejemplo de mover la imagen, el resto este en blanco.
El conversor web no. Mete el primer tile de la imagen en el 00, lo que hace que al mover la imagen, el resto se vea con ese tile.

Iba a adjuntar ejemplo con el código y compilados para que se vea más claro, pero no me deja el foro. Luego meto un link de dropbox.

Lo cachondo es que en ambos casos, la imagen ocupa 131 tiles, a pesar de que no hay ningún tile en blanco en el 2º caso, imagino que el pcx2gb busca un tile en blanco para meter en el 00 y a partir de ahí selecciona los demás; y el conversor web es lineal. Empieza por el primer tile 8x8 y sigue hasta el final.

No tiene mayor gravedad. Pero hay que tenerlo en cuenta a la hora de diseñar los mapas, si esperabas que salieran en blanco.


pocket_lucho

  • T-500
  • Mensajes: 1 117
TUTORIAL 4: FONDOS (II)

Ahora vamos a tratar el color en los mapas de tiles. Prácticamente se hace igual que en modo clásico pero añadiendo paletas y un mapa que indica que paleta usa cada tile.

Un poquito de teoría, la Game Boy Color usa una paleta de 15-bits RGB (32,768 colores). En pantalla, sin usar trucos, podremos llegar hasta 56 colores, 8 paletas de 4 colores para los fondos, más otras 8 de 3 colores para los sprites (el primer color de cada paleta de 4 es el transparente). Por truco, me refiero a cambiar las paletas en cada scanline (o incluso a mitad de scanline), pudiendo llegar a 10000 colores a la vez, pero para pantallas estáticas únicamente.

Vamos a utilizar esta imagen como ejemplo, pasarla a mapa de tiles optimizado con vuestro conversor preferido y los enlazamos en nuestro main.c:



Código: [Seleccionar]
#include <gb/gb.h>

#include "pcx2gb/tiles.c"
#include "pcx2gb/map.c"

Como con los sprites, definimos nuestras paletas:

Código: [Seleccionar]
// paletas
const UWORD bkg_palette[] = {
RGB_RED, RGB_DARKRED, RGB_GREEN, RGB_DARKGREEN,
RGB_BLUE, RGB_DARKBLUE, RGB_YELLOW, RGB_DARKYELLOW,
RGB_CYAN, RGB_AQUA, RGB_PINK, RGB_PURPLE,
RGB_LIGHTGRAY, RGB_DARKGRAY, RGB_WHITE, RGB_LIGHTFLESH,
RGB_BLACK, RGB_BROWN, RGB_ORANGE, RGB_TEAL
};

Estos colores son los que trae definidos las libreras, lo que no impide que uséis el que queráis de la la paleta de los 32768 usando este formato "0x7fff, 0x531c, 0x7da0, 0x0c63..."

Ahora vamos a definir el mapa de color, simplemente especificamos que paleta va a usar cada tile de nuestro mapa de esta forma:

Código: [Seleccionar]
// mapa de color para el fondo
unsigned char cgb_map[] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
  4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
  5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5
};

En principio hay que hacerlo a mano ya que no conozco ninguna utilidad que permita crear estos mapas, si alguien la conoce por favor que lo comente y lo edito.

Ya en nuestro "main()", cargamos los tiles y el mapa como vimos en el tutorial anterior:


Código: [Seleccionar]
void main(){

    set_bkg_data( 0, 99, tiledata);

    // mapa de tiles, x, y, ancho, alto y mapa
    set_bkg_tiles( 0, 0, 20, 18, tilemap);

Y al igual que en el tutorial de sprites a color, comprobamos que estamos en una GB COLOR, cambiamos al banco 1 de la VRAM (exclusivo de este modelo), allí cargamos el mapa de color con "set_bkg_tiles" y cargamos las paletas para el fondo con "set_bkg_palette". Recordad, podemos cargar hasta 8 paletas para el fondo y otras 8 para los sprites:

Código: [Seleccionar]
    if( _cpu == CGB_TYPE ){

        VBK_REG = 1;

// posicion x, posicion, y, tamaño x, tamaño y, mapa
        set_bkg_tiles(0, 0, 20, 18, cgb_map);
        VBK_REG = 0;

        // carga las paletas del fondo, paleta inicial, numero y datos
        set_bkg_palette( 0, 5, &bkg_palette[0] );
    }

Mostramos el fondo y entramos en un bucle infinito para terminar:

Código: [Seleccionar]
    SHOW_BKG;

// bucle infinito
while(1) {
// sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
wait_vbl_done();
}
}

El código completo quedaría así:

Código: [Seleccionar]
#include <gb/gb.h>

#include "pcx2gb/tiles.c"
#include "pcx2gb/map.c"

// paletas
const UWORD bkg_palette[] = {
RGB_RED, RGB_DARKRED, RGB_GREEN, RGB_DARKGREEN,
RGB_BLUE, RGB_DARKBLUE, RGB_YELLOW, RGB_DARKYELLOW,
RGB_CYAN, RGB_AQUA, RGB_PINK, RGB_PURPLE,
RGB_LIGHTGRAY, RGB_DARKGRAY, RGB_WHITE, RGB_LIGHTFLESH,
RGB_BLACK, RGB_BROWN, RGB_ORANGE, RGB_TEAL
};

// mapa de color para el fondo
unsigned char cgb_map[] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
  4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
  5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5
};

///////////////////////////////////////////////
//  punto de entrada
///////////////////////////////////////////////
void main(){

    set_bkg_data( 0, 99, tiledata);

    // mapa de tiles, x, y, ancho, alto y mapa
    set_bkg_tiles( 0, 0, 20, 18, tilemap);

    if( _cpu == CGB_TYPE ){

        VBK_REG = 1;
        set_bkg_tiles(0, 0, 20, 18, cgb_map);
        VBK_REG = 0;

        // carga las paletas del fondo, paleta inicial, numero y datos
        set_bkg_palette( 0, 5, &bkg_palette[0] );
    }

    SHOW_BKG;

// bucle infinito
while(1) {
// sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
wait_vbl_done();
}
}

Ya podemos crear la rom, no olvidéis añadir soporte de color al compilador con:

Código: [Seleccionar]
lcc -Wa-l -c -o  main.o main.c
lcc -Wl-yp0x143=0x80 -o rom.gb main.o

Y obtenemos este resultado, observar las diferentes paletas en el menú de debug:



Ah, la cpu de la GB COLOR puede trabajar en un modo más rápido que la normal, lo podemos activar con "cpu_fast(void)"


SCROLLER
Por último y como ejemplo, vais a hacer una rutina de scroll grande. Como ya comenté, el mapa del fondo solo puede ser de 32x32 tiles (256x256 pixels), si queremos hacer uno más grande, lo normal en un juego, nosotros con nuestras manitas deberemos programarlo. Y lo mejor, lo vais a hacer vosotros solitos con las directrices que os voy a dar.

Primero con el editor GBTD, dibujaremos los tiles que formarán el mapa, usaremos los mismos del tutorial de sprites.



Después, con el editor GBMB incluido en la carpeta TOOLS, crearemos el mapa, elegimos en propiedades un tamaño 64x20 tiles y el tileset:



Luego lo dibujamos tile a tile con sus herramientas de lápiz y cubo de pintura. Cuando acabemos lo exportamos así:





Convertimos los tiles y el mapa. Ya en el "main.c" los enlazamos:

extern unsigned char tile_data[];
extern unsigned char map_data[];

Deberemos saber nuestro tamaño del mapa, con un #define por ejemplo:

Código: [Seleccionar]
#define MapSizeX 64

Declaremos dos variables para controlar el scroll en el eje X:

Código: [Seleccionar]
UBYTE ScrollX = 0; // Contador de Scroll en X, hasta 255 tiles (2040 pixels), si quieres más grande... pues UINT16
BYTE SCXCnt = 0; // para el incremento/decremento de tile

Cargamos los tiles en VRAM:

Código: [Seleccionar]
// tiles del fondo, posicion inicial, numero y tiles
set_bkg_data( 0, 4, &tile_data);

Y ahora dibujaremos el fragmento del mapa que se vería en pantalla y dos columnas más (22x18):

Código: [Seleccionar]
// carga el mapa inicial
Cnt = 0;
for( tempa = 0; tempa != 18; tempa++ ){ // parte visible de la pantalla (144 pixels = 18 tiles)

// mapa de tiles, x, y, ancho, alto y mapa
set_bkg_tiles( 0, tempa, 22, 1, &(map_data+Cnt)); // el mapa maximo en memoria es 32 x 32
Cnt = Cnt + MapSizeX; // incrementa Cnt con el tamaño X del mapa para que se cargue la proxima fila
}

Si miramos la VRAM sería algo así:



Ahora pasaríamos a nuestro bucle infinito, controlamos el pad para incrementar el scroll y lo moveremos como vimos en el tutorial anterior, pero con scroll_bkg, es decir, por incrementos y no posición absoluta:

Código: [Seleccionar]
// modificamos el scroll y actualizamos sus contadores
if(( i & J_LEFT ) && ( ScrollX != 0)){
        scroll_bkg( -1, 0 );
        SCXCnt--;
}

if(( i & J_RIGHT ) && ( ScrollX < MapSizeX - 20 )){
        scroll_bkg( 1, 0 );
        SCXCnt++;
}

Vale, ahora simplemente nos faltaría por implementar el redibujado de la columna que toque. La idea es que cuando nos movamos un tile (controlado por SCXCnt), deberiamos dibujar la columna siguiente que tocara del mapa. Visualmente sería así:



Código: [Seleccionar]
// al avanzar un tile comprueba si hay que actualizar el mapa
if( SCXCnt == 8 ){

        ScrollX++;      // incrementamos el tile
        SCXCnt = 0; // reseteamos el contador del scroll

        // comprobamos que tile habria que poner 2 tiles a su derecha
        Cnt = ScrollX + 21;
        tempb = Cnt % 32;

        // lo repetimos el alto del mapa (32) o la parte que vamos a usar de este (18)
        for( tempa = 0; tempa != 18; tempa++ ){

                // cargamos los tiles: x, y, ancho, alto y mapa
                set_bkg_tiles( tempb , tempa, 1, 1, &(map_data+Cnt)); // cuando se llega al extremo del mapa (32x32) se empieza a contar por el 0

                // incrementamos el contador para impedir cargar los tiles incorrectos
                Cnt = Cnt + MapSizeX;
        }
}

Ahora nos queda el supuesto de retroceder un tile "if( SCXCnt == -8 )", que sería exáctamente igual menos los valores que tomarían Cnt y ScrollX, pensad cuales son (en vez de a la derecha, pues a la izquierda).



Y listo, no hay más, con esto podreís hacer el scroll tan grande como os permita ScrollX. Esta rutina modificándola debidamente, se puede adaptar a cualquier consola que trabaje en tiles como Megadrive, Nes, Master System, Super Nes... la idea es la misma adaptándose al tamaño del mapa en VRAM y resolución de la pantalla.

DEBERES
Pues os propongo añadir un marcador fijo en el plano WINDOW, un metasprite caminando por la pantalla, color y desplazamiento en el eje Y. Con esto ya podeís hacer vuestro Zelda jejeje

realbrucest

  • T-70
  • Mensajes: 204
  • WTC7
    • El emigrante Bruce
Ya sabes que por lo pronto voy a tardarme en dar el salto a la otra acera con lo agustico que se está programando para la megadrive.
Pero como opción al "espionaje industrial" esa explicación de cómo programar la rutina de scroll para mapeados grandes es especialmente cojonuda  ;D

Además comentarte que cojonudos cada uno de los tutos de gameboy, que ya me vale a mí no haberlo hecho hasta ahora  :-[

falvarez

  • Humano
  • Mensajes: 4
Hola, pocket_lucho.

Lo primero de todo, felicitarte por el tutorial. Porque, aunque esté enfocado a la Gameboy, muchos de los conceptos son aplicables a la mayoría de sistemas.

Y, yendo al turrón, hace tiempo que empecé a estudiar sobre cómo desarrollar para GBA. Aunque es algo que tengo parado desde hace meses por falta de tiempo, donde justo me quedé fue en cómo enfocar la rutina para manejar un mapeado que excediera el tamaño máximo que ofrece el hardware, que es justo lo que comentas en el último capítulo del tutorial. Ya tenía algo hecho totalmente por software pero quería exprimir las posibilidades del hardware.

Lo que me ha parecido entender es que, gracias a tu enfoque, al hacer scroll redibujas sólo una columna en vez de tener que redibujar toda la pantalla. Mi pregunta es, ¿por qué has escogido pintar 2 columnas más? Suponiendo un scroll píxel a píxel, eso te ahorraría tener que redibujar 15 de las 16 veces, ¿correcto? ¿Por qué no, entonces, pintar más columnas de golpe. ¿O es que lo he entendido mal y se me escapa algo?

Muchas gracias y un cordial saludo.

pocket_lucho

  • T-500
  • Mensajes: 1 117
Hola Falvarez! Bienvenido por aqui! Pues si, como has visto, la idea es que esto funcione en cualquier chisme que trabaje con mapas de tiles y si van en GB... en cualquier hardware superior pues mejor aun!

Lo de dibujar 2 columnas más inicialmente es pq yo redibujo una columna entera al avanzar una (un tile), una vez cada 8 pixels, si solo dibujara una... pillaría la que voy a actualizar, si lo modificas a una lo podrás comprobar, mejor dos. Aparte solo puedo dibujar tiles completos, no pixels uno a uno. Eso sirve para tener margen tanto si avanza a la derecha como a la izquierda. Si el scroll fuera únicamente en un sentido, si que dibujaría el mapa completo al inicio y actualizaría la columna más lejana, pero como no es el caso, dibujo eso, un pelín fuera del rango visible y solo una vez al avanzar un tile, por eso al redibujar reinicio SCXCnt = 0;

Esta es la forma más simple que se me ha ocurrido (pero seguro que no la más eficaz), luego ya cada uno que la adapte a sus propias necesidades ;)
última modificación: 04 Octubre 2014, 10:18:48 por pocket_lucho

pocket_lucho

  • T-500
  • Mensajes: 1 117
TUTORIAL 6: SONIDO

En esta parte del tutorial vamos a poder conseguir hacer sonar tanto música como efectos de sonido en la consola. Para ello voy a contar con la ayuda de David Sánchez "Murciano".

Hay que aclarar que no va a ser un tutorial para el aprendizaje de la composición con trackers, ya que veremos que al no haber uno dedicado para Game Boy y tener que utilizar el formato .MOD, cada uno puede utilizar el tracker con el que más cómodo esté.

El player que utilizaremos para la música es obra de Antonio Niño Díaz https://github.com/AntonioND/gbt-player y consta de un conversor de .MOD a GBT (Game Boy Tracker) y del player que hará que suene la música en la consola.

Así que nos vamos a centrar en las limitaciones con las que tenemos que lidiar y sobre todo, vamos a ver un caso práctico reconocible para que veáis como se escucha el conjunto de música y sfx.

La Game Boy tiene 4 canales: 2 de onda cuadrada, 1 de WAVE y 1 canal de ruido.

En el ejemplo que vamos a ver he usado sólo los 3 primeros canales, ya que por el poco tiempo que he tenido para hacer las pruebas, no me convenció ningún sonido de batería con el canal 4 así que decidí ir a la práctico y dejar sonando la melodía bien con sólo 3 canales.

Yo utilizo para editar los .MOD el Milky Tracker http://www.milkytracker.org/. Vamos a abrir el “template.mod” del ejemplo, aunque si queréis profundizar más en el tema, en el paquete del player también vienen ejemplos prácticos.



Como véis, tenemos que crear un .MOD con sólo 4 canales. Cosas a tener en cuenta, como por ejemplo la velocidad. No se pueden modificar los BPM. Sólo se puede modificar la velocidad así que si queremos modificarla en cualquier parte de la composición tendremos que utilizar el comando FXX.

Si os fijáis, tenemos una ayuda muy importante con los instrumentos, ya que en el ejemplo que viene con el player los tenemos definidos y nombrados con el canal a utilizar. Así nos hacemos una idea rápida de que instrumentos utilizar en cada uno de los 4 canales.

IMPORTANTE: El resultado final en la consola difiere de como se escucha en el tracker, así que os tocará ir compilando a menudo para ver si el sonido que estáis consiguiendo es de vuestro gusto. Pero ya os digo que por las pocas pruebas que he hecho, la GameBoy hace su magia y siempre me gusta más como suena en la consola que en el tracker.

Siguiendo con las limitaciones que nos encontramos a la hora de utilizar este player, copio las instrucciones que vienen con el player para que os hagáis una idea de por donde van los tiros.

Código: [Seleccionar]
                         FREQUENCIES
                         -----------

You can use notes from C3 to B8.

                         INSTRUMENTS
                         -----------

+----------------------------------------------------------------+
|CHANNEL|RANGE|                      NOTES                       |
+-------+-----+--------------------------------------------------+
|   1   | 1-4 |                                                  |
|   2   | 1-4 |                                                  |
|   3   | 8-15|Volume is usually a bit lower than other channels.|
|   4   |16-31|Doesn't change with frequency (always C5).        |
+-------+-----+--------------------------------------------------+

                           EFFECTS
                           -------

0xy - Arpeggio. Only channels 1, 2 and 3.

Bnn - Jump to pattern in order nn (in hexadecimal).

Cnn - Sets the volume to nn (in hexadecimal). Valid values from 00h to 40h.
      Channel 3 can only be set to 0%, 25%, 50% and 100%. Others can be set
      in a range of 0-Fh.

Dnn - Ends this pattern and jumps to position nn (in decimal) in next pattern.
      If used the last step of a pattern it will jump two patterns, not one!

E8n - Sets the panning to n (in hexadecimal).
      Left --- Both --- Right
      0123   456789AB    CDEF

ECn - Cut Note after n ticks. If n > speed or n = 0, it won't work.

Fnn - Sets speed to nn (in hexadecimal). Valid values are 01h to 1Fh.
      The higher the value, the slower the song. BPM speed not supported.

Effects are limited in channel 3 when setting a new note (only half of them
are available). It shouldn't be a problem since the effects that can't be
used are control commands that can be used by other channels.

**********************************************************************
* You should set volume and instrument whenever you put a new note!! *
* You should set instrument whenever you change volume in CH3!!      *
* You should always put an instrument whenever you use arpeggio!!    *
**********************************************************************

Tenemos el rango de notas que podemos utilizar, los instrumentos como os he comentado más arriba y los efectos que podemos utilizar.

Algo muy importante es que cada vez que pongamos una nota debe ir acompañada de su correspondiente volumen e instrumento.

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.

Y poco más, en esa lista vienen los efectos que son standard en este tipo de archivos y que se pueden utilizar con este player.


CÓDIGO
Ahora que ya sabemos como debe ser nuestro fichero musical, vamos a escribir el código para reproducirlo.

En nuestro "main.c", enlazamos a las cabeceras de las GBDK como siempre (gb.h), a las funciones de dibujado para el texto (drawing.h), a la del player de música (gbt_player.h) y al fichero de música convertido (ya veremos como compilarlo):

Código: [Seleccionar]
#include <gb/gb.h>
#include <gb/drawing.h>

// libreria musical Antonio Niño Díaz
#include "mod2gbt/gbt_player.h"

extern const unsigned char * song_Data[];

Ya en la función "main()", desactivamos las interrupciones, cargamos la canción con "gbt_play", activamos la reproducción en bucle y hacemos que cuando ocurra la interrupción VBL se ejecute la función de actualización del player de música con "add_VBL(gbt_update)". Añadimos la interrupción VBL con "set_interrupts(VBL_IFLAG)" y activamos las interrupciones:

Código: [Seleccionar]
void main(){

    disable_interrupts();       // desactiva las interrupciones

    gbt_play(song_Data, 2, 7);  // reproduce la cancion, en banco indicado y a la velocidad dada
    gbt_loop(0);                // activa/desactiva el loop

    add_VBL(gbt_update);        // actualiza el player, ha de ejecutarse cada frame (cambia a banco 1)

    set_interrupts(VBL_IFLAG);  // añade la interrupcion VBL
    enable_interrupts();        // activa las interrupciones

Ya para terminar, añadimos un texto de debug con las funciones que vimos en el primer tutorial y entramos en un bucle infinito:

Código: [Seleccionar]
    gotogxy(1, 1);
    gprintf("GAMEBOY SOUND TEST");
    gotogxy(1, 2);
    gprintf("------------------");

    // bucle infinito
    while(1){
        // sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
        wait_vbl_done();
    }

El tracker musical se guarda en el banco 2 (junto más cosas), por lo que hay que modificar la compilación para añadir soporte de banking como ya vimos en los tutoriales anteriores. Además, cada vez que se actualiza en "wait_vbl_done()", cambia a ese banco, por lo que si tenemos gráficos en otros bancos, recordad que hay que cambiar a su banco primero. Como podéis observar, empezamos con la conversión del fichero ".mod" y lo compilamos junto con nuestro código:

Código: [Seleccionar]
mod2gbt\mod2gbt mod2gbt/template.mod song -c 2
move output.c mod2gbt
lcc -c -o main.o main.c
lcc -c -o output.o mod2gbt/output.c
lcc -c -o gbt_player.o mod2gbt/gbt_player.s
lcc -c -o gbt_player_bank1.o mod2gbt/gbt_player_bank1.s
lcc -Wl-yt1 -Wl-yo4 -o rom.gb main.o output.o gbt_player.o gbt_player_bank1.o
del *.o
bgb.exe rom.gb
pause

El código completo quedaría así:

Código: [Seleccionar]
#include <gb/gb.h>
#include <gb/drawing.h>

// libreria musical Antonio Niño Díaz
#include "mod2gbt/gbt_player.h"

extern const unsigned char * song_Data[];

///////////////////////////////////////////////
//  punto de entrada
///////////////////////////////////////////////
void main(){

    disable_interrupts();       // desactiva las interrupciones

    gbt_play(song_Data, 2, 7);  // reproduce la cancion, en banco indicado y a la velocidad dada
    gbt_loop(0);                // activa/desactiva el loop

    add_VBL(gbt_update);        // actualiza el player, ha de ejecutarse cada frame (cambia a banco 1)

    set_interrupts(VBL_IFLAG);  // añade la interrupcion VBL
    enable_interrupts();        // activa las interrupciones

    gotogxy(1, 1);
    gprintf("GAMEBOY SOUND TEST");
    gotogxy(1, 2);
    gprintf("------------------");

    // bucle infinito
    while(1){
        // sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
        wait_vbl_done();
    }
}

El fuente con todo lo necesario para compilar lo teneis listo en este fichero:

https://dl.dropboxusercontent.com/u/33369593/tutosGB/06_musica.rar


SFX
Como se ha dicho al principio, la GB posee 4 canales. Desgraciadamente, al contrario que la música no hay ningún editor mágico de efectos de sonido y tocará hacerlos a mano. Si conoceis algo mejor, por favor indicarlo y lo añadimos al tutorial.

En la carpeta DOCS, he metido un fichero llamado "GBSOUND.txt" con la información que he encontrado del sonido. Básicamente para hacer efectos de sonido vamos a escribir sobre los registros de cada canal:

Canal 1, (tono y portamento)
NR10-NR14

Canal 2, (tono)             
NR21-NR24

Canal 3, (onda programable) 
NR30-NR34

Canal 4, (ruido)             
NR41-NR44

Para saber lo que hace cada registro os recomiendo mirar detenidamente la documentación y la sección de sonido del tutorial en ensamblador de xzakox donde vienen explicados de uno en uno, básicamente duración, envolvente y frecuencia:

http://wiki.ladecadence.net/doku.php?id=tutorial_de_ensamblador#hola_sonido

Tambien debemos prestar atención a estos registros:

NR50, "Vin (input line on cart connector) / Main Output Volume Control" o volumen principal de las salidas izquierda y derecha, que iniciaremos a 0x77U. Este registro permite añadir un canal extra desde el cartucho.

NR51, "Sound Output terminal select (1: enabled; 0: disabled)" o selección de salida de cada canal que iniciaremos a 0x00U.

NR52, "Master control and status flags" o encendido/apagado general del sonido, escribir 00h en este registro, ahorrará un 16% o más de pilas, lo iniciaremos a 0xF8U.

Vamos a hacer un programa que reproduzca varios SFX de esta forma, escribiendo en los registros de cada canal.

Como antes, enlazamos a las cabeceras de las GBDK como siempre (gb.h), a las funciones de dibujado para el texto (drawing.h).

Código: [Seleccionar]
#include <gb/gb.h>
#include <gb/drawing.h>

Desde "main()", creamos varias variables para controlar el pad y si hay algun sonido reproduciendose, para evitar repeticiones si dejamos el botón pulsado. Dibujamos además un texto de debug.

Código: [Seleccionar]
main(){
    UBYTE input, busy = 0, bIn_progress = 0;

    gotogxy(1, 1);
    gprintf("GAMEBOY SOUND TEST");
    gotogxy(1, 2);
    gprintf("------------------");


Iniciamos los registros de audio antes comentados:

Código: [Seleccionar]
    NR52_REG = 0xF8U;
    NR51_REG = 0x00U;
    NR50_REG = 0x77U;

Y desde el bucle infinito, comprobamos las pulsaciones de los botones y llamamos a las distintas funciones de cada SFX:

Código: [Seleccionar]
// bucle infinito
    while(1){

// sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
        wait_vbl_done();

        input = joypad();

        // sonidos de una nota
    if(input & J_A && busy == 0){
            busy = 1;
            sound_00();
        }

        if(input & J_B && busy == 0){
            busy = 1;
            sound_01();
        }

        if(input & J_SELECT && busy == 0){
            busy = 1;
            sound_02();
        }

        // sonido de dos notas
        if(input & J_START && busy == 0){
            busy = 1;
            bIn_progress = sound_03(bIn_progress);
        }

        if( bIn_progress )
             bIn_progress = sound_03(bIn_progress);


        // control para que los sonidos no se sigan reproduciendo si se deja el boton pulsado
        if( !( input & J_B ) && !( input & J_A ) && !( input & J_START ) && !( input & J_SELECT ) && busy == 1 )
            busy = 0;
}

Estas funciones, se limitan a escribir en los registros de cada canal que indicamos antes, especificando duración, envolvente, frecuencia...

Código: [Seleccionar]
//////////////////////////////////////////////////////////////////////////////////////////////
//
//  void sound_0X(void){
//  sonidos de una nota
//  se hacen escribiendo a pelo en los registros de cada canal
//
//////////////////////////////////////////////////////////////////////////////////////////////
void sound_00( void ){

    NR10_REG = 0x34U; // el 4º bit indica si la freq se incrementa o decrece

    // bits 5 a 7  indican el retraso del barrido
    NR11_REG = 0x80U;
    NR12_REG = 0xF0U;
    NR13_REG = 0x0AU; // 8 bits de frecuencia mas bajos
    NR14_REG = 0xC6U; // los primeros 3 bits son los 3 bits mas altos de la frecuencia
    NR51_REG |= 0x11;
}

void sound_01( void ){

    NR41_REG = 0x00;
    NR42_REG = 0xE1;
    NR43_REG = 0x22;
    NR44_REG = 0xC3;
    NR51_REG = 0x88U;
}

void sound_02( void ){

    NR10_REG = 0x04U;
    NR11_REG = 0xFEU;
    NR12_REG = 0xA1U;
    NR13_REG = 0x8FU;
    NR14_REG = 0x86U;
    NR51_REG = 0xF7;
}

//////////////////////////////////////////////////////////////////////////////////////////////
//
//  void sound_03( UBYTE bIn_progress ){
//  sonidos de dos nota
//  se hacen escribiendo a pelo en los registros de cada canal
//
//////////////////////////////////////////////////////////////////////////////////////////////
UBYTE sound_03( UBYTE bIn_progress ){

    // esta el sonido 2 reproduciendose?
    if( NR52_REG & 0x02 ){
return 0x01;
    }

    // el sonido 2 no esta reproduciendose, pero ya se ha tocado la primera nota
    else if( bIn_progress ){
NR21_REG = 0x80U;
NR22_REG = 0x73U;
NR23_REG = 0x9EU;
NR24_REG = 0xC7U;
NR51_REG |= 0x22;
        return 0x00;
    }
    // toca la primera nota
    else{
NR21_REG = 0xAEU;
NR22_REG = 0x68U;
NR23_REG = 0xDBU;
NR24_REG = 0xC6U;
NR51_REG |= 0x22;
return 0x01;
    }
}

El código completo quedaría así:

Código: [Seleccionar]
#include <gb/gb.h>
#include <gb/drawing.h>

///////////////////////////////////////////////
//
//  void sfxInit( void )
//  inicializa el chip de sonido para los sfx
//
///////////////////////////////////////////////
void sfxInit( void ){

    NR52_REG = 0xF8U;
    NR51_REG = 0x00U;
    NR50_REG = 0x77U;
}

//////////////////////////////////////////////////////////////////////////////////////////////
//
//  void sound_0X(void){
//  sonidos de una nota
//  se hacen escribiendo a pelo en los registros de cada canal
//
//////////////////////////////////////////////////////////////////////////////////////////////
void sound_00( void ){

    NR10_REG = 0x34U; // el 4º bit indica si la freq se incrementa o decrece

    // bits 5 a 7  indican el retraso del barrido
    NR11_REG = 0x80U;
    NR12_REG = 0xF0U;
    NR13_REG = 0x0AU; // 8 bits de frecuencia mas bajos
    NR14_REG = 0xC6U; // los primeros 3 bits son los 3 bits mas altos de la frecuencia
    NR51_REG |= 0x11;
}

void sound_01( void ){

    NR41_REG = 0x00;
    NR42_REG = 0xE1;
    NR43_REG = 0x22;
    NR44_REG = 0xC3;
    NR51_REG = 0x88U;
}

void sound_02( void ){

    NR10_REG = 0x04U;
    NR11_REG = 0xFEU;
    NR12_REG = 0xA1U;
    NR13_REG = 0x8FU;
    NR14_REG = 0x86U;
    NR51_REG = 0xF7;
}

//////////////////////////////////////////////////////////////////////////////////////////////
//
//  void sound_03( UBYTE bIn_progress ){
//  sonidos de dos nota
//  se hacen escribiendo a pelo en los registros de cada canal
//
//////////////////////////////////////////////////////////////////////////////////////////////
UBYTE sound_03( UBYTE bIn_progress ){

    // esta el sonido 2 reproduciendose?
    if( NR52_REG & 0x02 ){
return 0x01;
    }

    // el sonido 2 no esta reproduciendose, pero ya se ha tocado la primera nota
    else if( bIn_progress ){
NR21_REG = 0x80U;
NR22_REG = 0x73U;
NR23_REG = 0x9EU;
NR24_REG = 0xC7U;
NR51_REG |= 0x22;
        return 0x00;
    }
    // toca la primera nota
    else{
NR21_REG = 0xAEU;
NR22_REG = 0x68U;
NR23_REG = 0xDBU;
NR24_REG = 0xC6U;
NR51_REG |= 0x22;
return 0x01;
    }
}

///////////////////////////////////////////////
//  punto de entrada
///////////////////////////////////////////////
void main(){

    UBYTE input, busy = 0, bIn_progress = 0;

    gotogxy(1, 1);
    gprintf("GAMEBOY SOUND TEST");
    gotogxy(1, 2);
    gprintf("------------------");

    // inicia los registros para los sfx
    sfxInit();

    // bucle infinito
    while(1){

// sincroniza con el blanqueo vertical para dibujar los graficos y pone los contadores (timers)
        wait_vbl_done();

        input = joypad();

        // sonidos de una nota
    if(input & J_A && busy == 0){
            busy = 1;
            sound_00();
        }

        if(input & J_B && busy == 0){
            busy = 1;
            sound_01();
        }

        if(input & J_SELECT && busy == 0){
            busy = 1;
            sound_02();
        }

        // sonido de dos notas
        if(input & J_START && busy == 0){
            busy = 1;
            bIn_progress = sound_03(bIn_progress);
        }

        if( bIn_progress )
             bIn_progress = sound_03(bIn_progress);


        // control para que los sonidos no se sigan reproduciendo si se deja el boton pulsado
        if( !( input & J_B ) && !( input & J_A ) && !( input & J_START ) && !( input & J_SELECT ) && busy == 1 )
            busy = 0;
}
}

Podriamos reproducir música y efectos a la vez de esta forma pero claro, los canales no se deben pisar, asi que podemos hacer que nuestra canción no use cierto canal o usarlo para un canal de acompañamiento y reproducir por ese los SFX.

DEBERES
Por ejemplo, crear un modulo que no use cierto canal y añadir SFX que suenen por ese canal.