Es recomendable la lectura de los artÃculos anteriores para comprender mejor el actual:
- GameBoy Dev00
- GameBoy Dev01
- GameBoy Dev02
- GameBoy Dev03
- GameBoy Dev04
- GameBoy Dev05
- GameBoy Dev06
- GameBoy Dev07
- GameBoy Dev08
- GameBoy Dev09
- GameBoy Dev10
En el artÃculo anterior aprendimos a utilizar los bancos de memoria ROM/RAM del cartucho, en este caso utilizaremos dichos conocimientos para guardar el nombre de nuestro usuario, básicamente es el teclado del artÃculo GameBoy Dev08 pero con esta funcionalidad añadida.
Las modificaciones realizadas consisten en declarar como extern la variable que contendrá el nombre del usuario:
extern unsigned char playernamemap[18];
Crear una función que pondrá un fondo determinado en caso de detectar un nombre de usuario guardado en la RAM del cartucho:
void initialize_welcome_display(){
DISPLAY_OFF;
HIDE_SPRITES;
HIDE_BKG;
set_bkg_data(0, 45, keyboarddata);
set_bkg_tiles(0, 0, 20, 18, welcomemap);
SHOW_BKG;
DISPLAY_ON;
}
Función auxiliar de reseteo a 0x00 de los carácteres del nombre de usuario, esto es necesario porque las variables de la RAM del cartucho por defecto tienen el valor 0xFF, cuando editamos el nombre este contendrá los carácteres asignados mas el resto de valores por defecto 0xFF hasta llegar a 18 chars.
Por ejemplo si estamos escribiendo KR0M y vamos por la segunda letra playernamemap tendrá el siguiente valor:
KR"0xFF"*16
El resultado en pantalla es el siguiente:
Para evitar esto llamaremos a la función resetcharactername antes de empezar a editar el nombre de usuario:
void resetcharactername(){
for(i=0;i<=18;i++){
playernamemap[i] = 0x00;
}
}
De este modo la edición anterior quedarÃa del siguiente modo:
KR"0x00"*16
Esta solución implica un problema y es que si arrancamos nuestro juego por primera vez cuando llegamos al teclado de edición playernamemap contendrá el valor 0x00 en todos sus carácteres y si apagamos la consola sin introducir ningún nombre playernamemap queda con el valor 0x00 en todos sus carácteres. Cuando volvamos a arrancar la consola se leerán estos valores y se mostrará un nombre ilegal.
Por lo tanto debemos hacer un doble check para saber si el nombre de usuario fué guardado con anterioridad donde comprobamos si la variable tiene el valor por defecto 0xFF o si tiene el valor reseteado 0x00:
for(i=0;i<=18;i++){
// Uninitialized value: 0xFF OR Resetted value: 0x00
if (playernamemap[i] == 0xFF || playernamemap[i] == 0x00){
empty_playernamemap = 1;
}
else{
empty_playernamemap = 0;
break;
}
}
El contenido del banco de memoria simplemente es la definición de la variable playernamemap:
#include <gb/gb.h>
unsigned char playernamemap[18];
Dejo el fichero welcomemap.c por si queréis hacer pruebas sin tener que generarlo por vosotros mismos, el resto de ficheros son los mismos que en el artÃculo sobre el teclado.
El código principal serÃa el siguiente:
#include <gb/gb.h>
// Keyboard tiles data and tiles screen mapping
#include "keyboarddata.c"
#include "keyboardmap.c"
#include "welcomemap.c"
// Cursor tiles data
#include "cursordata.c"
// Code cursor C structure for cursor location info
#include "cursor.c"
// GameBoy screen of 18 tiles width so max name length is 18
// Only one line allowed for player name
//unsigned char playernamemap[18];
//extern UINT8 playernamemap[18];
extern unsigned char playernamemap[18];
struct Cursor cursor;
// Allowed cursor positions:
// All positions are moved 8,16 pixels
// And X is shifted 4 pixels
// Best way to get border positions is viewing it in tile designer
// xmin: 0*8+8+4 = 12
// ymin: (8*8)+16 = 80
// xmax: (18*8)+8+4 = 144+12 = 156
// ymax: (14*8)+16 = 112+16 = 128
// xdelete: (16*8)+8+4 = 128+12 = 140
// ydelete: (16*8)+16 = 128+16 = 144
// xdone: (18*8)+8+4 = 144+12 = 156
// ydone: (16*8)+16 = 128+16 = 144
const UINT8 mincursorx = 12;
const UINT8 mincursory = 80;
const UINT8 maxcursorx = 156;
const UINT8 maxcursory = 128;
const UINT8 xdelete = 140;
const UINT8 ydelete = 144;
const UINT8 xdone = 156;
const UINT8 ydone = 144;
UINT8 nameindex, i, empty_playernamemap;
UBYTE keydown = 0, playerhasname = 0;
UBYTE isWithinKeyboard(UINT8 x, UINT8 y){
// Cursor is inside main keyboard rectangle layout
if (x >= mincursorx && x <= maxcursorx && y >= mincursory && y <= maxcursory){
return 1;
}
// Check special locations at bottom of keyboard, DELETE and ENTER keys
if (x==xdelete && y==ydelete || x==xdone && y==ydone){
return 1;
}
// Any other position
return 0;
}
void addtoplayername(struct Cursor* cursor){
// Working with row/col we can get charsetindex of current char
// Its simple, we calculate char position knowing that each row has 10 chars
// So 0-Row: A-J, 10-Row: K-T, 20-Row: U-:, 30-Row: 0-9
// To get char number: Row*10 + Col + 1 (we add +1 because first char in tiledata is whitespace)
UINT8 charsetindex = cursor->row * 10 + cursor->col + 1;
// Max name length reached
if (nameindex == 18){
return;
}
// Update playernamemap[current_position] with new charsetindex
playernamemap[nameindex] = charsetindex;
nameindex++;
}
void removefromplayername(){
if (nameindex > 0){
// We dont want to delete current name char position
// We want to delete last char, so we go back one position before deleting char
nameindex--;
playernamemap[nameindex] = 0;
}
}
void drawplayername(){
// Print in 1,4 position, playernamemap content knowing that it is 18x1 length
set_bkg_tiles(1, 4, 18, 1, playernamemap);
}
void updateplayername(struct Cursor* cursor){
// check if cursor at delete or done
if (cursor->row == 4 && cursor->col==8){
// delete
removefromplayername();
drawplayername();
}
else if (cursor->row == 4 && cursor->col==9){
// done
// If player name is empty, force to write name
if (nameindex == 0){
return;
}
playerhasname = 1;
}
else{
addtoplayername(cursor);
drawplayername();
}
}
void inputplayername(){
while(playerhasname == 0){
// If key was pressed we wait to be released and reassign keydown to value 0
// In that way movement is not erratic
if (keydown){
waitpadup();
keydown = 0;
}
switch(joypad()){
// We check in all movements if future cursor position will be inside keyboard area
case J_UP:
if (isWithinKeyboard(cursor.x, cursor.y - 16)){
cursor.y -= 16;
// Scroll sprite 0, o positions in X axis and -16 in Y axis
scroll_sprite(0,0,-16);
// Activate keydown variable
keydown = 1;
// Adjust keyboard matrix position to know current keyboard char
cursor.row--;
}
break;
case J_DOWN:
if (isWithinKeyboard(cursor.x, cursor.y + 16)){
cursor.y += 16;
scroll_sprite(0,0,16);
keydown = 1;
cursor.row++;
}
break;
case J_LEFT:
if (isWithinKeyboard(cursor.x - 16, cursor.y)){
cursor.x -= 16;
scroll_sprite(0,-16,0);
keydown = 1;
cursor.col--;
}
break;
case J_RIGHT:
if (isWithinKeyboard(cursor.x + 16, cursor.y)){
cursor.x += 16;
scroll_sprite(0,16,0);
keydown = 1;
cursor.col++;
}
break;
case J_A:
// Each time we press A button we update player name
updateplayername(&cursor);
keydown = 1;
break;
}
}
}
void initialize_input_display(){
// By default VRAM positions not defined by keyboardmap are filled with first VRAM tile
// So we have to put blank char in first keyboarddata tile, if we dont do that we will get A char in 4pixel shift
set_bkg_data(0, 45, keyboarddata);
set_bkg_tiles(0, 0, 20, 18, keyboardmap);
// To have all numbers(0-9) in one unique row we need 10*(char+space) tiles by keyboard row
// 10*(char+space): 20 tiles, but first char doesnt have space before char
// So we hack the background shifting it 4pixels: 1/2 tile to right
// In that way we get 1/2 tile before first char and 1/2 tile after last one
scroll_bkg(-4,0);
// Load cursordata in sprite number 0 knowing that its length is one sprite
set_sprite_data(0, 1, cursordata);
// Show cursordata[0] in sprite number 0
set_sprite_tile(0, 0);
// Set initial cursor position
// Char A is located in: 00,08 tile position, in pixels -> 0,64
// But remember that screen coordenates is moved 8,16 pixels so 0,64 -> 8,80
// And we have shifted background 4 pixels to right so 8,80 -> 12,80
cursor.x = 12;
cursor.y = 80;
move_sprite(0, cursor.x, cursor.y);
// We keep track of current col and row inside keyboard layout(not screen)
// That way we can know which letter is selected
cursor.col = 0;
cursor.row = 0;
SHOW_BKG;
SHOW_SPRITES;
DISPLAY_ON;
}
void initialize_welcome_display(){
DISPLAY_OFF;
HIDE_SPRITES;
HIDE_BKG;
set_bkg_data(0, 45, keyboarddata);
set_bkg_tiles(0, 0, 20, 18, welcomemap);
SHOW_BKG;
DISPLAY_ON;
}
void resetcharactername(){
for(i=0;i<=18;i++){
playernamemap[i] = 0x00;
}
}
void main(){
while(1){
ENABLE_RAM;
SWITCH_RAM(0);
// Uninitialized RAM default value(Emulicious debugger SRAM viewer): 0xFF
// A weird behaviour showing empty username can be possible if we power on GameBoy for first time
// and power off without inputting an username, in that way our username would be (0x00*18)
// as result of resetcharactername function.
// We can check both values simultaneously because none of them is a keyboard allowed char
// so we dont have to check (0xFF)*18 nor (0x00)*18 username content, only one match with
// one of the two chars will be considered empty playername.
for(i=0;i<=18;i++){
// Uninitialized value: 0xFF OR Resetted value: 0x00
if (playernamemap[i] == 0xFF || playernamemap[i] == 0x00){
empty_playernamemap = 1;
}
else{
empty_playernamemap = 0;
break;
}
}
if(empty_playernamemap == 1){
// Uninitialized RAM is 0xFF, so while we are editing our name
// the program will show our username as (our chars + 0xFF*X)
// where X is the length to fill 18 chars username length
// So if its a new input, we first reset RAM playernamemap variable to (0x00*18)
resetcharactername();
initialize_input_display();
inputplayername();
}
else{
initialize_welcome_display();
drawplayername();
}
DISABLE_RAM;
waitpad(J_START);
}
}
El fichero de Make serÃa el siguiente:
#!/usr/local/bin/bash
~/GBDEV/gbdk/bin/lcc -Wf--vc -Wf--debug -Wf--nooverlay -Wf--nogcse -Wf--nolabelopt -Wf--noinvariant -Wf--noinduction -Wf--noloopreverse -Wf--no-peep -Wf--no-reg-params -Wf--no-peep-return -Wf--nolospre -Wf--nostdlibcall -Wa-l -Wa-y -Wl-m -Wl-w -Wl-y -Wf-ba0 -c -o bank0.o bank0.c
~/GBDEV/gbdk/bin/lcc -Wf--vc -Wf--debug -Wf--nooverlay -Wf--nogcse -Wf--nolabelopt -Wf--noinvariant -Wf--noinduction -Wf--noloopreverse -Wf--no-peep -Wf--no-reg-params -Wf--no-peep-return -Wf--nolospre -Wf--nostdlibcall -Wa-l -Wa-y -Wl-m -Wl-w -Wl-y -c -o 11.o 11.c
~/GBDEV/gbdk/bin/lcc -Wf--vc -Wf--debug -Wf--nooverlay -Wf--nogcse -Wf--nolabelopt -Wf--noinvariant -Wf--noinduction -Wf--noloopreverse -Wf--no-peep -Wf--no-reg-params -Wf--no-peep-return -Wf--nolospre -Wf--nostdlibcall -Wa-l -Wa-y -Wl-m -Wl-w -Wl-y -Wl-yt0x1B -Wl-ya1 -o 11.gb 11.o bank0.o
Y el resultado final este: