Tyren Sealess
A fullmetal heart.
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.
[hr][/hr]
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.
[hr][/hr]
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.
[hr][/hr]
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:
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;
};
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.
[hr][/hr]
Paso 2: Añadir el menú
Ya hemos terminado de editar el código de Game Freak, ahora toca añadir el nuestro. Crearemos un archivo de nombre "debug.h" en la carpeta "include" y le pondremos 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
Luego, crearemos un archivo de nombre "debug.h" en la carpeta "src" con el siguiente código:
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
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.
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,
};
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
};
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.
Última edición por un moderador: