Skip to content

Adding new event object or overworld sprites

purrfectdoodle edited this page Jan 30, 2024 · 4 revisions

This tutorial is for adding a new overworld sprite. We'll be using the following sprite sheet as an example.

Contents

  1. Formatting your image
  2. Adding your image
  3. Adding a new palette
  4. Adding a new pic table
  5. Adding a new ObjectEventGraphicsInfo entry
  6. Adding a define for your overworld
  7. Caveats

1. Formatting your image

Your image should be saved as a .png with a bit depth of 4, allowing for a maximum palette of 16 colours. The first colour within the palette will be transparent in game, so ensure that your background colour is in that first slot. The width and height should be a multiple of 8 (16, 32, 64 for example) and the poses should be ordered like the sprite sheet above in order to avoid having to make changes to the animation tables, although if desired making custom animations should be somewhat simple.

The overworld graphics, or object event graphics, are located in graphics/object_events/pics so it is recommended to save your image there.

2. Adding your image

Within src/data/object_events/object_event_graphics.h add a label for your image:

const u32 gObjectEventPic_Example[] = INCBIN_U32("graphics/object_events/pics/people/example.4bpp");

If you want your new overworld to use a new palette, then add that too. The palette can be generated from the .png, so you don’t need to have a separate .pal file.

const u16 gObjectEventPalette_Example[] = INCBIN_U16("graphics/object_events/pics/people/example.gbapal");

Next, you need to specify the build rules for the sprite sheet within spritesheet_rules.mk.

–mwidth and –mheight represent the size of one frame of your spritesheet, in tiles rather than pixels (1 tile is 8x8 pixels). Each frame of the example above is 32x32 pixels so the build rule becomes:

$(OBJEVENTGFXDIR)/people/example.4bpp: %.4bpp: %.png
	$(GFX) $< $@ -mwidth 4 -mheight 4

3. Adding a new palette

If your new overworld uses an existing palette then you can skip this step.

In src/event_object_movement.c you will need to add a reference to your new palette to sObjectEventSpritePalettes[], the palette requires a unique tag which we will use later. The existing tags are defined above sObjectEventSpritePalettes[].

  #define OBJ_EVENT_PAL_TAG_30 0x111F
  #define OBJ_EVENT_PAL_TAG_31 0x1120
  #define OBJ_EVENT_PAL_TAG_32 0x1121
  #define OBJ_EVENT_PAL_TAG_33 0x1122
  #define OBJ_EVENT_PAL_TAG_34 0x1123
+ #define OBJ_EVENT_PAL_EXAMPLE 0x1124
  #define OBJ_EVENT_PAL_TAG_NONE 0x11FF
const struct SpritePalette sObjectEventSpritePalettes[] = {
    // removed for brevity
    {gObjectEventPalette30, OBJ_EVENT_PAL_TAG_30},
    {gObjectEventPalette31, OBJ_EVENT_PAL_TAG_31},
    {gObjectEventPalette32, OBJ_EVENT_PAL_TAG_32},
    {gObjectEventPalette33, OBJ_EVENT_PAL_TAG_33},
    {gObjectEventPalette34, OBJ_EVENT_PAL_TAG_34},
+   {gObjectEventPalette_Example, OBJ_EVENT_PAL_EXAMPLE},
    {NULL,                  0x0000},
};

(See Caveat #1 when it comes to adding palettes.)

4. Adding a new pic table

Within src/data/object_events/object_event_pic_tables.h, add a new pic table for your overworld. We will be using the macro:

overworld_frame(ptr, width, height, frame)

Where the width and height are in tiles rather than pixels. So our new pic table becomes:

const struct SpriteFrameImage gObjectEventPicTable_Example[] = {
    overworld_frame(gObjectEventPic_Example, 4, 4, 0),
    overworld_frame(gObjectEventPic_Example, 4, 4, 1),
    overworld_frame(gObjectEventPic_Example, 4, 4, 2),
    overworld_frame(gObjectEventPic_Example, 4, 4, 3),
    overworld_frame(gObjectEventPic_Example, 4, 4, 4),
    overworld_frame(gObjectEventPic_Example, 4, 4, 5),
    overworld_frame(gObjectEventPic_Example, 4, 4, 6),
    overworld_frame(gObjectEventPic_Example, 4, 4, 7),
    overworld_frame(gObjectEventPic_Example, 4, 4, 8),
};

If the frames in your sprite sheet are in a different order to the example shown above, then this table will need re-arranging.

5. Adding a new ObjectEventGraphicsInfo entry

Within src/data/object_events/object_event_graphics_info.h, add a new entry for your overworld.

const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_Example = { 
    .tileTag = 0xFFFF,
    .paletteTag = OBJ_EVENT_PAL_EXAMPLE,
    .reflectionPaletteTag = OBJ_EVENT_PAL_TAG_NONE,
    .size = 512,
    .width = 32,
    .height = 32,
    .paletteSlot = 0,
    .shadowSize = SHADOW_SIZE_M,
    .inanimate = FALSE,
    .disableReflectionPaletteLoad = FALSE,
    .tracks = TRACKS_FOOT,
    .oam = &gObjectEventBaseOam_32x32,
    .subspriteTables = sOamTables_32x32,
    .anims = sAnimTable_Standard,
    .images = gObjectEventPicTable_Example,
    .affineAnims = gDummySpriteAffineAnimTable,
};
  • .tileTag is always 0xFFFF.
  • .paletteTag is the standard palette used by the overworld sprite.
  • .reflectionPaletteTag is the reflection palette used, for NPCs this is typically OBJ_EVENT_PAL_TAG_NONE.
  • .size refers to the size in bytes of 1 frame of your overworld. It can be calculated by multiplying the width by height by bit depth (in this case 32x32x4) to get the size in bits, then divide by 8 to convert it into bytes.
  • .width is the width in pixels of one frame of your sprite sheet.
  • .height is the height in pixels of one frame of your sprite sheet.
  • .paletteSlot: There are 16 available palette slots. Slot 0 is used by the main character you play as, and slot 10 is used by special NPCs such as the cable car or invisible Kecleon. Note that whilst slot 0 is used by the main character, there are duplicate ObjectEventGraphicsInfo entries for your rival, which uses slot 10 instead so as not to load the wrong palette when both Brendan and May are on screen. (See Caveat #1 for more details.) If using an existing paletteTag, the paletteSlot will likely need to match the paletteSlot of other overworlds using the same paletteTag.
  • .shadowSize When a movement taken by an NPC creates a shadow (e.g. jumping over a ledge), this determines what shadow sprite will be drawn under it.
  • .inanimate If set to TRUE then your overworld won't be animated, used for NPCs such as the dolls or cushions.
  • .disableReflectionPaletteLoad disables loading a reflection palette, e.g. for surfing or underwater overworlds.
  • .tracks determines what tracks, if any, are left when moving through sand or similar tiles.
  • .oam The oam tables are located in src/data/object_events/base_oam.h and are size dependent.
  • .subspriteTables The subsprite tables are located in src/data/object_events/object_event_subsprites.h and are size dependent.
  • .anims The anim tables are located in src/data/object_events/object_event_anims.h. Most NPCs use sAnimTable_Standard, which allows for the walking animation.
  • .images refers to the pic table we just created.
  • .affineAnims allows for effects such as rotation and scale. Most overworlds use gDummySpriteAffineAnimTable, but Groudon and Kyogre do use such effects.

Next, we need to add a pointer to our new ObjectEventGraphicsInfo and amend the table of pointers within src/data/object_events/object_event_graphics_info_pointers.h.

First, add a declaration before the table of pointers (gObjectEventGraphicsInfoPointers), then add an entry to the table itself.

const struct ObjectEventGraphicsInfo gObjectEventGraphicsInfo_Example;
const struct ObjectEventGraphicsInfo *const gObjectEventGraphicsInfoPointers[NUM_OBJ_EVENT_GFX] = {
    // removed for brevity
    [OBJ_EVENT_GFX_BRANDON] =         &gObjectEventGraphicsInfo_Brandon,
    [OBJ_EVENT_GFX_LINK_RS_BRENDAN] = &gObjectEventGraphicsInfo_RubySapphireBrendan,
    [OBJ_EVENT_GFX_LINK_RS_MAY] =     &gObjectEventGraphicsInfo_RubySapphireMay,
    [OBJ_EVENT_GFX_LUGIA] =           &gObjectEventGraphicsInfo_Lugia,
    [OBJ_EVENT_GFX_HOOH] =            &gObjectEventGraphicsInfo_HoOh,
+   [OBJ_EVENT_GFX_EXAMPLE] =         &gObjectEventGraphicsInfo_Example,
};

6. Adding a define for your overworld

Finally, we want to add a define for our overworld in include/constants/event_objects.h so we can use that as opposed to raw numbers. As we've added our overworld to the end of the standard overworld table (i.e before gMauvilleOldManGraphicsInfoPointers[]), we will need to add our define between OBJ_EVENT_GFX_HOOH and NUM_OBJ_EVENT_GFX and update both of their associated numbers.

Original
#define OBJ_EVENT_GFX_LUGIA                      237
#define OBJ_EVENT_GFX_HOOH                       238

#define NUM_OBJ_EVENT_GFX                        239
Updated
#define OBJ_EVENT_GFX_LUGIA                      237
#define OBJ_EVENT_GFX_HOOH                       238
#define OBJ_EVENT_GFX_EXAMPLE                    239

#define NUM_OBJ_EVENT_GFX                        240

(See Caveat #2 when it comes to more than 256 overworld objects.)

Caveats

  1. The GBA has a maximum of 16 palettes it can load at once (these are the palette slots), and the vanilla overworld palette system has those divided up pretty rigidly: Palette slot 0 is reserved for the player palette, and slot 1 is for the reflection. Palette slots 2, 3, 4, and 5 are shared between nearly all overworld objects, and slots 6, 7, 8, and 9 are for reflections of each of those. Slot 10 and 11 are used for palette and reflection of "special" objects, which include the rival character (ie, the other player palette) and the overworld legendaries; only one of these can be in the same map at the same time, because fighting for that palette will occur otherwise. The remaining 4 slots are generally used as needed for screen effects such as dirt clouds when you hop a ledge, rustling tall grass, or weather effects.
    If you choose to implement the dynamic overworld palette system, all of these rules go away, but the system can come with its own caveats.

  2. By default, there is a limit of 256 overworld graphics objects (0-255) simply due to the graphics id being stored and passed around as a u8. This limit can be expanded with some work: Here is an example of the limit being raised to an effective limit of 65536.

Clone this wiki locally