Registrarse

[Decomp-GBA] Printear Texto sin Textbox, cambiar fuente, colores de texto y sombra.

Hoennwalker

Usuario de platino
¡Muy buenas! Bueno con este código que voy a explicar vamos a conseguir printear texto tal y como se hace con msgbox normal PERO sin cargar los gráficos del textbox. También vas a aprender a cambiar la fuente del texto, su color y el color de la sombra de las letras. Espero que os pueda ayudar con vuestras ideas en interfaces.

Lo primero de todo es que doy por sentado que sabes configurar y cargar backgrounds y que además sabes organizar la interfaz en tasks. Esto lo comento porque, aunque este tutorial requiera esas bases de conocimiento, el código está comentado para que puedas comprenderlo una vez tengas esas bases.

El WindowTemplate configura los window que pueden contener un msgbox, en este caso usaremos el primero. WindowTemplate (que lo he llamado "gTextWindows") es un array que en este caso solo contiene dos elementos por mi elección, podrían ser más o incluso solo uno. Esos elementos son el 0 y el 1 (NO 1 y 2... NO) pero como se puede observar el elemento 0 del array se llama "WINDOW" y para ello lo declaramos de antemano con "enum". De esta forma, al primer elemento lo podemos llamar 0 o WINDOW, en cambio, al elemento 1 solo lo podemos llamar 1, ahora lo vemos más adelante. El segundo window no lo vamos a usar, me sirve para haber explicado lo anterior.
C:
enum {WINDOW};

static const struct WindowTemplate gTextWindows[] =
{
    [WINDOW] = {         // nombre del window
        .bg = 0,            // El bg donde se colocará el window
        .tilemapLeft = 2,   // x position
        .tilemapTop = 15,    // y position
        .width = 27,         //ancho
        .height = 4,        //alto
        .paletteNum = 15,    // Paleta que va a ocupar
        .baseBlock = 1,
    },

    {
        .bg = 1,            // El bg donde se colocará el window
        .tilemapLeft = 2,   // x position
        .tilemapTop = 10,    // y position
        .width = 27,         //ancho
        .height = 4,        //alto
        .paletteNum = 15,    // Paleta que va a ocupar
        .baseBlock = 109,
    },
}
La función de abajo, AddText, recibirá como parámetro nuestro texto como se verá más adelante. Realmente podría incluso colocar estas dos funciones (el StringExpandPlaceholders y el printer), que contiene AddText, dentro de CreateTextBox junto a esas 3, pero lo dividimos así para manejarnos mejor. Lo explico ahora mismo en el siguiente párrafo.
C:
const u8 gText[] = _("Prueba de texto.\p");

static void AddText(const u8* text)
{
    StringExpandPlaceholders(gStringVar4, text);
    AddTextPrinterForMessage(1);
}
Como se ha podido observar, AddText necesita un "text" que es un "const u8", es decir, nuestro texto nombrado "gText". Además ese "text" se pasará a StringExpandPlaceholders, el cual también necesita un const u8. Por tanto, sin necesidad de AddText, tendríamos las dos funciones libres de la siguiente manera: (Y como ya dije, se podrían introducir dentro de CreateTextBox sin ningún problema, o llamarlas sin más desde fuera. Pero es opcional.).
C:
StringExpandPlaceholders(gStringVar4, gText);
AddTextPrinterForMessage(1);
¿Y qué pasa con AddTextPrinterForMessage(1)? Bien, lo primero es que el printer se encarga de dar color al texto y a su sombra, establece la fuente del texto, y lo más importante ¿Por qué (1)? AddTextPrinterForMessage necesita que demos un valor al booleano llamado "allowSkippingDelayWithButtonPress", o sea, permitir o no que al pulsar un botón el texto printee más rápido ¿Os suena no? Si le decimos 0, no lo permite, en cambio con 1 sí. También haremos al final del tutorial una pequeña modificación en la definición de esta función, porque de momento esto carga las letras dentro de un recuadro blanco preparado para el textbox original.

CreateTextBox, Esta función recibirá como parámetro "u8 windowId" nuestro window, su nombre en este caso WINDOW ó 0. De esta forma todas las funciones que contiene CreateTextBox se les pasará el windowId sin necesidad de especificarlo en cada una.
C:
static void CreateTextBox(u8 windowId)
{
    //Estas 3 funciones son predefinidas de pokeemerald (muy importante para los que están empezando diferenciar cuáles creamos y cúales no)
    FillWindowPixelBuffer(windowId, 0);//color 0 para los píxeles(transparente), de no hacerlo veríamos el texto acompañado de unas líneas verticales, muy bug.
    PutWindowTilemap(windowId); // Colocamos el tilemap, OJO sus gráficos no los estamos colocando ni lo vamos a hacer. Sin el tilemap no se pueden mostrar textos.
    CopyWindowToVram(windowId, 3);// Evidentemente, si no mandamos el window a la ram de video tampoco podemos mostrar texto. Generalmente se usa el modo 3.
}

static void InitTextWindows()
{
    //Estas 3 funciones son predefinidas de pokeemerald
    InitWindows(gTextWindows); //Inicializa los window, para que podamos colocar nuestro texto.
    DeactivateAllTextPrinters(); //Desactiva printers anteriores que hubiese, para que no haya contradicciones.
    LoadPalette(GetOverworldTextboxPalettePtr(), 0xf0, 0x20); /*Carga la paleta del texto por defecto del juego en el slot 15 de las paletas para backgrounds,
                                esto claramente afecta si quieres cargar un bg de 224 colores, porque necesitas 16 colores para el
                                texto y otros 16 para el textbox(si lo tuviera, que en este caso no lo cargamos, por tanto podrías usar
                                un fondo de 240 colores). Así que si indexas tu tileset en 255 colores, con CMP debes eliminar
                                en la paleta los 16 colores de la fila 15 y los 16 colores de la última fila. CMP adaptará el tileset perfectamente.*/
}
Con InitTexts englobamos en una sola función a las otra dos que hemos creado anteriormente.
C:
static void InitTexts()
{
    CreateTextBox(WINDOW); // ó CreateTextBox(0);
    AddText(gText);
}
Literalmente este siguiente task contiene todo lo necesario que hemos creado para que cargue el mensaje, sí, es en este momento cuando lanzas el mensaje.
C:
static void Task_mensaje(u8 taskId){
    InitTextWindows();
    InitTexts();
    gTasks[taskId].func = Task_cerraMensaje;
}
A la hora de cerrar el mensaje hay que saber qué es esto "RunTextPrintersAndIsPrinter0Active". Lo usamos mediante un "if" para comprbar si NO se está imprimiendo texto (por eso el signo: !). Y claro, eso es necesario para que el mensaje no se cierre hasta que se haya printeado totalmente y pulses un botón. ¿Recuerdas esto en la declaración del texto "\p"? El printer, es la flechita roja que aparece al final y DEBES PONERLA, porque si no, el mensaje se cerrará automáticamente cuando haya terminado de aparecer ya que no habría ningún printer.
C:
static void Task_cerraMensaje(u8 taskId){
    if(!RunTextPrintersAndIsPrinter0Active()){
        ClearDialogWindowAndFrame(WINDOW, 1);
        FreeAllWindowBuffers();
    }
}
Bueno ClearDialogWindowAndFrame recibe como segundo parámetro un "bool8 copyToVram", esto es lo más importante, hay que ponerlo en valor 1 para que sea TRUE y por tanto pasemos esta info a la Vram por medio del modo 3 "CopyWindowToVram(windowId, 3);". Esto se debe a que en la definición de la función encontramos esto:
C:
if (copyToVram == TRUE)
    CopyWindowToVram(windowId, 3);

/* También podrías haberlo encontrado de esta otra forma, pero la primera manera
sólo es válida cuando el IF contiene una sola línea de código, eso sí, tabulada
para indicar que está dentro del IF*/

if (copyToVram == TRUE){
    CopyWindowToVram(windowId, 3);
}
ADVERTENCIA, ClearDialogWindowAndFrame no elimina el window del Bg, sino el textbox y el texto. Para liberar el Bg completamente usamos FreeAllWindowBuffers.

Ahora hagamos esa modificación de la que hablé para que las letras carguen sin ese recuadro y también vamos a cambiar la fuente y los colores del texto y su sombra. Ve a "menu.c" y encuentra esta función: (Mi recomendación es que la copies y crees una nueva y hagas modificaciones en esa y, que sea con la que hagas las pruebas).
C:
void AddTextPrinterForMessage(bool8 allowSkippingDelayWithButtonPress)
{
    void (*callback)(struct TextPrinterTemplate *, u16) = NULL;
    gTextFlags.canABSpeedUpPrint = allowSkippingDelayWithButtonPress;
    AddTextPrinterParameterized2(0, 1, gStringVar4, GetPlayerTextSpeedDelay(), callback, 2, 1, 3);
}
En AddTextPrinterParameterized2, el penúltimo parámetro que vale 1, lo ponemos en 0 y así las letras ya tendrán fondo transparente. Pues ahora ya sí que tenemos un texto que carga perfectamente sin textbox. El resto de cosas:

- El segundo parámetro es la fuente, puedes escoger entre 0, 1 y 2.
- El sexto es el color del texto.
- El último es el color de la sombra.
C:
#define TEXT_COLOR_TRANSPARENT  0x0
#define TEXT_COLOR_WHITE        0x1
#define TEXT_COLOR_DARK_GREY    0x2
#define TEXT_COLOR_LIGHT_GREY   0x3
#define TEXT_COLOR_RED          0x4
#define TEXT_COLOR_LIGHT_RED    0x5
#define TEXT_COLOR_GREEN        0x6
#define TEXT_COLOR_LIGHT_GREEN  0x7
#define TEXT_COLOR_BLUE         0x8
#define TEXT_COLOR_LIGHT_BLUE   0x9
#define TEXT_DYNAMIC_COLOR_1    0xA // Usually white
#define TEXT_DYNAMIC_COLOR_2    0xB // Usually white w/ tinge of green
#define TEXT_DYNAMIC_COLOR_3    0xC // Usually white
#define TEXT_DYNAMIC_COLOR_4    0xD // Usually aquamarine
#define TEXT_DYNAMIC_COLOR_5    0xE // Usually blue-green
#define TEXT_DYNAMIC_COLOR_6    0xF // Usually cerulean
Aquí tienes una demostración del resultado:

gif.gif
 
Última edición:

Hoennwalker

Usuario de platino
Creo que la nomenclatura de las funciones predefinidas es la misma para otras bases de decomp, por eso he etiquetado el tema en decomp genérico.

También aclarar que el gif incluye el cierre del mensaje de texto, sin el textbox puede parecer que se termina el gif ahí.
 

Kaktus

Miembro insignia
Miembro insignia
¡¡Sí señor!! Un gran tutorial que ya iba siendo necesario puesto que cada vez que tengo que imprimir texto, doy cincuenta mil vueltas a las funciones para recordarlo todo. Está muy bien tenerlo aquí todo resumidito, un muy muy buen aporte.

Te pondré una par de pegas al tuto a modo de crítica constructiva, eres libre de tomar las sugerencias si así lo consideras oportuno. En primer lugar, y lo que más me ha descolocado es que no me da la sensación de que existe una lógica en el orden de la explicación de cada función. Es decir, da la sensación de que las funciones están puestas conforme las has ido viendo, o desde la función más "interna", a la más "externa", y esto hace que tengas que estar continuamente retrocediendo en el tutorial para ver donde se usa cada cosa y entenderlo, y es un tanto discontinuo. Si yo pudiera cambiar algo, sin duda, cambiaría la organización, porque está algo liosa, y en su lugar, empezaría desde la función más externa (explicando brevemente lo que hará ella y sus subfunciones) y de ahí, ir desglosando cada parte.

Por otro lado, un par de notas simples que considero importante remarcar para quien no haya trabajado todavía con gráficos y estructuras de los mismos desde código. Los tamaños y posiciones de la estructura WindowTemplate (es decir: .tilemapLeft, .tilemapTop, .width y .height no están dados en píxeles, están dados en tiles, es decir, que si quisieramos saber las medidas en píxeles, tendríamos que multiplicar los valores de estos parámetros por 8.

Y esto, que es más cuestión de organización para luego no perdernos entre las líneas de código, es que por lo general, si vais a usar este tutorial para interfaces, los textos de dichas interfaces están todos en un mismo archivo del proyecto, por lo que no os conviene declararlo en el propio archivo .c donde esteis creando la interfaz, no porque no vaya a funcionar, si no porque no va a estar organizado.

Soltado este tocho, felicitarte por tu grandísima iniciativa, y por traer un aporte tan, pero tan útil @Hoennwalker. Mis diez, espero seguir viendo aportes de tu mano por aquí. ¡¡Un saludo!!
 

Hoennwalker

Usuario de platino
@InmortalKaktus De hecho vienen bien tus sugerencias. Creo que cambiaré en ciertos párrafos algunas palabras o introduciré otras para aclararlo mejor. Y sobre los bloques de 8x8 es cierto también, lo actualizaré.
 
Arriba