[Pokeemerald] Implementación de un menú de debug

Avisos


Like Tree3Gracias
  • 3 Post By Tyren Sealess
Respuesta
 
Herramientas Desplegado
  #1  
03/05/2019
Predeterminado [Pokeemerald] Implementación de un menú de debug
Post original creado por Ketsuban en GitHub y posteado en PokeCommunity, todos los créditos van dirigidos al autor/a. Adaptado y traído a WaH para vuestro uso y disfrute por el Equipo de traducción. Os dejamos con ello.





Implementación de un menú de debug
pokeemerald


Durante el desarrollo de un juego es común encontrarse en situaciones en las que, para testear algo, has de jugar una gran parte del juego, lo cual supone una pérdida de tiempo. Por eso es útil tener un menú de debug con una colección de herramientas que te permitan hacer este testeo, a la vez que ahorrar tiempo. Sin embargo, como pokeemerald reproduce el lanzamiento comercial de Pokémon Esmeralda, no hay ningún menú de debug. Vamos a arreglar eso.

Este tutorial está dirigido a aquellos con un buen conocimiento de C, así que no explicaré cosas como directivas preprocesador, include guards, qué es una struct y sus campos y demás. Si desconoces qué son estas cosas, ya sabes qué hacer. Las adiciones de código que se hagan en archivos ya existentes estarán marcadas con un "+" al comienzo de la línea.




Paso 0: Añadir una flag de compilación


No tiene sentido darles un menú debug a los jugadores, así que es una buena idea ocultarlo con una flag de compilación como hizo Game Freak. Técnicamente esto es opcional, ya que simplemente puedes decirle a la gente que no mantenga apretado R mientras pulsa Start o algo similar, pero es muy fácil de hacer. Si decides, no hacerlo ten cuidado durante el resto del tutorial para ignorar las parejas #if DEBUG y #endif.

Comienza añadiendo esto al Makefile:

Código:
ifeq ($(NODEP),1)
$(C_BUILDDIR)/%.o: c_dep :=
else
$(C_BUILDDIR)/%.o: c_dep = $(shell $(SCANINC) -I include $(C_SUBDIR)/$*.c)
endif

ifeq ($(DINFO),1)
override CFLAGS += -g
endif

+ifeq ($(DDEBUG),1)
+override ASFLAGS += --defsym DEBUG=1
+override CPPFLAGS += -D DEBUG=1
+endif

$(C_BUILDDIR)/%.o : $(C_SUBDIR)/%.c $$(c_dep)
	@$(CPP) $(CPPFLAGS) $< -o $(C_BUILDDIR)/$*.i
	@$(PREPROC) $(C_BUILDDIR)/$*.i charmap.txt | $(CC1) $(CFLAGS) -o $(C_BUILDDIR)/$*.s
	@echo -e ".text\n\t.align\t2, 0\n" >> $(C_BUILDDIR)/$*.s
	$(AS) $(ASFLAGS) -o $@ $(C_BUILDDIR)/$*.s

De ahora en adelante, cuando estés construyendo la versión del hack para ti, querrás usar make DDEBUG=1 (¡cuidado con la doble D!) en lugar de make.




Paso 1: Añadir una combinación de botones


En este paso trabajaremos en el archivo fuente "field_control_avatar.c" y su correspondiente archivo puntero "field_control_avatar.h". Busca en el archivo puntero la definición de FieldInput. Nos dirigimos al header, que para mí contiene esto:

FieldInput

Código:
struct FieldInput
{
    bool8 pressedAButton:1;
    bool8 checkStandardWildEncounter:1;
    bool8 pressedStartButton:1;
    bool8 pressedSelectButton:1;
    bool8 heldDirection:1;
    bool8 heldDirection2:1;
    bool8 tookStep:1;
    bool8 pressedBButton:1;
    bool8 input_field_1_0:1;
    bool8 input_field_1_1:1;
    bool8 input_field_1_2:1;
    bool8 input_field_1_3:1;
    bool8 input_field_1_4:1;
    bool8 input_field_1_5:1;
    bool8 input_field_1_6:1;
    bool8 input_field_1_7:1;
    u8 dpadDirection;
};

Los campos cuyos nombres empiezan con "input_field_1_" son flags de entrada al menú de debug; en las compilaciones de debug de Game Freak estas se activan cuando el usuario introduce cierta combinación de botones. Usaré "input_field_1_2" porque es la que usó Game Freak. Puede que tenga otro nombre en tu copia de pokeemerald, pero estará en la misma posición y estructura así que todo lo que tienes que hacer es recordar el nombre.

Vuelve al source y añade al final de la función FieldGetPlayerInput lo siguiente:

Código:
    if (heldKeys & DPAD_UP)
        input->dpadDirection = DIR_NORTH;
    else if (heldKeys & DPAD_DOWN)
        input->dpadDirection = DIR_SOUTH;
    else if (heldKeys & DPAD_LEFT)
        input->dpadDirection = DIR_WEST;
    else if (heldKeys & DPAD_RIGHT)
        input->dpadDirection = DIR_EAST;

+#if DEBUG
+    if ((heldKeys & R_BUTTON) && input->pressedStartButton)
+    {
+        input->input_field_1_2 = TRUE;
+        input->pressedStartButton = FALSE;
+    }
+#endif
}

Esto define la combinación de botones para acceder al menú de debug: mantén R y pulsa start. Puedes cambiarlo si quieres, pero el número de combinaciones sensatas está bastante limitado.

Ahora baja y añade al final de la función ProcessPlayerFieldInput lo siguiente:

Código:
    if (input->pressedSelectButton && UseRegisteredKeyItemOnField() == TRUE)
        return TRUE;

+#if DEBUG
+    if (input->input_field_1_2)
+    {
+        PlaySE(SE_WIN_OPEN);
+        Debug_ShowMainMenu();
+        return TRUE;
+    }
+#endif

    return FALSE;
}

El resultado de estos cambios es que si el usuario ha presionado la combinación de botones habrá un sonido y (próximamente) un menú. Si quieres puedes comentar la línea Debug_ShowMainMenu(); y testear el código tal y como está ahora. El sonido es el mismo que suena al abrir el menú start, pero no se abre ningún menú. Para acabar, añade al comienzo del archivo el siguiente include:

Código:
#include "global.h"
#include "battle_setup.h"
#include "bike.h"
#include "coord_event_weather.h"
#include "daycare.h"
+#include "debug.h"
#include "faraway_island.h"

Ahora vamos a crear los archivos "debug.h" y "debug.c" en las carpetas "include" y "src" respectivamente.




Paso 2: Añadir el menú


Ya hemos terminado de editar el código de Game Freak, ahora toca añadir el nuestro. En el archivo "debug.h" añadiremos el siguiente código:

Código:
#ifndef GUARD_DEBUG_H
#define GUARD_DEBUG_H
#if DEBUG

void Debug_ShowMainMenu(void);

#endif
#endif // GUARD_DEBUG_H

Y en el archivo "debug.c" el siguiente:

Spoiler

Código:
#if DEBUG

#include "global.h"
#include "list_menu.h"
#include "main.h"
#include "menu.h"
#include "script.h"
#include "sound.h"
#include "strings.h"
#include "task.h"
#include "constants/songs.h"

#define DEBUG_MAIN_MENU_HEIGHT 7
#define DEBUG_MAIN_MENU_WIDTH 11

void Debug_ShowMainMenu(void);
static void Debug_DestroyMainMenu(u8);
static void DebugAction_Cancel(u8);
static void DebugTask_HandleMainMenuInput(u8);

enum {
    DEBUG_MENU_ITEM_CANCEL,
};

static const u8 gDebugText_Cancel[] = _("Cancel");

static const struct ListMenuItem sDebugMenuItems[] =
{
    [DEBUG_MENU_ITEM_CANCEL] = {gDebugText_Cancel, DEBUG_MENU_ITEM_CANCEL}
};

static void (*const sDebugMenuActions[])(u8) =
{
    [DEBUG_MENU_ITEM_CANCEL] = DebugAction_Cancel
};

static const struct WindowTemplate sDebugMenuWindowTemplate =
{
    .bg = 0,
    .tilemapLeft = 1,
    .tilemapTop = 1,
    .width = DEBUG_MAIN_MENU_WIDTH,
    .height = 2 * DEBUG_MAIN_MENU_HEIGHT,
    .paletteNum = 15,
    .baseBlock = 1,
};

static const struct ListMenuTemplate sDebugMenuListTemplate =
{
    .items = sDebugMenuItems,
    .moveCursorFunc = ListMenuDefaultCursorMoveFunc,
    .totalItems = ARRAY_COUNT(sDebugMenuItems),
    .maxShowed = DEBUG_MAIN_MENU_HEIGHT,
    .windowId = 0,
    .header_X = 0,
    .item_X = 8,
    .cursor_X = 0,
    .upText_Y = 1,
    .cursorPal = 2,
    .fillValue = 1,
    .cursorShadowPal = 3,
    .lettersSpacing = 1,
    .itemVerticalPadding = 0,
    .scrollMultiple = LIST_NO_MULTIPLE_SCROLL,
    .fontId = 1,
    .cursorKind = 0
};

void Debug_ShowMainMenu(void) {
    struct ListMenuTemplate menuTemplate;
    u8 windowId;
    u8 menuTaskId;
    u8 inputTaskId;

    // create window
    windowId = AddWindow(&sDebugMenuWindowTemplate);
    DrawStdWindowFrame(windowId, FALSE);

    // create list menu
    menuTemplate = sDebugMenuListTemplate;
    menuTemplate.windowId = windowId;
    menuTaskId = ListMenuInit(&menuTemplate, 0, 0);

    // draw everything
    CopyWindowToVram(windowId, 3);

    // create input handler task
    inputTaskId = CreateTask(DebugTask_HandleMainMenuInput, 3);
    gTasks[inputTaskId].data[0] = menuTaskId;
    gTasks[inputTaskId].data[1] = windowId;
}

static void Debug_DestroyMainMenu(u8 taskId)
{
    DestroyListMenuTask(gTasks[taskId].data[0], NULL, NULL);
    ClearStdWindowAndFrame(gTasks[taskId].data[1], TRUE);
    RemoveWindow(gTasks[taskId].data[1]);
    DestroyTask(taskId);
    EnableBothScriptContexts();
}

static void DebugTask_HandleMainMenuInput(u8 taskId)
{
    void (*func)(u8);
    u32 input = ListMenu_ProcessInput(gTasks[taskId].data[0]);

    if (gMain.newKeys & A_BUTTON)
    {
        PlaySE(SE_SELECT);
        if ((func = sDebugMenuActions[input]) != NULL)
            func(taskId);
    }
    else if (gMain.newKeys & B_BUTTON)
    {
        PlaySE(SE_SELECT);
        Debug_DestroyMainMenu(taskId);
    }
}

static void DebugAction_Cancel(u8 taskId)
{
    Debug_DestroyMainMenu(taskId);
}

#endif

Vamos a ver qué hace cada parte del código que acabamos de ver.

Código:
static const u8 gDebugText_Cancel[] = _("Cancel");

enum {
    DEBUG_MENU_ITEM_CANCEL,
};

static const struct ListMenuItem sDebugMenuItems[] =
{
    [DEBUG_MENU_ITEM_CANCEL] = {gDebugText_Cancel, DEBUG_MENU_ITEM_CANCEL},
};

static void (*const sDebugMenuActions[])(u8) =
{
    [DEBUG_MENU_ITEM_CANCEL] = DebugAction_Cancel,
};

En esta parte definimos la lista de herramientas. sDebugMenuItems agrupa el nombre y la ID en una enumeración. sDebugMenuActions es una lista de funciones que contiene la acción que tiene lugar cuando se usa un elemento de la lista anterior. Es importante que la enumeración y la lista tengan la misma longitud, o pasarán cosas extrañas. Si quieres añadir una nueva entrada a la lista, utiliza este código como guía:

Código:
static const u8 gDebugText_NewEntry[] = _("New Entry");
static const u8 gDebugText_Cancel[] = _("Cancel");

enum {
    DEBUG_MENU_ITEM_NEW_ENTRY,
    DEBUG_MENU_ITEM_CANCEL,
};

static const struct ListMenuItem sDebugMenuItems[] =
{
    [DEBUG_MENU_ITEM_NEW_ENTRY] = {gDebugText_NewEntry, DEBUG_MENU_ITEM_NEW_ENTRY},
    [DEBUG_MENU_ITEM_CANCEL] = {gDebugText_Cancel, DEBUG_MENU_ITEM_CANCEL},
};

static void (*const sDebugMenuActions[])(u8) =
{
    [DEBUG_MENU_ITEM_NEW_ENTRY] = DebugAction_NewEntry,
    [DEBUG_MENU_ITEM_CANCEL] = DebugAction_Cancel,
};

Asegúrate de crear la función static void DebugAction_NewEntry(u8 taskId). Si quieres que la nueva entrada no haga nada cuando sea usada, simplemente usa null.

Plantilla de ventana

Código:
static const struct WindowTemplate sDebugMenuWindowTemplate =
{
    .bg = 0,
    .tilemapLeft = 1,
    .tilemapTop = 1,
    .width = DEBUG_MAIN_MENU_WIDTH,
    .height = 2 * DEBUG_MAIN_MENU_HEIGHT,
    .paletteNum = 15,
    .baseBlock = 1,
};

Esta es la plantilla de una ventana. Una ventana es una caja enmarcada por el borde que elijas en las opciones del juego, y una plantilla define las propiedades de esta ventana. La mayoría de los campos no deben ser modificados, pero los que interesa conocer son "tilemapLeft" y "tilemapTop" (que son la posición relativa a la parte izquierda y la superior de la pantalla en tiles), "width" y "height", (que, como podrás imaginar, son la altura y el ancho de la ventana en tiles) y "baseBlock" (que es usado por el engine para colocar los tiles de cada ventana). Si solo tenemos una ventana abierta, como es nuestro caso, "baseBlock" es 1, pero si queremos abrir otra ventana al mismo tiempo, "baseBlock" debe ser igual al producto de los campos width y height para evitar errores.

Plantilla de listado

Código:
static const struct ListMenuTemplate sDebugMenuListTemplate =
{
    .items = sDebugMenuItems,
    .moveCursorFunc = ListMenuDefaultCursorMoveFunc,
    .totalItems = ARRAY_COUNT(sDebugMenuItems),
    .maxShowed = DEBUG_MAIN_MENU_HEIGHT,
    .windowId = 0,
    .header_X = 0,
    .item_X = 8,
    .cursor_X = 0,
    .upText_Y = 1,
    .cursorPal = 2,
    .fillValue = 1,
    .cursorShadowPal = 3,
    .lettersSpacing = 1,
    .itemVerticalPadding = 0,
    .scrollMultiple = LIST_NO_MULTIPLE_SCROLL,
    .fontId = 1,
    .cursorKind = 0
};

Esta es una plantilla de la lista de elementos de un menú. La lista se compone por los elementos entre los que el jugador puede elegir pulsando A. Al igual que la plantilla de una ventana define las propiedades de esta, esta plantilla hace lo correspondiente. Los campos importantes son "totalItems" (la cantidad de elementos que tiene la lista), "maxShowed" (la cantidad de elementos que se muestran) y "scrollMultiple" (define la capacidad de saltar elementos de la lista, y tiene tres opciones: LIST_NO_MULTIPLE_SCROLL para que esta función esté desactivada, y LIST_MULTIPLE_SCROLL_DPAD y LIST_MULTIPLE_SCROLL_L_R para que esta función esté activa usando izquierda y derecha en el D-pad o L y R respectivamente).

Código:
void Debug_ShowMainMenu(void) {
    struct ListMenuTemplate menuTemplate;
    u8 windowId;
    u8 menuTaskId;
    u8 inputTaskId;

    // create window
    windowId = AddWindow(&sDebugMenuWindowTemplate);
    DrawStdWindowFrame(windowId, FALSE);

    // create list menu
    menuTemplate = sDebugMenuListTemplate;
    menuTemplate.windowId = windowId;
    menuTaskId = ListMenuInit(&menuTemplate, 0, 0);

    // draw everything
    CopyWindowToVram(windowId, 3);

    // create input handler task
    inputTaskId = CreateTask(DebugTask_HandleMainMenuInput, 3);
    gTasks[inputTaskId].data[0] = menuTaskId;
    gTasks[inputTaskId].data[1] = windowId;
}

Esta es la función que se ejecuta en "field_control_avatar.c". Primero crea tanto la ventana como la lista de elementos que ya hemos explicado, y después crea una tarea para manejar el input del jugador. Esta tarea es una función que se ejecuta cada frame, en este caso DebugTask_HandleMainMenuInput será la función ejecutada, que comprueba y redirige el input del jugador a la función correspondiente.

Ahora mismo, deberías poder compilar el juego y abrir y cerrar el menú. Esto tan sólo es una base, es ahora tu trabajo el darle utilidad a este menú de debug.




¡Gracias a todos!
No habría recibido estos premios sin:
El Señor del Agua y la Arena | Un Cuento de Madrid | En el Festival de la Victoria | Hijos del Bosque, Hijos del Viento | La Biblioteca Olvidada



¡Gracias, Rata! Se te recuerda... :c
Respuesta

Herramientas
Desplegado

Permisos para publicar mensajes
No puedes crear nuevos temas
No puedes responder mensajes
No puedes subir archivos adjuntos
No puedes editar tus mensajes

Los BB code están Activado
Los Emoticones están Activado
El código [IMG] está Activado
El Código HTML está Desactivado
Trackbacks are Activado
Pingbacks are Activado
Refbacks are Desactivado



Extra
Estilo clásico
La franja horaria es GMT +1. Ahora son las 14:35.

© Whack a Hack! 2019