80x80 Sprites - pret/pokeemerald GitHub Wiki

Disclaimer: This code does not give you functionality for 80x80 sprites in battle. This is only a means of creating 80x80 sprites on the screen; nothing more. Credits:

  • froggestspirit for the CreateBigSprite code, which I have modified
  • hedara for the ConvertToTiles4BppBig code

sprite.c

u8 CreateBigSpriteAt(u8 index, const struct SpriteTemplate *template, s16 x, s16 y, u8 subpriority)
{
    u8 ic;
    struct Sprite *spriteA = &gSprites[index];
    struct Sprite *spriteB = &gSprites[index + 1];
    struct Sprite *spriteC = &gSprites[index + 2];
    struct Sprite *spriteD = &gSprites[index + 3];
    struct Sprite *spriteE = &gSprites[index + 4];
    struct Sprite *spriteF = &gSprites[index + 5];

    ResetSprite(spriteA);
    ResetSprite(spriteB);
    ResetSprite(spriteC);
    ResetSprite(spriteD);
    ResetSprite(spriteE);
    ResetSprite(spriteF);
    
    spriteA->inUse = TRUE;
    spriteA->animBeginning = TRUE;
    spriteA->affineAnimBeginning = TRUE;
    spriteA->usingSheet = TRUE;

    spriteA->subpriority = subpriority;
    spriteA->oam = *template->oam;
    spriteA->anims = template->anims;
    spriteA->affineAnims = template->affineAnims;
    spriteA->template = template;
    spriteA->callback = template->callback;
    spriteB->subpriority = subpriority;
    spriteB->oam = *template->oam;
    spriteB->anims = template->anims;
    spriteB->affineAnims = template->affineAnims;
    spriteB->template = template;
    spriteB->callback = SpriteCallbackDummy;
    spriteC->subpriority = subpriority;
    spriteC->oam = *template->oam;
    spriteC->anims = template->anims;
    spriteC->affineAnims = template->affineAnims;
    spriteC->template = template;
    spriteC->callback = SpriteCallbackDummy;
    spriteD->subpriority = subpriority;
    spriteD->oam = *template->oam;
    spriteD->anims = template->anims;
    spriteD->affineAnims = template->affineAnims;
    spriteD->template = template;
    spriteD->callback = SpriteCallbackDummy;
    spriteE->subpriority = subpriority;
    spriteE->oam = *template->oam;
    spriteE->anims = template->anims;
    spriteE->affineAnims = template->affineAnims;
    spriteE->template = template;
    spriteE->callback = SpriteCallbackDummy;
    spriteF->subpriority = subpriority;
    spriteF->oam = *template->oam;
    spriteF->anims = template->anims;
    spriteF->affineAnims = template->affineAnims;
    spriteF->template = template;
    spriteF->callback = SpriteCallbackDummy;

    spriteA->x = x;
    spriteA->y = y;
    gSprites[0].x = x;
    gSprites[0].y = y;
    gSprites[1].x = x;
    gSprites[1].y = y + 64;
    gSprites[2].x = x + 32;
    gSprites[2].y = y + 64;
    gSprites[3].x = x + 64;
    gSprites[3].y = y;
    gSprites[4].x = x + 64;
    gSprites[4].y = y + 32;
    gSprites[5].x = x + 64;
    gSprites[5].y = y + 64;

    spriteA->oam.shape = 0; //square
    spriteA->oam.size = SPRITE_SIZE(64x64); //64x64
    spriteB->oam.shape = 1; //horizontal
    spriteB->oam.size = SPRITE_SIZE(32x16); //32x16
    spriteC->oam.shape = 1; //horizontal
    spriteC->oam.size = SPRITE_SIZE(32x16); //32x16
    spriteD->oam.shape = 2; //vertical
    spriteD->oam.size = SPRITE_SIZE(16x32); //16x32
    spriteE->oam.shape = 2; //vertical
    spriteE->oam.size = SPRITE_SIZE(16x32); //16x32
    spriteF->oam.shape = 0; //square
    spriteF->oam.size = SPRITE_SIZE(16x16); //16x16

    if (template->tileTag == 0xFFFF)
    {
        s16 tileNum;
        spriteA->images = template->images;
        tileNum = AllocSpriteTiles((u8)(CARD_PIC_SIZE / TILE_SIZE_8BPP)); //allocate for a 80x80 sprite
        if (tileNum == -1)
        {
            ResetSprite(spriteA);
            ResetSprite(spriteB);
            ResetSprite(spriteC);
            ResetSprite(spriteD);
            ResetSprite(spriteE);
            ResetSprite(spriteF);
            return MAX_SPRITES;
        }
        spriteA->oam.tileNum = tileNum;
        spriteA->usingSheet = FALSE;
        spriteA->sheetTileStart = 0;
    }
    else
    {
        spriteA->sheetTileStart = GetSpriteTileStartByTag(template->tileTag);
        SetSpriteSheetFrameTileNum(spriteA);
    }
        spriteB->usingSheet = spriteA->usingSheet;
        spriteB->sheetTileStart = spriteA->sheetTileStart + 0x80;
        spriteB->oam.tileNum = spriteA->oam.tileNum + 0x80;
        spriteB->images = spriteA->images + 0x600;
        spriteC->usingSheet = spriteA->usingSheet;
        spriteC->sheetTileStart = spriteA->sheetTileStart + 0x90;
        spriteC->oam.tileNum = spriteA->oam.tileNum + 0x90;
        spriteC->images = spriteA->images + 0xA00;
        spriteD->usingSheet = spriteA->usingSheet;
        spriteD->sheetTileStart = spriteA->sheetTileStart + 0xA0;
        spriteD->oam.tileNum = spriteA->oam.tileNum + 0xA0;
        spriteD->images = spriteA->images + 0xA80;
        spriteE->usingSheet = spriteA->usingSheet;
        spriteE->sheetTileStart = spriteA->sheetTileStart + 0xB0;
        spriteE->oam.tileNum = spriteA->oam.tileNum + 0xB0;
        spriteE->images = spriteA->images + 0xB00;
        spriteF->usingSheet = spriteA->usingSheet;
        spriteF->sheetTileStart = spriteA->sheetTileStart + 0xC0;
        spriteF->oam.tileNum = spriteA->oam.tileNum + 0xC0;
        spriteF->images = spriteA->images + 0xB80;

    if (spriteA->oam.affineMode & ST_OAM_AFFINE_ON_MASK)
        InitSpriteAffineAnim(spriteA);


    if (template->paletteTag != 0xFFFF){
        spriteA->oam.paletteNum = IndexOfSpritePaletteTag(template->paletteTag);
    }
    spriteB->inUse = TRUE;
    spriteC->inUse = TRUE;
    spriteD->inUse = TRUE;
    spriteE->inUse = TRUE;
    spriteF->inUse = TRUE;
    return index;
}

u8 CreateBigSprite(const struct SpriteTemplate *template, s16 x, s16 y, u8 subpriority)
{
    u8 i;
    u8 ret = 0;
    for (i = 0; i < MAX_SPRITES; i++){
        if (!gSprites[i].inUse){
            if(++ret == 5){
                return CreateBigSpriteAt(i - 4, template, x, y, subpriority);
            }
        }else{
            ret = 0;
        }
    }
    return MAX_SPRITES;
}

tools/gbagfx/gfx.c

static void ConvertToTiles4BppBig(unsigned char *src, unsigned char *dest, int images)
{
	int subTileX = 0;
	int subTileY = 0;
	int metatileX = 0;
	int metatileY = 0;
	int pitch = 10 * 4;

	for (int im = 0; im < images; im++) {
		metatileX = 0;
		metatileY = im * 10;
		for (int i = 0; i < 80; i++) { //64x80 part
			for (int j = 0; j < 8; j++) {
				int srcY = metatileY * 8 + j;

				for (int k = 0; k < 4; k++) {
					int srcX = metatileX * 4 + k;
					unsigned char srcPixelPair = src[srcY * pitch + srcX];
					unsigned char leftPixel = srcPixelPair >> 4;
					unsigned char rightPixel = srcPixelPair & 0xF;

					*dest++ = (rightPixel << 4) | leftPixel;
				}
			}

			AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, 8, 1, 1);
		}

		metatileX = 0;
		metatileY = im * 10;
		for (int i = 0; i < 20; i++) { //16x80 part
			for (int j = 0; j < 8; j++) {
				int srcY = metatileY * 8 + j;

				for (int k = 0; k < 4; k++) {
					int srcX = (metatileX + 8) * 4 + k;
					unsigned char srcPixelPair = src[srcY * pitch + srcX];
					if(metatileX >= 2) srcPixelPair = 0;
					unsigned char leftPixel = srcPixelPair >> 4;
					unsigned char rightPixel = srcPixelPair & 0xF;

					*dest++ = (rightPixel << 4) | leftPixel;
				}
			}

			AdvanceMetatilePosition(&subTileX, &subTileY, &metatileX, &metatileY, 2, 1, 1);
		}
	}
}

tools/gbagfx/main.c

#define TILE_SIZE 64
#define TILES_PER_ROW 10

int tile_index(int x, int y) {
    return y * TILES_PER_ROW + x;
}

void copy_tile(unsigned char *dst, unsigned char *src, int index) {
    memcpy(dst, src + index * TILE_SIZE, TILE_SIZE);
}

void HandleBigSpriteCommand(char *inputPath, char *outputPath, int argc UNUSED, char **argv UNUSED)
{
    int size;
    unsigned char *fileContents = ReadWholeFile(inputPath, &size);
    unsigned char *outputContents = malloc(80 * 80); // 6400 bytes

    int index = 0;

    // 64x64 sprite (tiles 0-7 in rows 0-7)
    for (int y = 0; y < 8; y++) {
        for (int x = 0; x < 8; x++) {
            copy_tile(outputContents + index, fileContents, tile_index(x, y));
            index += TILE_SIZE;
        }
    }

    // 32x16 A (row 8, columns 0–3)
    for (int y = 8; y < 10; y++) {
        for (int x = 0; x < 4; x++) {
            copy_tile(outputContents + index, fileContents, tile_index(x, y));
            index += TILE_SIZE;
        }
    }

    // 32x16 B (row 8, columns 4–7)
    for (int y = 8; y < 10; y++) {
        for (int x = 4; x < 8; x++) {
            copy_tile(outputContents + index, fileContents, tile_index(x, y));
            index += TILE_SIZE;
        }
    }

    // 16x32 A (tile column 8, rows 0–3)
    for (int y = 0; y < 4; y++) {
        for (int x = 8; x < 10; x++) {
            copy_tile(outputContents + index, fileContents, tile_index(x, y));
            index += TILE_SIZE;
        }
    }

    // 16x32 B (tile column 8, rows 4–7)
    for (int y = 4; y < 8; y++) {
        for (int x = 8; x < 10; x++) {
            copy_tile(outputContents + index, fileContents, tile_index(x, y));
            index += TILE_SIZE;
        }
    }

    // 16x16 (bottom-right)
    for (int y = 8; y < 10; y++) {
        for (int x = 8; x < 10; x++) {
            copy_tile(outputContents + index, fileContents, tile_index(x, y));
            index += TILE_SIZE;
        }
    }

    WriteWholeFile(outputPath, outputContents, size);

    free(fileContents);
    free(outputContents);
}

How to Use:

In struct CommandHandler handlers[] in int main in tools/gbagfx/main.c, add { "8bpp", "8bpp", HandleBigSpriteCommand }, so that you can do gbagfx pic.8bpp pic.8bpp in command line to convert from a normal pic to the format used for a sprite stitch in the code.

In WriteTileImage in tools/gbagfx/gfx.c, replace case 4 with:

	case 4:
		if (image->width == 80 && ((image->height % 80) == 0))
		{
			ConvertToTiles4BppBig(image->pixels, buffer, image->height / 80);
		}
		else
		ConvertToTiles4Bpp(image->pixels, buffer, maxNumTiles, metatilesWide, metatileWidth, metatileHeight, invertColors);
		break;

This will allow you to perform gbagfx pic.png pic.4bpp (the 4bpp sprite reformatting function).

Now, all you have to do is use it like CreateSprite on a SpriteTemplate (SPRITE_SHAPE(64x64) and SPRITE_SIZE(64x64), still) and, if you've performed the gbagfx conversion, it should turn out properly.