2 80x80 Sprites
Soul-8691 edited this page 2025-07-03 12:23:18 -04:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 03)
    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 47)
    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 03)
    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 47)
    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.