Registrarse

[Otros] Como crear juegos de GBA

L!no

GBA Developer
hey!
Hoy les traigo un tutorial sobre el desarrollo de programas de gba desde 0, para eso vamos a necesitar:
  1. Cygwin
  2. DevkitARM
  3. Notepad++
Para compilar tu primer programa deberás copiar el directorio "template" dentro de C:\devkitPro\examples\gba\ a otra ubicación en tu disco duro. Es conveniente que esa ubicación sea cercana a la raíz de tu disco duro y que los nombres de directorio no tengan espacios o caracteres especiales. Un ejemplo apropiado sería: d:\gba\.

La carpeta template contiene lo siguiente:

+ Carpeta source. Contiene el código fuente de tu programa.
+ Archivo makefile. Archivo de texto que contiene las instrucciones necesarias para integrar tu ejecutable GBA.

Abre el archivo template.c en tu Notepad++, verás el siguiente código:
Código:
#include <gba_console.h>

#include <gba_video.h>

#include <gba_interrupt.h>

#include <gba_systemcalls.h>

#include <gba_input.h>

#include <stdio.h>

#include <stdlib.h>

 

//---------------------------------------------------------------------------------

// Program entry point

//---------------------------------------------------------------------------------

int main(void) {

//---------------------------------------------------------------------------------

 

 

   // the vblank interrupt must be enabled for VBlankIntrWait() to work

   // since the default dispatcher handles the bios flags no vblank handler

   // is required

   irqInit();

   irqEnable(IRQ_VBLANK);

 

   consoleDemoInit();

 

   // ansi escape sequence to set print co-ordinates

   // /x1b[line;columnH

   iprintf("\x1b[10;10HHello World!\n");

 

   while (1) {

      VBlankIntrWait();

   }

}
La primera sección son las inclusiones de librerías para controlar el hardware del GBA. Posteriormente se inicializan las interrupciones hardware del GBA con irqInit() y en este caso se prepara la interrupción de refresco de video, pero no se usará.

Después se inicializa la pantalla del GBA para que opere como consola de texto ASCII con la función consoleDemoInit(). Al haber inicializado la pantalla ya se puede usar la función iprintf() para enviar cadenas de texto a pantalla. Esta función opera con secuencias de escape ANSI para mover el cursor o colocar texto en lugares específicos.

Por último el programa entra a un ciclo infinito.

Compilando el hello world.

Esta parte es facil, solo tienen que abrir Cygwin y escribir lo siguiente:
Código:
cd ruta/a/tu/proyecto
(recuerden usar las barras invertidas(/) o no funcionara)
y luego escribir lo siguiente:
Código:
make
si todo salio bien, se habra creado una rom .gba, que cuando lo habramos mostrara lo siguiente:
http://prntscr.com/h6bv8v
felicidades, compilaron su primer programa de gba!
fuentes consultadas:
devkitpro.org

ahora vamos a tomar el archivo template.c, y borrar su contenido para que contenga esto:
Código:
//codigo de ejemplo sacado de los tutoriales de tonc 
int main() {
    *(unsigned int*)0x04000000 = 0x0403;

    ((unsigned short*)0x06000000)[120+80*240] = 0x001F;
    ((unsigned short*)0x06000000)[136+80*240] = 0x03E0;
    ((unsigned short*)0x06000000)[120+96*240] = 0x7C00;

    while(1);

    return 0;
}
no te preocupes si no lo entiendes, es informacion muy cruda y directa, y no esta hecho para ser muy comprensible. algo mejor es esto:
Código:
//codigo de tonc, adaptdo por L!no
#define VRAM 0x06000000 //definimos VRAM como 0x06000000, la seccion de la memoria correspondiente a la VRAM
#define IO 0x04000000 //definimos IO como 0x04000000, la seccion de la memoria correspondiente a la IO
#define CLR_RED     0x001F //definimos los colores rojo, lima,amarillo y azul
#define CLR_LIME    0x03E0
#define CLR_YELLOW  0x03FF
#define CLR_BLUE    0x7C00

void setmode3() { //definimos la funcion setmode3
	*(unsigned int*)IO = 0x0403; //iniciamos la pantalla en modo 3, y activamos el background 2
}

void writepoint(int x, int y, int color) { //definimos la funcion writepoint
	((unsigned short*)VRAM)[x+y*240] = color; //escribimos un punto en la VRAM con las cordenadas y el color indicados
}
	
int main()
{
    setmode3(); //llamamos a setmode3
    writepoint(120, 80, CLR_RED); //llamamos a writepoint, indicando la posicion x, la posicion y, y el color
    writepoint(136, 80, CLR_LIME);
    writepoint(136, 96, CLR_YELLOW);
    writepoint(120, 96, CLR_BLUE);

    while(1); //bucle infinito para evitar errores extraños

    return 0;
}
es lo mismo que el otro, pero mas comprensible y mas facil de modificar y entender.
conceptos:
  • Si nosotros escribimos 0x0403 en la direccion 0x04000000 se activa el mode 3 y el background 2
  • para la posicion x e y se usa esto: [x+y*240]
si nosotros lo compilamos nos sale esto en el caso del 1er codigo:
http://prntscr.com/h6c5yl
y esto en el caso del segundo:
http://prntscr.com/h6c6a8
fuentes consultadas:
http://www.coranac.com/tonc

Hoy les traigo un capitulo muy importante: la deteccion de botones. La GBA tiene 10 botones, los cuales son:
    1. A
    2. B
    3. SELECT
    4. START
    5. DERECHA
    6. IZQUIERDA
    7. ARRIBA
    8. ABAJO
    9. R
    10. L
para checkearlos se recurre a la dirección 0x04000132, tambien conocida como REG_KEYINPUT o simplemente REG_P1.
¿como leemos esto?
Primero importamos las librerias:
Código:
#include <gba_types.h>
No se preocupen por esto, son solo definciones para numeros.
Luego definimos la región de la IO correspondiente a REG_KEYINPUT:
Código:
#define REG_KEYINPUT (* (volatile u16*) 0x4000130)
lo definimos como volatile ya que es una región cambiante y como u16 (un tipo de numero importado en la primera linea) porque es un registro de 16 bits.
Despues definimos las mascaras:
Código:
#define KEY_A 0x0001
#define KEY_B 0x0002
#define KEY_SELECT 0x0004
#define KEY_START 0x0008
#define KEY_RIGHT 0x0010
#define KEY_LEFT 0x0020
#define KEY_UP 0x0040
#define KEY_DOWN 0x0080
#define KEY_R 0x0100
#define KEY_L 0x0200
aunque no concuerden con los numeros dichos mas arriba, es una mascara cuy bit activado concuerda con el numero de boton.
Aunque suene antiintuitivo, cuando una tecla esta siendo presionada, su registro esta en 0, mientras que cuando no lo esta, su registro esta en 1. eso quiere decir que cuando la tecla START esta siendo presionada, el registro se parece a esto:
Código:
REGISTRO:  ???? ??11 1111 0111
Le aplicamos una operacion OR con la mascara general para poner los bits que no conocemos en 1.
Código:
INPUT: ???? ??11 1111 0111
FLAG : 1111 1100 0000 0000
 --------------------------
      1111 1111 1111 0111
Luego le aplicamos un AND con la mascara de start:
Código:
 INPUT:  1111 1111 1111 0111
 START: 0000 0000 0000 1000
 --------------------------
val= 0x 0000 0000 0000 0000
Eso nos da 0, si le aplicamos un NOT nos da 1: ¡La tecla fue pulsada!
ahora pongamos eso en un codigo:
Primero iniciamos una variable de 16 bits key_cur:
Código:
u16 key_cur;
Despues creamos una función key_poll:
Código:
inline void key_poll()
{
	
}
Luego metemos dentro la operacion OR:
Código:
inline void key_poll()
{
	key_cur = REG_KEYINPUT | KEY_MASK;
}
y creamos una funcion getKeyState:
Código:
u32 getKeyState(u16 key_code)
{
    return !(key_cur & key_code);
}
ahora solo falta llamar a estas funciones desde main. Creamos un bucle infinito:
Código:
void main()
{
	while(1)
	{
		        
	}
}
llamamos a la funcion key_poll:
Código:
void main()
{
	while(1)
	{
		key_poll();
	}
}
luego añadimos un if:
Código:
void main()
{
	while(1)
	{
		key_poll();
		if () {
			//hacer algo
		}
	}
}
y le añadimos el getkeystate
Código:
void main()
{
	while(1)
	{
		key_poll();
		if ( getKeyState(KEY_A) ) {
			//tecla A pulsada
		}
	}
}
fuentes consultadas:
http://kylehalladay.com/blog/tutorial/gba/

hoy continuaremos con el tema de los botones. que pasa si queremos saber si una tecla esta siendo pulsada? ahí les va:
si nosotros comparamos el estado anterior de la tecla con el estado actual, podemos saber varias cosas. entre ellas, saber si una tecla acaba de ser presionada o si fue soltada en es mismo momento. eso se logra con un algoritmo tal que asi:
para saber si la tecla acaba de ser pulsada:
Código:
(NOT(tecla_anterior) OR tecla_actual) AND mascara_de_la_tecla
eso nos da 1 si la tecla acaba de ser pulsada y 0 en caso contrario.
para saber si la tecla acaba de ser soltada:
Código:
(NOT(tecla_actual) OR tecla_anterior) AND mascara_de_la_tecla
eso nos da 1 si la tecla acaba de ser soltada y 0 en caso contrario.
ahora pasemos esas ecuaciones al C:
primero definimos una variable key_prev:
Código:
u16 key_prev;
luego actualizamos la funcion key_poll, dejandola así:
Código:
void key_poll()
{
    input_prev = input_cur;
    input_cur = REG_KEYINPUT | KEY_MASK;
}
ahora definimos una funcion waskeypressed:
Código:
u16 wasKeyPressed(u16 key_code)
{

}
y dentrole añadimos el algoritmo de mas arriba:
Código:
uint16 wasKeyPressed(uint16 key_code)
{
	return (input_cur & ~input_prev) & key_code;
}
luego creamos una funcion waskeyeleased:
Código:
u16 wasKeyReleased(u16 key_code)
{

}
y le añadimos el otro algoritmo:
Código:
uint16 wasKeyReleased(uint16 key_code)
{
	return (~input_cur & input_prev) & key_code;
}
ahora ya podemos controlar con toda precision el estado de las teclas.
fuentes consultadas:
http://kylehalladay.com/blog/tutorial/gba/

Tomemos por un segundo el codigo del captulo 4, y guardemoslo como input.h . vayamos de vuelta an nuestro archivo principal, borremosle el contenido y empecemos con el tema de hoy: dibujar y mover cuadrados en pantalla.
Primero definimos la VRAM, la IO, la funcion setmode3 e importamos la libreria gba_types:
Código:
#define VRAM 0x06000000
#define IO 0x04000000

void setmode3() {
	*(unsigned int*)IO = 0x0403;
}
despues definimos una funcion main:
Código:
void main() {

}
y definimos una variable i y un bucle infinito:
Código:
int i = 0;
void main() {
	while(1) {

	}
}
y dentro le ponemos esto:
Código:
int i = 0;
void main() {
	while(1) {
		((volatile uint16*)VRAM)[i] = 0xFFFF;
		i += 1;
	}
}
con esto deberamos tener un hermoso fondo blanco

pero a quien le gustan los fondos blancos? creemos una funcion makecol:
Código:
inline u16 MakeCol(u8 red, u8 green, u8 blue)
{
    return (red & 0x1F) | (green & 0x1F) << 5 | (blue & 0x1F) << 10;
}
no voy a profundizar en esto, eso ya lo haremos en otro capitulo.
luego definimos una funcion drawline:
Código:
void drawRect(int left, int top, int width, int height, u16 clr)
{

}
le insertamos un bucle for:
Código:
void drawRect(int left, int top, int width, int height, u16 clr)
{
    for (int y = 0; y < height; ++y)
    {
        //lo que este aqui se repetira hasta que se alcanze el limite heigth
    }
}
y le añadimos otro bucle for dentro:
Código:
void drawRect(int left, int top, int width, int height, u16 clr)
{
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
    	   //esto se repetira hasta que alcanzemos el limite width
        }
    }
}
y le añadimos la funcion principal:
Código:
void drawRect(int left, int top, int width, int height, u16 clr)
{
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
    	   VRAM[(top + y) * 240 + left + x] = clr;
        }
    }
}
con eso escribimos nuestro cuadrado/rectangulo/paraboloide en la VRAM.

para regular los fps de la consola y hacer que no mueva mas de los que soporta la pantalla, creamos esta funcion:
Código:
#define REG_VCOUNT      (* (volatile u16*) 0x04000006)

inline void vsync()
{
  while (REG_VCOUNT >= 160);
  while (REG_VCOUNT < 160);
}
no voy a explicarla, no es dificil de comprender.
ahora actualizemos la funcion main:
primero borramos su contenido y llamamos a setmode3:
Código:
setmode3();
luego abrimos un bucle for:
Código:
    for ()
    {

    }
y le añadimos las condiciones:
Código:
    for (int i = 0; i < 240 * 160; ++i)
    {
    	
    }
y la rama condicional:
Código:
    for (int i = 0; i < 240 * SCREEN_H; ++i)
    {
    	SCREENBUFFER[i] = MakeCol(0,0,0);
    }
es basicamente el while(1) de antes, solo que llena la pantalla de negro y cuando termina para.
luego definimos la variable x:
Código:
int x = 0;
y abrimos un bucle infinito:
Código:
    while(1)
    {

    }
dentro llamamos a la funcion vsync:
Código:
    while(1)
    {
    	vsync();
    }
llamamos a la funcion drawrect:
Código:
    while(1)
    {
    	vsync();
    	drawRect(x % 240, (x / 240) * 10, 10, 10,MakeCol(31,31,31));
    }
y aumentamos el valor de x:
Código:
    while(1)
    {
    	vsync();
    	drawRect(x % 240, (x / 240) * 10, 10, 10,MakeCol(31,31,31));
    	x += 10;
    }
por ultimo, añadimos un return 0 luego del bucle:
Código:
     return 0;
ahora tenemos una pantalla negra que se va llenando de blanco 10 pixels a la vez.

pero eso termina y se queda ahí. vamos a cambiar eso:
primero vamos a main y creamos un if despues de la llamada a vsync:
Código:
if ()
colocamos la condicion:
Código:
if ( x > 240 * (160/10))
como tiene una rama condicional de una sola linea, lo ponemos seguido:
Código:
if ( x > 240 * (SCREEN_H/10)) x = 0;
luego abrimos una variable last:
Código:
int last = x - 10;
y llamamos a la funcion drawrect:
Código:
drawRect(last % 240, (last / 240 * 10, 10, 10,MakeCol(0,0,0));
ahora tenemos un cuadrado de 10 por 10 viajando por la pantalla de nuestra GBA (o nuestro emulador).

hoy vamos a tomar nuestro cuadrado de 10 x 10 y lo convertiremos en un juego. o al menos un boceto de uno.
primero incluimos la libreria input.h de antes:
Código:
#include "input.h"
despues definimos SCREEN_W y SCREEN_H:
Código:
#define SCREEN_W            240
#define SCREEN_H            160
luego eliminaremos esto:
Código:
int last = x - 10;
e iremos arriba para definir last:
Código:
int last;
vamos de bajo de vsync() y llamamos a key_poll:
Código:
key_poll();
borramos esto:
Código:
drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10,MakeCol(31,31,31));
    x += 10;
ahora abran un multibucle if/else if (no me molesten, me gusta decirles asi):
Código:
	if () {

	} else if () {

	} else if () {

	} else if () {

	}
eso significa que si lo primero da falso, controla lo segundo, si el segundo da falso, controla el tercero, etc.
le añadimos condiciones getkeypressed:
Código:
	if (getKeyState()) {

	} else if (getKeyState()) {

	} else if (getKeyState()) {

	} else if (getKeyState()) {

	}
y le añadimos las teclas:
Código:
	if (getKeyState(KEY_RIGHT)) {

	} else if (getKeyState(KEY_LEFT)) {

	} else if (getKeyState(KEY_DOWN)) {

	} else if (getKeyState(KEY_UP)) {

	}
ya podemos empezar.
Derecha:
esta es simple. le dcimos que dibuje al cuadrado en x:
Código:
	if (getKeyState(KEY_RIGHT)) {
		drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
	}
aumentamos x:
Código:
	if (getKeyState(KEY_RIGHT)) {
		drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
		x += 10;
	}
y restamos last:
Código:
	if (getKeyState(KEY_RIGHT)) {
		drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
		x += 10;
		last = x - 10;
	}
ahora el cuadrado va para la derecha al tocar dicha tecla.
Izquierda:
la izquierda es lo mismo pero al reves:
dibujamos el cuadrado:
Código:
	} else if (getKeyState(KEY_LEFT)) {
		drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
	}
restamos x:
Código:
	} else if (getKeyState(KEY_LEFT)) {
		drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
		x -= 10;
	}
y sumamos last:
Código:
	} else if (getKeyState(KEY_LEFT)) {
		drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
		x -= 10;
		last = x + 10;
	}
ahora el cuadrado va a la izquierda.
Abajo:
ese es mas dificil. Primero escribimos esto:
Código:
	} else if (getKeyState(KEY_DOWN)) {
		if (x > SCREEN_W * 15) {
			last = x;
			x = x - SCREEN_W * (15);
		}
	}
esto hace que volvamos arriba cundo lleguemos abajo.
abrimos un bucle else:
Código:
	} else if (getKeyState(KEY_DOWN)) {
		if (x > SCREEN_W * 15) {
			last = x;
			x = x - SCREEN_W * (15);
		} else {

		}
	}
dibujamos el cuadrado:
Código:
	} else if (getKeyState(KEY_DOWN)) {
		if (x > SCREEN_W * 15) {
			last = x;
			x = x - SCREEN_W * (15);
		} else {
			drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
		}
	}
definimos el valor de last:
Código:
	} else if (getKeyState(KEY_DOWN)) {
		if (x > SCREEN_W * 15) {
			last = x;
			x = x - SCREEN_W * (15);
		} else {
			drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
			last = x;
		}
	}
y aumentamos x:
Código:
	} else if (getKeyState(KEY_DOWN)) {
		if (x > SCREEN_W * 15) {
			last = x;
			x = x - SCREEN_W * (15);
		} else {
			drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
			last = x;
			x += 240;
		}
	}
ahora nos movemos hacia abajo.
Arriba
escribimos esto:
Código:
	} else if (getKeyState(KEY_UP)) {
		if (x < 160) {
			last = x;
			x = x + SCREEN_W * (15);
		}
	}
dibujamos el cuadrado:
Código:
	} else if (getKeyState(KEY_UP)) {
		if (x < 160) {
			last = x;
			x = x + SCREEN_W * (15);
		} else {
			drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
		} 
	}
definimos el valor de last:
Código:
	} else if (getKeyState(KEY_UP)) {
		if (x < 160) {
			last = x;
			x = x + SCREEN_W * (15);
		} else {
			drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
			last = x;
		} 
	}
y restamos x:
Código:
	} else if (getKeyState(KEY_UP)) {
		if (x < 160) {
			last = x;
			x = x + SCREEN_W * (15);
		} else {
			drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
			last = x;
			x -= 240;
		} 
	}
solo falta solucionar bugs:
añadimos esto en el bucle de main:
Código:
	if (x < 0) {
		x = SCREEN_W * (SCREEN_H/10); 
		drawRect(10 % SCREEN_W, (last / SCREEN_W) * 10, 10, 10,MakeCol (0,0,0)); 
		drawRect(last % SCREEN_W, (0 / SCREEN_W) * 10, 10, 10,MakeCol (0,0,0)); 
	}
y listo!

lo primero que haremos sera crear y definir el valor de una variable puntos:
Código:
int points;
una variable foodinscreen:
Código:
int foodinscreen = 0;
y una variable food_x:
Código:
int food_x;
ahora definimos una funcion drawfood:
Código:
void drawfood() {

}
le añadimos un if:
Código:
void drawfood() {
	if () {

	}
}
su condicion:
Código:
void drawfood() {
	if (!(foodinscreen)) {

	}
}
y escribimos en la rama condicional lo siguiente:
la definicion del valor de food_x:
Código:
void drawfood() {
	if (!(foodinscreen)) {
		food_x =  ;
	}
}
dentro le añadimos un rand:
Código:
void drawfood() {
	if (!(foodinscreen)) {
		food_x = (rand());
	}
}
un limite para el rand:
Código:
void drawfood() {
	if (!(foodinscreen)) {
		food_x = (rand()%(25*16));
	}
}
y una multiplicacion por 10:
Código:
void drawfood() {
	if (!(foodinscreen)) {
		food_x = (rand()%(25*16)) * 10;
	}
}
acabamos de settear el valor de food_x en un numero al azar en los limites de la pantalla multiplo de 10.
llamamos a drawrect:
Código:
void drawfood() {
	if (!(foodinscreen)) {
		food_x = (rand()%(25*16)) * 10;
		drawRect(food_x % SCREEN_W, (food_x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,0));
	}
}
ahora dibujara un cuadrado amarillo en food_x.
por ultimo activamos foodinscreen:
Código:
void drawfood() {
	if (!(foodinscreen)) {
		food_x = (rand()%(25*16)) * 10;
		drawRect(food_x % SCREEN_W, (food_x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,0));
		foodinscreen = 1;
	}
}
ahora definimos una funcion checkfood:
Código:
void checkfood() {

}
le añadimos un if:
Código:
void checkfood() {
	if () {
		
	}
}
y le añadimos la condicion:
Código:
void checkfood() {
	if (food_x == x) {
		//lo que este aqui se ejecutara si el cuadrado esta sobre la comida
	}
}
dentro borramos la comida:
Código:
void checkfood() {
	if (food_x == x) {
		drawRect(food_x % SCREEN_W, (food_x / SCREEN_W) * 10, 10, 10, MakeCol(0,0,0));
	}
}
sumamos 1 a la variable points:
Código:
void checkfood() {
	if (food_x == x) {
		drawRect(food_x % SCREEN_W, (food_x / SCREEN_W) * 10, 10, 10, MakeCol(0,0,0));
		points += 1;
	}
}
y desactivamos foodinscreen:
Código:
void checkfood() {
	if (food_x == x) {
		drawRect(food_x % SCREEN_W, (food_x / SCREEN_W) * 10, 10, 10, MakeCol(0,0,0));
		points += 1;
		foodinscreen = 0;
	}
}
ahora solo añadan esto debajo de key_poll():
Código:
	drawfood();
	checkfood();
Listo! ahora ya hay puntos.
Fuentes:
todo esto fue programado por mi.
La GBA, ademas de las interrupciones por hardware tiene interrupciones por software, que son llamadas mediante el comando de assembly (a.k.a ASM) SWI, que significa software interrupt, y usan como parametos de Entrada/salida los registros del r0 al r3. y me diran cosas como:
¡Lino, no puedes usar ASM en un archivo .c!
y yo les respondere algo como:
claro que se puede, mira lo que sigue:
si nosotros escribimos esto:
Código:
int ASM()
{   asm("");   }
ejecutara la instruccion ASM que pongas dentro de las comillas
ahora vamos a lo bueno:
si saben assembly avanzado, seguro se saben lo que sigue. pero como no hay mucha gente que conozca ASM, ni que hablar de avanzado, explico esto. la funcion SWI recibe 1 parametro:
Código:
SWI 0x[codigo de interrupcion]
el cual puede ser cualquiera de esta lista:
Código:
0x00 	SoftReset 		0x08 	Sqrt
0x01 	RegisterRamReset		0x09 	ArcTan
0x02 	Halt 		0x0A 	ArcTan2
0x03 	Stop 		0x0B 	CPUSet
0x04 	IntrWait 		0x0C 	CPUFastSet
0x05 	VBlankIntrWait 		0x0D 	BiosChecksum
0x06 	Div 		0x0E 	BgAffineSet
0x07 	DivArm 		0x0F 	ObjAffineSet
 
0x10 	BitUnPack 		0x18 	Diff16bitUnFilter
0x11 	LZ77UnCompWRAM 		0x19 	SoundBiasChange
0x12 	LZ77UnCompVRAM 		0x1A 	SoundDriverInit
0x13 	HuffUnComp 		0x1B 	SoundDriverMode
0x14 	RLUnCompWRAM 		0x1C 	SoundDriverMain
0x15 	RLUnCompVRAM 		0x1D 	SoundDriverVSync
0x16 	Diff8bitUnFilterWRAM 		0x1E 	SoundChannelClear
0x17 	Diff8bitUnFilterVRAM 		0x1F 	MIDIKey2Freq
 
0x20 	MusicPlayerOpen 		0x28 	SoundDriverVSyncOff
0x21 	MusicPlayerStart 		0x29 	SoundDriverVSyncOn
0x22 	MusicPlayerStop 		0x2A 	GetJumpList
0x23 	MusicPlayerContinue
0x24 	MusicPlayerFadeOut
0x25 	MultiBoot
0x26 	HardReset
0x27 	CustomHalt
Definiciones de algunas:
0x06: Div
entrada:

r0: numerador

r1: denominador
salida:

r0: numerador / denominador

r1: numerador % denominador

r3: abs(numerador / denominador)
Nota: NO dividas por 0!
0x08: Sqrt
Entrada:

r0: num, un entero sin signo de 32 bits
salida:
r1: sqrt(num)
0x0a: ArcTan2
entrada:

r0: x, un numero con signo de 16 bits (s16)

r1: y, un numero con signo de 16 bits (s16)
salida:

r0: x≥0 : θ= arctan(y/x) ∨ x<0 : θ= sign(y)*(π − arctan(|y/x|).
esto da la inversa total de y = x*tan(θ). El problema con la tangente es que el dominio es un semicírculo, como lo es el rango de arctan. para conseguir el rango del circulo completo, los valores x e y son necesarios no solo para su cociente, sino también para sus signos. el rango matematico de θ es [−π, π⟩, que corresponde a [−0x8000, 0x8000⟩ (o [0, 2π⟩ y [0, 0xFFFF] si prefieres)

para llamar a cualquiera de estas funciones, usamos lo siguiente:
Código:
int funcion(int parametros) {
	asm("swi 0x");
}

guardemos el codigo anterior como game.c y borremos el contenido de template.c . luego haremos esto:
definimos una variable x:
Código:
int x;
y creamos una funcion swi_sqrt:
Código:
int swi_sqrt() {

}
le añadimos el parametro num:
Código:
int swi_sqrt(int num) {

}
y la instruccion:
Código:
int swi_sqrt(int num) {
	asm("swi 0x08");
}
ahora definimos main:
Código:
void main() {

}
dentro llamamos a sqrt con 4 como parametro:
Código:
void main() {
	int y = swi_sqrt1(4);
}
abrmos un if:
Código:
void main() {
	int y = swi_sqrt1(4);
	if() {

	}
}
le añadimos la condicion:
Código:
void main() {
	int y = swi_sqrt1(4);
	if(y == 2) {

	}
}
en la rama condicional abrimos un while:
Código:
void main() {
	int y = swi_sqrt1(4);
	if(y == 2) {
		while(1) {

		}
	}
}
escribimos blanco en la VRAM:
Código:
void main() {
	int y = swi_sqrt1(4);
	if(y == 2) {
		while(1) {
			((unsigned short*)0x06000000)[x] = 0xFFFF;
		}
	}
}
y aumentamos x:
Código:
void main() {
	int y = swi_sqrt1(4);
	if(y == 2) {
		while(1) {
			((unsigned short*)0x06000000)[x] = 0xFFFF;
			x += 1;
		}
	}
}
ahora tenemos una pantalla blanca.



Les presento a OpenEngine-GBA, un proyecto de codigo libre que se viene cociendo desde hace bastante y permite la implementacion de un codigo mucho mas limpio, y esta escrito, hasta el dia de la fecha, 19/3/18, enteramente por mi. esta dividido en 3 partes, las cuales son:

  1. OpenEngine.h : se encarga de gestionar variables de sistema, funciones fisicas y regiones de control. todas las variables, regiones y demas, que sean usadas por esta o alguna de las otras 2 partes se encuentran aqui.
  2. OpenInput.h : gestiona las funciones de entrada de la GBA. las funciones aqui definidas son usadas para conocer la respuesta del jugador.
  3. OpenGraphics.h : gestiona la VRAM, y escribe sobre ella. tambien gestiona los modos de la pantalla

pueden encontrarlo aquí

en poco tiempo, explicare como utilizar cada componente por separado y explicare las nuevas funciones que sean añadidas

volví!
les mostrare brevemente como usar cada función:
carga las variables para que las demas funciones anden.
uso:
Código:
key_poll();

TRUE si una tecla acaba de empezar a pulsarse y FALSE en caso contrario.
uso:
Código:
if (wasKeyPressed(KEY_CODE)) {
        //codigo
}
para saber que puede ser KEY_CODE, ver CODIGOS

TRUE si una tecla acaba de ser soltada y FALSE en caso contrario.
uso:
Código:
if (wasKeyReleased(KEY_CODE)) {
        //codigo
}
para saber que puede ser KEY_CODE, ver CODIGOS
TRUE si una tecla esta siendo pulsada y FALSE en caso contrario.
uso:
Código:
if (getKeyState(KEY_CODE)) {
        //codigo
}
para saber que puede ser KEY_CODE, ver CODIGOS.

devuelve 1 si es pulsada la tecla DERECHA y -1 si es pulsada la tecla IZQUIERDA
uso:
Código:
x += key_tri_h();

devuelve 1 si es pulsada la tecla ARRIBA y -1 si es pulsada la tecla ABAJO
uso:
Código:
y += key_tri_v();

devuelve 1 si es pulsada la tecla R y -1 si es pulsada la tecla L
uso:
Código:
s += key_tri_s();

#define KEY_A 0x0001
#define KEY_B 0x0002
#define KEY_SELECT 0x0004
#define KEY_START 0x0008
#define KEY_RIGHT 0x0010
#define KEY_LEFT 0x0020
#define KEY_UP 0x0040
#define KEY_DOWN 0x0080
#define KEY_R 0x0100
#define KEY_L 0x0200

eso es todo, hasta el proximo capitulo.

agradecimientos
a @Kaiser de Emperana por ayudarme con mis miles de dudas
 
Última edición:

MichaKing

Grafista avanzado
Respuesta: [GBA]|Creacion de juegos en GBA

Un tutorial muy bueno para todos aquellos que quieren adentrarse en la programación (Obviamente no es mi caso, pero quién sabe que me deparará el futuro). Si te soy sincero, apenas pude comprender lo que explicaste (No digo que este mal redactado ni mucho menos), pero sería muy bueno que gracias a eso se pudieran crear herramientas para Roms Españolas, ya que como dije en un post anterior, daría ese sentido de pertenencia. Por cierto, no soy un genio ni nada, pero el titulo no tiene nada que ver con el contenido xD

-¡Saludos! :D
 

Kaiser de Emperana

Called in hand
Re: Respuesta: [GBA]|Creacion de juegos en GBA

Un tutorial muy bueno para todos aquellos que quieren adentrarse en la programación (Obviamente no es mi caso, pero quién sabe que me deparará el futuro). Si te soy sincero, apenas pude comprender lo que explicaste (No digo que este mal redactado ni mucho menos), pero sería muy bueno que gracias a eso se pudieran crear herramientas para Roms Españolas, ya que como dije en un post anterior, daría ese sentido de pertenencia. Por cierto, no soy un genio ni nada, pero el titulo no tiene nada que ver con el contenido xD

-¡Saludos! :D
El título está perfecto, el que se confundió sos vos. Este es un tutorial para programar juegos de gba, no herramientas para hackearlo.

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

Sobre el tuto. Está bien. Las primeras dos secciones son meros primeros encuentros con la consola, no hay mucho que decir. Y principalmente la segunda, no es que se pueda sacarmucho de ella...
Pero la tercera ya me gustó más, está bien explicada, y si bien no es nada raro, para alguien que no tengo mucha idea de los registros de I/O, le viene bien. Y es algo que todo juego va a necesitar.

A ver si alguien se anima a hacer un pacman aunque sea :p

EDIT: me olvidé de ponerlo. No te hace falta armips para esto (lopodrías usar, pero no tiene mucho caso...). Y cygwin depende del makefile, pero de todas formas, mejor usarlo (la línea de comandos de windows es horrible).
 
Última edición:

Chandragupta

~the lunatic is in my head~
Respuesta: [GBA]|Como crear programas de GBA

Lo entendí.
Fue genial la parte de las teclas, su programación es tan sencilla y portátil!!.
Aunque me da curiosidad, modo 3?. Que es eso?, entiendo que es Background 2 pero no se que es lo de "modo" (tiene que ver con la pantalla?).

Gracias por el tutorial.
Me haré un programa ahora mismo jeje.

I see you later!!.
 

Naren Jr.

Puto amo
Usuario de Platino
Respuesta: [GBA]|Como crear programas de GBA

Muy bonito el tuto y todo, pero el titulo esta mal interpretado.

Debería ser como montar o desarrollar un juego GBA.

Ya te di mi opinión por discord, es muy interesante y todo.

Pero que sentido tiene armar todo un juego, a duras penas terminamos los hacks que hacemos, eso si tomando en cuenta que muchos sistemas ya estan hechos.

Creo que deberías enfocar todo ese conocimiento a editar rutinas nativas propias de los juegos GBA, veasé el caminar diagonal, si se puede en GBA pero es complicada por que el juego solo te lee cuatro direcciones, no mas de ahí.

Sería bueno que enfoques bien eso que quieres plasmar, btw edita el titulo.

Un saludo
 

L!no

GBA Developer
Respuesta: [GBA]|Como crear juegos de GBA

hey!
les presento la primera demo del engine, que si bien no es la primera, es la mas pulida.



tiene varias funciones interesantes, explicadas debajo:

PAUSA:
al tocar START, se activa o desactiva la pausa.

PAUSA DESACTIVADA:
D-PAD: mueve el sprite.
L/R: reduce/aumenta el tile inicial
A: pone el sprite en modo 4bpp..
B: pone el sprite en modo 8bpp.

PAUSA ACTIVADA:
D-PAD: flips hacia la direccion pulsada.

Link:
sprite-demo.gba
 
Última edición:

Omega

For endless fight
Miembro del equipo
Administrador
Respuesta: [GBA]|Como crear juegos de GBA

Que curioso, no sabia nada sobre hacer juegos de Gba desde 0.
Sin duda esto en las manos correctas es una gran oportunidad para aprovechar todo el potencial de la gba, pero comparto la opinión de Naren, apenas y podemos con una ROM de base, la "Demo" es cuanto menos interesante y muestra que el tutorial funciona xD

Saludos!!!
 

Cheve

MoonLover~
Miembro de honor
Respuesta: [GBA]|Como crear juegos de GBA

Muy interesante, no recuerdo haber visto éste tutorial antes.
Se pueden aprender cosas sobre el desarrollo de juegos para consolas :

Pd: también hay kits para desarrollar juegos de DS y debería haber de plataformas más nuevas también
 

L!no

GBA Developer
Respuesta: [GBA]|Como crear juegos de GBA

Muy interesante, no recuerdo haber visto éste tutorial antes.
Se pueden aprender cosas sobre el desarrollo de juegos para consolas :

Pd: también hay kits para desarrollar juegos de DS y debería haber de plataformas más nuevas también
si, he intentado desarrollar al menos un hello world en ds, pero no he encontrado tutos. (ademas que el emu de ds me va fatal)

Enviado desde mi SM-G531M mediante Tapatalk
 

GigaPikachu

Usuario de platino
Hay un motor para hacer juegos de Game Boy / Color. Me encantaría que en la vercion 4.0 incluyan esto en su herramienta para poder hacer juegos de Game Boy Advance y terminar la línea de motores de juegos Game Boy.

Podessubir el engine de nuevo?
 
Última edición:

Daiki_

Let the voice of love take you higher
hey!
Hoy les traigo un tutorial sobre el desarrollo de programas de gba desde 0, para eso vamos a necesitar:
  1. Cygwin
  2. DevkitARM
  3. Notepad++
Para compilar tu primer programa deberás copiar el directorio "template" dentro de C:\devkitPro\examples\gba\ a otra ubicación en tu disco duro. Es conveniente que esa ubicación sea cercana a la raíz de tu disco duro y que los nombres de directorio no tengan espacios o caracteres especiales. Un ejemplo apropiado sería: d:\gba\.

La carpeta template contiene lo siguiente:

+ Carpeta source. Contiene el código fuente de tu programa.
+ Archivo makefile. Archivo de texto que contiene las instrucciones necesarias para integrar tu ejecutable GBA.

Abre el archivo template.c en tu Notepad++, verás el siguiente código:
Código:
#include <gba_console.h>

#include <gba_video.h>

#include <gba_interrupt.h>

#include <gba_systemcalls.h>

#include <gba_input.h>

#include <stdio.h>

#include <stdlib.h>



//---------------------------------------------------------------------------------

// Program entry point

//---------------------------------------------------------------------------------

int main(void) {

//---------------------------------------------------------------------------------





   // the vblank interrupt must be enabled for VBlankIntrWait() to work

   // since the default dispatcher handles the bios flags no vblank handler

   // is required

   irqInit();

   irqEnable(IRQ_VBLANK);



   consoleDemoInit();



   // ansi escape sequence to set print co-ordinates

   // /x1b[line;columnH

   iprintf("\x1b[10;10HHello World!\n");



   while (1) {

      VBlankIntrWait();

   }

}
La primera sección son las inclusiones de librerías para controlar el hardware del GBA. Posteriormente se inicializan las interrupciones hardware del GBA con irqInit() y en este caso se prepara la interrupción de refresco de video, pero no se usará.

Después se inicializa la pantalla del GBA para que opere como consola de texto ASCII con la función consoleDemoInit(). Al haber inicializado la pantalla ya se puede usar la función iprintf() para enviar cadenas de texto a pantalla. Esta función opera con secuencias de escape ANSI para mover el cursor o colocar texto en lugares específicos.

Por último el programa entra a un ciclo infinito.

Compilando el hello world.

Esta parte es facil, solo tienen que abrir Cygwin y escribir lo siguiente:
Código:
cd ruta/a/tu/proyecto
(recuerden usar las barras invertidas(/) o no funcionara)
y luego escribir lo siguiente:
Código:
make
si todo salio bien, se habra creado una rom .gba, que cuando lo habramos mostrara lo siguiente:
http://prntscr.com/h6bv8v
felicidades, compilaron su primer programa de gba!
fuentes consultadas:
devkitpro.org

ahora vamos a tomar el archivo template.c, y borrar su contenido para que contenga esto:
Código:
//codigo de ejemplo sacado de los tutoriales de tonc
int main() {
    *(unsigned int*)0x04000000 = 0x0403;

    ((unsigned short*)0x06000000)[120+80*240] = 0x001F;
    ((unsigned short*)0x06000000)[136+80*240] = 0x03E0;
    ((unsigned short*)0x06000000)[120+96*240] = 0x7C00;

    while(1);

    return 0;
}
no te preocupes si no lo entiendes, es informacion muy cruda y directa, y no esta hecho para ser muy comprensible. algo mejor es esto:
Código:
//codigo de tonc, adaptdo por L!no
#define VRAM 0x06000000 //definimos VRAM como 0x06000000, la seccion de la memoria correspondiente a la VRAM
#define IO 0x04000000 //definimos IO como 0x04000000, la seccion de la memoria correspondiente a la IO
#define CLR_RED     0x001F //definimos los colores rojo, lima,amarillo y azul
#define CLR_LIME    0x03E0
#define CLR_YELLOW  0x03FF
#define CLR_BLUE    0x7C00

void setmode3() { //definimos la funcion setmode3
    *(unsigned int*)IO = 0x0403; //iniciamos la pantalla en modo 3, y activamos el background 2
}

void writepoint(int x, int y, int color) { //definimos la funcion writepoint
    ((unsigned short*)VRAM)[x+y*240] = color; //escribimos un punto en la VRAM con las cordenadas y el color indicados
}
  
int main()
{
    setmode3(); //llamamos a setmode3
    writepoint(120, 80, CLR_RED); //llamamos a writepoint, indicando la posicion x, la posicion y, y el color
    writepoint(136, 80, CLR_LIME);
    writepoint(136, 96, CLR_YELLOW);
    writepoint(120, 96, CLR_BLUE);

    while(1); //bucle infinito para evitar errores extraños

    return 0;
}
es lo mismo que el otro, pero mas comprensible y mas facil de modificar y entender.
conceptos:
  • Si nosotros escribimos 0x0403 en la direccion 0x04000000 se activa el mode 3 y el background 2
  • para la posicion x e y se usa esto: [x+y*240]
si nosotros lo compilamos nos sale esto en el caso del 1er codigo:
http://prntscr.com/h6c5yl
y esto en el caso del segundo:
http://prntscr.com/h6c6a8
fuentes consultadas:
http://www.coranac.com/tonc

Hoy les traigo un capitulo muy importante: la deteccion de botones. La GBA tiene 10 botones, los cuales son:
    1. A
    2. B
    3. SELECT
    4. START
    5. DERECHA
    6. IZQUIERDA
    7. ARRIBA
    8. ABAJO
    9. R
    10. L
para checkearlos se recurre a la dirección 0x04000132, tambien conocida como REG_KEYINPUT o simplemente REG_P1.
¿como leemos esto?
Primero importamos las librerias:
Código:
#include <gba_types.h>
No se preocupen por esto, son solo definciones para numeros.
Luego definimos la región de la IO correspondiente a REG_KEYINPUT:
Código:
#define REG_KEYINPUT (* (volatile u16*) 0x4000130)
lo definimos como volatile ya que es una región cambiante y como u16 (un tipo de numero importado en la primera linea) porque es un registro de 16 bits.
Despues definimos las mascaras:
Código:
#define KEY_A 0x0001
#define KEY_B 0x0002
#define KEY_SELECT 0x0004
#define KEY_START 0x0008
#define KEY_RIGHT 0x0010
#define KEY_LEFT 0x0020
#define KEY_UP 0x0040
#define KEY_DOWN 0x0080
#define KEY_R 0x0100
#define KEY_L 0x0200
aunque no concuerden con los numeros dichos mas arriba, es una mascara cuy bit activado concuerda con el numero de boton.
Aunque suene antiintuitivo, cuando una tecla esta siendo presionada, su registro esta en 0, mientras que cuando no lo esta, su registro esta en 1. eso quiere decir que cuando la tecla START esta siendo presionada, el registro se parece a esto:
Código:
REGISTRO:  ???? ??11 1111 0111
Le aplicamos una operacion OR con la mascara general para poner los bits que no conocemos en 1.
Código:
INPUT: ???? ??11 1111 0111
FLAG : 1111 1100 0000 0000
--------------------------
      1111 1111 1111 0111
Luego le aplicamos un AND con la mascara de start:
Código:
 INPUT:  1111 1111 1111 0111
START: 0000 0000 0000 1000
--------------------------
val= 0x 0000 0000 0000 0000
Eso nos da 0, si le aplicamos un NOT nos da 1: ¡La tecla fue pulsada!
ahora pongamos eso en un codigo:
Primero iniciamos una variable de 16 bits key_cur:
Código:
u16 key_cur;
Despues creamos una función key_poll:
Código:
inline void key_poll()
{
  
}
Luego metemos dentro la operacion OR:
Código:
inline void key_poll()
{
    key_cur = REG_KEYINPUT | KEY_MASK;
}
y creamos una funcion getKeyState:
Código:
u32 getKeyState(u16 key_code)
{
    return !(key_cur & key_code);
}
ahora solo falta llamar a estas funciones desde main. Creamos un bucle infinito:
Código:
void main()
{
    while(1)
    {
              
    }
}
llamamos a la funcion key_poll:
Código:
void main()
{
    while(1)
    {
        key_poll();
    }
}
luego añadimos un if:
Código:
void main()
{
    while(1)
    {
        key_poll();
        if () {
            //hacer algo
        }
    }
}
y le añadimos el getkeystate
Código:
void main()
{
    while(1)
    {
        key_poll();
        if ( getKeyState(KEY_A) ) {
            //tecla A pulsada
        }
    }
}
fuentes consultadas:
http://kylehalladay.com/blog/tutorial/gba/

hoy continuaremos con el tema de los botones. que pasa si queremos saber si una tecla esta siendo pulsada? ahí les va:
si nosotros comparamos el estado anterior de la tecla con el estado actual, podemos saber varias cosas. entre ellas, saber si una tecla acaba de ser presionada o si fue soltada en es mismo momento. eso se logra con un algoritmo tal que asi:
para saber si la tecla acaba de ser pulsada:
Código:
(NOT(tecla_anterior) OR tecla_actual) AND mascara_de_la_tecla
eso nos da 1 si la tecla acaba de ser pulsada y 0 en caso contrario.
para saber si la tecla acaba de ser soltada:
Código:
(NOT(tecla_actual) OR tecla_anterior) AND mascara_de_la_tecla
eso nos da 1 si la tecla acaba de ser soltada y 0 en caso contrario.
ahora pasemos esas ecuaciones al C:
primero definimos una variable key_prev:
Código:
u16 key_prev;
luego actualizamos la funcion key_poll, dejandola así:
Código:
void key_poll()
{
    input_prev = input_cur;
    input_cur = REG_KEYINPUT | KEY_MASK;
}
ahora definimos una funcion waskeypressed:
Código:
u16 wasKeyPressed(u16 key_code)
{

}
y dentrole añadimos el algoritmo de mas arriba:
Código:
uint16 wasKeyPressed(uint16 key_code)
{
    return (input_cur & ~input_prev) & key_code;
}
luego creamos una funcion waskeyeleased:
Código:
u16 wasKeyReleased(u16 key_code)
{

}
y le añadimos el otro algoritmo:
Código:
uint16 wasKeyReleased(uint16 key_code)
{
    return (~input_cur & input_prev) & key_code;
}
ahora ya podemos controlar con toda precision el estado de las teclas.
fuentes consultadas:
http://kylehalladay.com/blog/tutorial/gba/

Tomemos por un segundo el codigo del captulo 4, y guardemoslo como input.h . vayamos de vuelta an nuestro archivo principal, borremosle el contenido y empecemos con el tema de hoy: dibujar y mover cuadrados en pantalla.
Primero definimos la VRAM, la IO, la funcion setmode3 e importamos la libreria gba_types:
Código:
#define VRAM 0x06000000
#define IO 0x04000000

void setmode3() {
    *(unsigned int*)IO = 0x0403;
}
despues definimos una funcion main:
Código:
void main() {

}
y definimos una variable i y un bucle infinito:
Código:
int i = 0;
void main() {
    while(1) {

    }
}
y dentro le ponemos esto:
Código:
int i = 0;
void main() {
    while(1) {
        ((volatile uint16*)VRAM)[i] = 0xFFFF;
        i += 1;
    }
}
con esto deberamos tener un hermoso fondo blanco

pero a quien le gustan los fondos blancos? creemos una funcion makecol:
Código:
inline u16 MakeCol(u8 red, u8 green, u8 blue)
{
    return (red & 0x1F) | (green & 0x1F) << 5 | (blue & 0x1F) << 10;
}
no voy a profundizar en esto, eso ya lo haremos en otro capitulo.
luego definimos una funcion drawline:
Código:
void drawRect(int left, int top, int width, int height, u16 clr)
{

}
le insertamos un bucle for:
Código:
void drawRect(int left, int top, int width, int height, u16 clr)
{
    for (int y = 0; y < height; ++y)
    {
        //lo que este aqui se repetira hasta que se alcanze el limite heigth
    }
}
y le añadimos otro bucle for dentro:
Código:
void drawRect(int left, int top, int width, int height, u16 clr)
{
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
           //esto se repetira hasta que alcanzemos el limite width
        }
    }
}
y le añadimos la funcion principal:
Código:
void drawRect(int left, int top, int width, int height, u16 clr)
{
    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
           VRAM[(top + y) * 240 + left + x] = clr;
        }
    }
}
con eso escribimos nuestro cuadrado/rectangulo/paraboloide en la VRAM.

para regular los fps de la consola y hacer que no mueva mas de los que soporta la pantalla, creamos esta funcion:
Código:
#define REG_VCOUNT      (* (volatile u16*) 0x04000006)

inline void vsync()
{
  while (REG_VCOUNT >= 160);
  while (REG_VCOUNT < 160);
}
no voy a explicarla, no es dificil de comprender.
ahora actualizemos la funcion main:
primero borramos su contenido y llamamos a setmode3:
Código:
setmode3();
luego abrimos un bucle for:
Código:
    for ()
    {

    }
y le añadimos las condiciones:
Código:
    for (int i = 0; i < 240 * 160; ++i)
    {
      
    }
y la rama condicional:
Código:
    for (int i = 0; i < 240 * SCREEN_H; ++i)
    {
        SCREENBUFFER[i] = MakeCol(0,0,0);
    }
es basicamente el while(1) de antes, solo que llena la pantalla de negro y cuando termina para.
luego definimos la variable x:
Código:
int x = 0;
y abrimos un bucle infinito:
Código:
    while(1)
    {

    }
dentro llamamos a la funcion vsync:
Código:
    while(1)
    {
        vsync();
    }
llamamos a la funcion drawrect:
Código:
    while(1)
    {
        vsync();
        drawRect(x % 240, (x / 240) * 10, 10, 10,MakeCol(31,31,31));
    }
y aumentamos el valor de x:
Código:
    while(1)
    {
        vsync();
        drawRect(x % 240, (x / 240) * 10, 10, 10,MakeCol(31,31,31));
        x += 10;
    }
por ultimo, añadimos un return 0 luego del bucle:
Código:
     return 0;
ahora tenemos una pantalla negra que se va llenando de blanco 10 pixels a la vez.

pero eso termina y se queda ahí. vamos a cambiar eso:
primero vamos a main y creamos un if despues de la llamada a vsync:
Código:
if ()
colocamos la condicion:
Código:
if ( x > 240 * (160/10))
como tiene una rama condicional de una sola linea, lo ponemos seguido:
Código:
if ( x > 240 * (SCREEN_H/10)) x = 0;
luego abrimos una variable last:
Código:
int last = x - 10;
y llamamos a la funcion drawrect:
Código:
drawRect(last % 240, (last / 240 * 10, 10, 10,MakeCol(0,0,0));
ahora tenemos un cuadrado de 10 por 10 viajando por la pantalla de nuestra GBA (o nuestro emulador).

hoy vamos a tomar nuestro cuadrado de 10 x 10 y lo convertiremos en un juego. o al menos un boceto de uno.
primero incluimos la libreria input.h de antes:
Código:
#include "input.h"
despues definimos SCREEN_W y SCREEN_H:
Código:
#define SCREEN_W            240
#define SCREEN_H            160
luego eliminaremos esto:
Código:
int last = x - 10;
e iremos arriba para definir last:
Código:
int last;
vamos de bajo de vsync() y llamamos a key_poll:
Código:
key_poll();
borramos esto:
Código:
drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10,MakeCol(31,31,31));
    x += 10;
ahora abran un multibucle if/else if (no me molesten, me gusta decirles asi):
Código:
    if () {

    } else if () {

    } else if () {

    } else if () {

    }
eso significa que si lo primero da falso, controla lo segundo, si el segundo da falso, controla el tercero, etc.
le añadimos condiciones getkeypressed:
Código:
    if (getKeyState()) {

    } else if (getKeyState()) {

    } else if (getKeyState()) {

    } else if (getKeyState()) {

    }
y le añadimos las teclas:
Código:
    if (getKeyState(KEY_RIGHT)) {

    } else if (getKeyState(KEY_LEFT)) {

    } else if (getKeyState(KEY_DOWN)) {

    } else if (getKeyState(KEY_UP)) {

    }
ya podemos empezar.
Derecha:
esta es simple. le dcimos que dibuje al cuadrado en x:
Código:
    if (getKeyState(KEY_RIGHT)) {
        drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
    }
aumentamos x:
Código:
    if (getKeyState(KEY_RIGHT)) {
        drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
        x += 10;
    }
y restamos last:
Código:
    if (getKeyState(KEY_RIGHT)) {
        drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
        x += 10;
        last = x - 10;
    }
ahora el cuadrado va para la derecha al tocar dicha tecla.
Izquierda:
la izquierda es lo mismo pero al reves:
dibujamos el cuadrado:
Código:
    } else if (getKeyState(KEY_LEFT)) {
        drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
    }
restamos x:
Código:
    } else if (getKeyState(KEY_LEFT)) {
        drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
        x -= 10;
    }
y sumamos last:
Código:
    } else if (getKeyState(KEY_LEFT)) {
        drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
        x -= 10;
        last = x + 10;
    }
ahora el cuadrado va a la izquierda.
Abajo:
ese es mas dificil. Primero escribimos esto:
Código:
    } else if (getKeyState(KEY_DOWN)) {
        if (x > SCREEN_W * 15) {
            last = x;
            x = x - SCREEN_W * (15);
        }
    }
esto hace que volvamos arriba cundo lleguemos abajo.
abrimos un bucle else:
Código:
    } else if (getKeyState(KEY_DOWN)) {
        if (x > SCREEN_W * 15) {
            last = x;
            x = x - SCREEN_W * (15);
        } else {

        }
    }
dibujamos el cuadrado:
Código:
    } else if (getKeyState(KEY_DOWN)) {
        if (x > SCREEN_W * 15) {
            last = x;
            x = x - SCREEN_W * (15);
        } else {
            drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
        }
    }
definimos el valor de last:
Código:
    } else if (getKeyState(KEY_DOWN)) {
        if (x > SCREEN_W * 15) {
            last = x;
            x = x - SCREEN_W * (15);
        } else {
            drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
            last = x;
        }
    }
y aumentamos x:
Código:
    } else if (getKeyState(KEY_DOWN)) {
        if (x > SCREEN_W * 15) {
            last = x;
            x = x - SCREEN_W * (15);
        } else {
            drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
            last = x;
            x += 240;
        }
    }
ahora nos movemos hacia abajo.
Arriba
escribimos esto:
Código:
    } else if (getKeyState(KEY_UP)) {
        if (x < 160) {
            last = x;
            x = x + SCREEN_W * (15);
        }
    }
dibujamos el cuadrado:
Código:
    } else if (getKeyState(KEY_UP)) {
        if (x < 160) {
            last = x;
            x = x + SCREEN_W * (15);
        } else {
            drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
        }
    }
definimos el valor de last:
Código:
    } else if (getKeyState(KEY_UP)) {
        if (x < 160) {
            last = x;
            x = x + SCREEN_W * (15);
        } else {
            drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
            last = x;
        }
    }
y restamos x:
Código:
    } else if (getKeyState(KEY_UP)) {
        if (x < 160) {
            last = x;
            x = x + SCREEN_W * (15);
        } else {
            drawRect(x % SCREEN_W, (x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,31));
            last = x;
            x -= 240;
        }
    }
solo falta solucionar bugs:
añadimos esto en el bucle de main:
Código:
    if (x < 0) {
        x = SCREEN_W * (SCREEN_H/10);
        drawRect(10 % SCREEN_W, (last / SCREEN_W) * 10, 10, 10,MakeCol (0,0,0));
        drawRect(last % SCREEN_W, (0 / SCREEN_W) * 10, 10, 10,MakeCol (0,0,0));
    }
y listo!

lo primero que haremos sera crear y definir el valor de una variable puntos:
Código:
int points;
una variable foodinscreen:
Código:
int foodinscreen = 0;
y una variable food_x:
Código:
int food_x;
ahora definimos una funcion drawfood:
Código:
void drawfood() {

}
le añadimos un if:
Código:
void drawfood() {
    if () {

    }
}
su condicion:
Código:
void drawfood() {
    if (!(foodinscreen)) {

    }
}
y escribimos en la rama condicional lo siguiente:
la definicion del valor de food_x:
Código:
void drawfood() {
    if (!(foodinscreen)) {
        food_x =  ;
    }
}
dentro le añadimos un rand:
Código:
void drawfood() {
    if (!(foodinscreen)) {
        food_x = (rand());
    }
}
un limite para el rand:
Código:
void drawfood() {
    if (!(foodinscreen)) {
        food_x = (rand()%(25*16));
    }
}
y una multiplicacion por 10:
Código:
void drawfood() {
    if (!(foodinscreen)) {
        food_x = (rand()%(25*16)) * 10;
    }
}
acabamos de settear el valor de food_x en un numero al azar en los limites de la pantalla multiplo de 10.
llamamos a drawrect:
Código:
void drawfood() {
    if (!(foodinscreen)) {
        food_x = (rand()%(25*16)) * 10;
        drawRect(food_x % SCREEN_W, (food_x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,0));
    }
}
ahora dibujara un cuadrado amarillo en food_x.
por ultimo activamos foodinscreen:
Código:
void drawfood() {
    if (!(foodinscreen)) {
        food_x = (rand()%(25*16)) * 10;
        drawRect(food_x % SCREEN_W, (food_x / SCREEN_W) * 10, 10, 10, MakeCol(31,31,0));
        foodinscreen = 1;
    }
}
ahora definimos una funcion checkfood:
Código:
void checkfood() {

}
le añadimos un if:
Código:
void checkfood() {
    if () {
      
    }
}
y le añadimos la condicion:
Código:
void checkfood() {
    if (food_x == x) {
        //lo que este aqui se ejecutara si el cuadrado esta sobre la comida
    }
}
dentro borramos la comida:
Código:
void checkfood() {
    if (food_x == x) {
        drawRect(food_x % SCREEN_W, (food_x / SCREEN_W) * 10, 10, 10, MakeCol(0,0,0));
    }
}
sumamos 1 a la variable points:
Código:
void checkfood() {
    if (food_x == x) {
        drawRect(food_x % SCREEN_W, (food_x / SCREEN_W) * 10, 10, 10, MakeCol(0,0,0));
        points += 1;
    }
}
y desactivamos foodinscreen:
Código:
void checkfood() {
    if (food_x == x) {
        drawRect(food_x % SCREEN_W, (food_x / SCREEN_W) * 10, 10, 10, MakeCol(0,0,0));
        points += 1;
        foodinscreen = 0;
    }
}
ahora solo añadan esto debajo de key_poll():
Código:
    drawfood();
    checkfood();
Listo! ahora ya hay puntos.
Fuentes:
todo esto fue programado por mi.
La GBA, ademas de las interrupciones por hardware tiene interrupciones por software, que son llamadas mediante el comando de assembly (a.k.a ASM) SWI, que significa software interrupt, y usan como parametos de Entrada/salida los registros del r0 al r3. y me diran cosas como:
¡Lino, no puedes usar ASM en un archivo .c!
y yo les respondere algo como:
claro que se puede, mira lo que sigue:
si nosotros escribimos esto:
Código:
int ASM()
{   asm("");   }
ejecutara la instruccion ASM que pongas dentro de las comillas
ahora vamos a lo bueno:
si saben assembly avanzado, seguro se saben lo que sigue. pero como no hay mucha gente que conozca ASM, ni que hablar de avanzado, explico esto. la funcion SWI recibe 1 parametro:
Código:
SWI 0x[codigo de interrupcion]
el cual puede ser cualquiera de esta lista:
Código:
0x00     SoftReset         0x08     Sqrt
0x01     RegisterRamReset        0x09     ArcTan
0x02     Halt         0x0A     ArcTan2
0x03     Stop         0x0B     CPUSet
0x04     IntrWait         0x0C     CPUFastSet
0x05     VBlankIntrWait         0x0D     BiosChecksum
0x06     Div         0x0E     BgAffineSet
0x07     DivArm         0x0F     ObjAffineSet

0x10     BitUnPack         0x18     Diff16bitUnFilter
0x11     LZ77UnCompWRAM         0x19     SoundBiasChange
0x12     LZ77UnCompVRAM         0x1A     SoundDriverInit
0x13     HuffUnComp         0x1B     SoundDriverMode
0x14     RLUnCompWRAM         0x1C     SoundDriverMain
0x15     RLUnCompVRAM         0x1D     SoundDriverVSync
0x16     Diff8bitUnFilterWRAM         0x1E     SoundChannelClear
0x17     Diff8bitUnFilterVRAM         0x1F     MIDIKey2Freq

0x20     MusicPlayerOpen         0x28     SoundDriverVSyncOff
0x21     MusicPlayerStart         0x29     SoundDriverVSyncOn
0x22     MusicPlayerStop         0x2A     GetJumpList
0x23     MusicPlayerContinue
0x24     MusicPlayerFadeOut
0x25     MultiBoot
0x26     HardReset
0x27     CustomHalt
Definiciones de algunas:
0x06: Div
entrada:

r0: numerador

r1: denominador
salida:

r0: numerador / denominador

r1: numerador % denominador

r3: abs(numerador / denominador)
Nota: NO dividas por 0!
0x08: Sqrt
Entrada:

r0: num, un entero sin signo de 32 bits
salida:
r1: sqrt(num)
0x0a: ArcTan2
entrada:

r0: x, un numero con signo de 16 bits (s16)

r1: y, un numero con signo de 16 bits (s16)
salida:

r0: x≥0 : θ= arctan(y/x) ∨ x<0 : θ= sign(y)*(π − arctan(|y/x|).
esto da la inversa total de y = x*tan(θ). El problema con la tangente es que el dominio es un semicírculo, como lo es el rango de arctan. para conseguir el rango del circulo completo, los valores x e y son necesarios no solo para su cociente, sino también para sus signos. el rango matematico de θ es [−π, π⟩, que corresponde a [−0x8000, 0x8000⟩ (o [0, 2π⟩ y [0, 0xFFFF] si prefieres)

para llamar a cualquiera de estas funciones, usamos lo siguiente:
Código:
int funcion(int parametros) {
    asm("swi 0x");
}

guardemos el codigo anterior como game.c y borremos el contenido de template.c . luego haremos esto:
definimos una variable x:
Código:
int x;
y creamos una funcion swi_sqrt:
Código:
int swi_sqrt() {

}
le añadimos el parametro num:
Código:
int swi_sqrt(int num) {

}
y la instruccion:
Código:
int swi_sqrt(int num) {
    asm("swi 0x08");
}
ahora definimos main:
Código:
void main() {

}
dentro llamamos a sqrt con 4 como parametro:
Código:
void main() {
    int y = swi_sqrt1(4);
}
abrmos un if:
Código:
void main() {
    int y = swi_sqrt1(4);
    if() {

    }
}
le añadimos la condicion:
Código:
void main() {
    int y = swi_sqrt1(4);
    if(y == 2) {

    }
}
en la rama condicional abrimos un while:
Código:
void main() {
    int y = swi_sqrt1(4);
    if(y == 2) {
        while(1) {

        }
    }
}
escribimos blanco en la VRAM:
Código:
void main() {
    int y = swi_sqrt1(4);
    if(y == 2) {
        while(1) {
            ((unsigned short*)0x06000000)[x] = 0xFFFF;
        }
    }
}
y aumentamos x:
Código:
void main() {
    int y = swi_sqrt1(4);
    if(y == 2) {
        while(1) {
            ((unsigned short*)0x06000000)[x] = 0xFFFF;
            x += 1;
        }
    }
}
ahora tenemos una pantalla blanca.



Les presento a OpenEngine-GBA, un proyecto de codigo libre que se viene cociendo desde hace bastante y permite la implementacion de un codigo mucho mas limpio, y esta escrito, hasta el dia de la fecha, 19/3/18, enteramente por mi. esta dividido en 3 partes, las cuales son:

  1. OpenEngine.h : se encarga de gestionar variables de sistema, funciones fisicas y regiones de control. todas las variables, regiones y demas, que sean usadas por esta o alguna de las otras 2 partes se encuentran aqui.
  2. OpenInput.h : gestiona las funciones de entrada de la GBA. las funciones aqui definidas son usadas para conocer la respuesta del jugador.
  3. OpenGraphics.h : gestiona la VRAM, y escribe sobre ella. tambien gestiona los modos de la pantalla

pueden encontrarlo aquí

en poco tiempo, explicare como utilizar cada componente por separado y explicare las nuevas funciones que sean añadidas

volví!
les mostrare brevemente como usar cada función:
carga las variables para que las demas funciones anden.
uso:
Código:
key_poll();

TRUE si una tecla acaba de empezar a pulsarse y FALSE en caso contrario.
uso:
Código:
if (wasKeyPressed(KEY_CODE)) {
        //codigo
}
para saber que puede ser KEY_CODE, ver CODIGOS

TRUE si una tecla acaba de ser soltada y FALSE en caso contrario.
uso:
Código:
if (wasKeyReleased(KEY_CODE)) {
        //codigo
}
para saber que puede ser KEY_CODE, ver CODIGOS
TRUE si una tecla esta siendo pulsada y FALSE en caso contrario.
uso:
Código:
if (getKeyState(KEY_CODE)) {
        //codigo
}
para saber que puede ser KEY_CODE, ver CODIGOS.

devuelve 1 si es pulsada la tecla DERECHA y -1 si es pulsada la tecla IZQUIERDA
uso:
Código:
x += key_tri_h();

devuelve 1 si es pulsada la tecla ARRIBA y -1 si es pulsada la tecla ABAJO
uso:
Código:
y += key_tri_v();

devuelve 1 si es pulsada la tecla R y -1 si es pulsada la tecla L
uso:
Código:
s += key_tri_s();

#define KEY_A 0x0001
#define KEY_B 0x0002
#define KEY_SELECT 0x0004
#define KEY_START 0x0008
#define KEY_RIGHT 0x0010
#define KEY_LEFT 0x0020
#define KEY_UP 0x0040
#define KEY_DOWN 0x0080
#define KEY_R 0x0100
#define KEY_L 0x0200

eso es todo, hasta el proximo capitulo.

agradecimientos
a @Kaiser de Emperana por ayudarme con mis miles de dudas
Cancelado Pokémon Searcher, voy a hacer un Metroidvania.
Hablando en serio, recomiendo de antemano seguir este tutorial si estás interesado en crear tu proyecto desde cero para la GBA, recomiendo complementarlo con un el tutorial de C orientado a decomp que hay en el foro (notengolink).
Honestamente, me gustaría una 2da parte de esto, sería muy interesante.

PD: Resube el OpenEngineGBA
 
Arriba