-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Show a throbber animation while the game is saving
Credit to me (meejle), and to Anon822 for helping me finish it off when I got stuck. 😬
It's designed to fit with the default Pokémon Emerald text window, so you might want to redesign it. Just keep to the usual 16-colour palette, and keep each frame of animation contained within a 32×64px "block" (like below), and you can't go wrong.
First, add #include "decompress.h"
to the includes at the top of the file.
Then, add this big block of code to start_menu.c
. It doesn't matter where you put it, as long as it comes before static u8 SaveSavingMessageCallback(void)
. I put it at the top, just above // Menu actions
.
#define TAG_THROBBER 0x1000
static const u16 sThrobber_Pal[] = INCBIN_U16("graphics/text_window/throbber.gbapal");
const u32 gThrobber_Gfx[] = INCBIN_U32("graphics/text_window/throbber.4bpp.lz");
static u8 spriteId;
static const struct OamData sOam_Throbber =
{
.y = DISPLAY_HEIGHT,
.affineMode = ST_OAM_AFFINE_OFF,
.objMode = ST_OAM_OBJ_NORMAL,
.mosaic = FALSE,
.bpp = ST_OAM_4BPP,
.shape = SPRITE_SHAPE(32x64),
.x = 0,
.matrixNum = 0,
.size = SPRITE_SIZE(32x64),
.tileNum = 0,
.priority = 0,
.paletteNum = 0,
.affineParam = 0,
};
static const union AnimCmd sAnim_Throbber[] =
{
ANIMCMD_FRAME(0, 4),
ANIMCMD_FRAME(32, 4),
ANIMCMD_FRAME(64, 4),
ANIMCMD_FRAME(96, 4),
ANIMCMD_FRAME(128, 4),
ANIMCMD_FRAME(160, 4),
ANIMCMD_FRAME(192, 4),
ANIMCMD_FRAME(224, 4),
ANIMCMD_JUMP(0),
};
static const union AnimCmd * const sAnims_Throbber[] = { sAnim_Throbber, };
static const struct CompressedSpriteSheet sSpriteSheet_Throbber[] =
{
{
.data = gThrobber_Gfx,
.size = 0x3200,
.tag = TAG_THROBBER
},
{}
};
static const struct SpritePalette sSpritePalettes_Throbber[] =
{
{
.data = sThrobber_Pal,
.tag = TAG_THROBBER
},
{},
};
static const struct SpriteTemplate sSpriteTemplate_Throbber =
{
.tileTag = TAG_THROBBER,
.paletteTag = TAG_THROBBER,
.oam = &sOam_Throbber,
.anims = sAnims_Throbber,
.images = NULL,
.affineAnims = gDummySpriteAffineAnimTable,
.callback = SpriteCallbackDummy
};
void ShowThrobber(void)
{
LoadCompressedSpriteSheet(&sSpriteSheet_Throbber[0]);
LoadSpritePalettes(sSpritePalettes_Throbber);
// 217 and 123 are the x and y coordinates (in pixels)
spriteId = CreateSprite(&sSpriteTemplate_Throbber, 217, 123, 2);
};
Next, find the SaveSavingMessageCallback
function. We just need to add ShowThrobber();
to the very top of the function, like this:
static u8 SaveSavingMessageCallback(void)
{
ShowThrobber();
ShowSaveMessage(gText_SavingDontTurnOff, SaveDoSaveCallback);
return SAVE_IN_PROGRESS;
}
Finally, in the SaveDoSaveCallback
function, we just need to replace this if
statement:
if (saveStatus == SAVE_STATUS_OK)
ShowSaveMessage(gText_PlayerSavedGame, SaveSuccessCallback);
else
ShowSaveMessage(gText_SaveError, SaveErrorCallback);
With this one:
if (saveStatus == SAVE_STATUS_OK)
{
ShowSaveMessage(gText_PlayerSavedGame, SaveSuccessCallback);
DestroySprite(&gSprites[spriteId]);
}
else
{
ShowSaveMessage(gText_SaveError, SaveErrorCallback);
DestroySprite(&gSprites[spriteId]);
}
We're almost done. In save.c
, right before the WriteSaveSectorOrSlot
function, we'll add this:
static void VBlankCB_Saving(void)
{
AnimateSprites();
BuildOamBuffer();
LoadOam();
ProcessSpriteCopyRequests();
}
Then, in the WriteSaveSectorOrSlot
function itself, we need to add IntrCallback prevVblankCB;
right at the top, so it looks like this:
static u8 WriteSaveSectorOrSlot(u16 sectorId, const struct SaveSectorLocation *locations)
{
IntrCallback prevVblankCB;
u32 status;
u16 i;
And finally, we're going to add some lines before and after this for
statement:
for (i = 0; i < NUM_SECTORS_PER_SLOT; i++)
HandleWriteSector(i, locations);
So that it looks like this:
prevVblankCB = gMain.vblankCallback;
SetVBlankCallback(VBlankCB_Saving);
for (i = 0; i < NUM_SECTORS_PER_SLOT; i++)
HandleWriteSector(i, locations);
SetVBlankCallback(prevVblankCB);
Then build, save, and enjoy!