Edit the male and female player colors - pret/pokecrystal GitHub Wiki
The male player, Chris, is red, and the female player, Kris, is blue. This affects color palettes in a number of different places, including the introductory naming screen, the trainer card, the overworld sprite, the Town Map sprite, and the Magnet Train cutscene. It can be tricky to find and edit all of these places.
As an example, we'll be making Chris gold (with a brown overworld sprite) and Kris teal (with a green overworld sprite).
Note: For older versions of the pokecrystal disassembly, please check the file history of this tutorial for changes as many changes have occurred since the creation of this tutorial.
Contents
- Edit the overworld sprite colors
- Edit the Pokégear colors
- Define sprite animation constants
- Edit the sprite animation data
- Use the edited sprite animation data
- Sync the Pokégear and overworld palettes
- Edit the trainer constants
- Create trainer sprite palettes
- Give the catch tutorial Dude his own palette
- Edit the trainer card colors
1. Edit the overworld sprite colors
Let's start with the overworld sprites. They don't need any custom colors, since you already have a small set of colors to choose from: red, blue, green, brown, or pink. (Although nothing actually uses pink, and you can change it to something else by editing gfx/overworld/npc_sprites.pal. I favor purple, since so many default NPCs could have used it anyway: Eusine, Will, Koga, Janine, the Psychics, the Pokémaniacs...)
Edit data/sprites/sprites.asm:
OverworldSprites:
; entries correspond to SPRITE_* constants
table_width NUM_SPRITEDATA_FIELDS, OverworldSprites
- overworld_sprite ChrisSpriteGFX, 12, WALKING_SPRITE, PAL_OW_RED
- overworld_sprite ChrisBikeSpriteGFX, 12, WALKING_SPRITE, PAL_OW_RED
+ overworld_sprite ChrisSpriteGFX, 12, WALKING_SPRITE, PAL_OW_BROWN
+ overworld_sprite ChrisBikeSpriteGFX, 12, WALKING_SPRITE, PAL_OW_BROWN
...
- overworld_sprite KrisSpriteGFX, 12, WALKING_SPRITE, PAL_OW_BLUE
- overworld_sprite KrisBikeSpriteGFX, 12, WALKING_SPRITE, PAL_OW_BLUE
+ overworld_sprite KrisSpriteGFX, 12, WALKING_SPRITE, PAL_OW_GREEN
+ overworld_sprite KrisBikeSpriteGFX, 12, WALKING_SPRITE, PAL_OW_GREEN
...
This affects the default colors of the sprites for Chris and Kris when some random object_event
uses them, but it turns out that the player's object is initialized elsewhere.
Edit engine/overworld/player_object.asm:
SpawnPlayer:
ld a, -1
ld [wObjectFollow_Leader], a
ld [wObjectFollow_Follower], a
ld a, PLAYER
ld hl, PlayerObjectTemplate
call CopyPlayerObjectTemplate
ld b, PLAYER
call PlayerSpawn_ConvertCoords
ld a, PLAYER_OBJECT
call GetMapObject
ld hl, MAPOBJECT_COLOR
add hl, bc
- ln e, PAL_NPC_RED, OBJECTTYPE_SCRIPT
+ ln e, PAL_NPC_BROWN, OBJECTTYPE_SCRIPT
ld a, [wPlayerSpriteSetupFlags]
bit PLAYERSPRITESETUP_FEMALE_TO_MALE_F, a
jr nz, .ok
ld a, [wPlayerGender]
bit PLAYERGENDER_FEMALE_F, a
jr z, .ok
- ln e, PAL_NPC_BLUE, OBJECTTYPE_SCRIPT
+ ln e, PAL_NPC_GREEN, OBJECTTYPE_SCRIPT
.ok
...
There's also a brief moment after Prof. Oak's introductory speech, when your sprite gets shrunk to overworld size and you appear in your room, which has its own color setup.
Edit engine/menus/intro_menu.asm:
- ld b, PAL_OW_RED
+ ld b, PAL_OW_BROWN
ld a, [wPlayerGender]
bit PLAYERGENDER_FEMALE_F, a
jr z, .male
- ld b, PAL_OW_BLUE
+ ld b, PAL_OW_GREEN
.male
ld a, b
Of course, before your sprite is shrunk, you see its full-size trainer picture, which will also need color changing; but we'll get to that later.
One more thing needs editing for the overworld colors. When you trade or battle in the Cable Club, a girl player will appear as a boy for compatibility reasons.
Edit maps/Pokecenter2F.asm:
- setval (PAL_NPC_RED << 4)
+ setval (PAL_NPC_BROWN << 4)
special SetPlayerPalette
- setval (PAL_NPC_BLUE << 4)
+ setval (PAL_NPC_GREEN << 4)
special SetPlayerPalette
There are two instances of PAL_NPC_RED
and three of PAL_NPC_BLUE
, all of which need editing.
2. Edit the Pokégear colors
The Pokégear's card icons, and the cities on the Town Map, change color depending on your gender.
Edit gfx/pokegear/pokegear.pal:
; city (boy)
RGB 28, 31, 20
- RGB 31, 15, 00
- RGB 15, 07, 00
+ RGB 31, 23, 06
+ RGB 23, 16, 00
RGB 00, 00, 00
Edit gfx/pokegear/pokegear_f.pal:
; city (girl)
RGB 28, 31, 20
- RGB 10, 18, 31
- RGB 13, 06, 31
+ RGB 08, 25, 24
+ RGB 00, 16, 18
RGB 00, 00, 00
I've made them gold and green respectively for the boy and girl, but these colors are independent of any of the sprite colors, so you can freely choose them.
3. Define sprite animation constants
There are various parts of the game which look like they're using standard overworld NPC sprites—the Magnet Train ride animation, the Town Map, the naming screen—but actually they're using custom animations. The sprite animation system is somewhat complicated, with layers of abstract data referring to each other, so let's take it step by step.
Edit constants/sprite_anim_constants.asm:
; SpriteAnimObjects indexes (see data/sprite_anims/objects.asm)
const_def
const SPRITE_ANIM_OBJ_PARTY_MON ; 00
...
const SPRITE_ANIM_OBJ_RED_WALK ; 0a
...
- const SPRITE_ANIM_OBJ_MAGNET_TRAIN_RED ; 15
+ const SPRITE_ANIM_OBJ_MAGNET_TRAIN_BROWN ; 15
...
- const SPRITE_ANIM_OBJ_BLUE_WALK ; 1e
+ const SPRITE_ANIM_OBJ_GREEN_WALK ; 1e
- const SPRITE_ANIM_OBJ_MAGNET_TRAIN_BLUE ; 1f
+ const SPRITE_ANIM_OBJ_MAGNET_TRAIN_GREEN ; 1f
...
const SPRITE_ANIM_OBJ_CELEBI ; 2c
+ const SPRITE_ANIM_OBJ_BROWN_WALK
DEF NUM_SPRITE_ANIM_OBJS EQU const_value
; SpriteAnimFrameData indexes (see data/sprite_anims/framesets.asm)
const_def
const SPRITE_ANIM_FRAMESET_00 ; 00
...
const SPRITE_ANIM_FRAMESET_RED_WALK ; 11
...
- const SPRITE_ANIM_FRAMESET_MAGNET_TRAIN_RED ; 1b
+ const SPRITE_ANIM_FRAMESET_MAGNET_TRAIN_BROWN ; 1b
...
- const SPRITE_ANIM_FRAMESET_BLUE_WALK ; 2d
+ const SPRITE_ANIM_FRAMESET_GREEN_WALK ; 2d
- const SPRITE_ANIM_FRAMESET_MAGNET_TRAIN_BLUE ; 2e
+ const SPRITE_ANIM_FRAMESET_MAGNET_TRAIN_GREEN ; 2e
...
const SPRITE_ANIM_FRAMESET_CELEBI_RIGHT ; 41
+ const SPRITE_ANIM_FRAMESET_BROWN_WALK
NUM_SPRITE_ANIM_FRAMESETS EQU const_value
; SpriteAnimOAMData indexes (see data/sprite_anims/oam.asm)
const_def
const SPRITE_ANIM_OAMSET_RED_WALK_1 ; 00
const SPRITE_ANIM_OAMSET_RED_WALK_2 ; 01
...
- const SPRITE_ANIM_OAMSET_MAGNET_TRAIN_RED_1 ; 41
- const SPRITE_ANIM_OAMSET_MAGNET_TRAIN_RED_2 ; 42
+ const SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BROWN_1 ; 41
+ const SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BROWN_2 ; 42
...
- const SPRITE_ANIM_OAMSET_BLUE_WALK_1 ; 63
- const SPRITE_ANIM_OAMSET_BLUE_WALK_2 ; 64
+ const SPRITE_ANIM_OAMSET_GREEN_WALK_1 ; 63
+ const SPRITE_ANIM_OAMSET_GREEN_WALK_2 ; 64
- const SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BLUE_1 ; 65
- const SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BLUE_2 ; 66
+ const SPRITE_ANIM_OAMSET_MAGNET_TRAIN_GREEN_1 ; 65
+ const SPRITE_ANIM_OAMSET_MAGNET_TRAIN_GREEN_2 ; 66
...
const SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_11 ; 8b
+ const SPRITE_ANIM_OAMSET_BROWN_WALK_1
+ const SPRITE_ANIM_OAMSET_BROWN_WALK_2
NUM_SPRITE_ANIM_OAMSETS EQU const_value
As we'll see next, the SpriteAnimObjects
entries refer to SpriteAnimFrameData
entries, the SpriteAnimFrameData
entries refer to SpriteAnimOAMData
entries, and finally the SpriteAnimOAMData
declare the actual colors that get used.
Notice that we simply rename the BLUE
constants to GREEN
, but have to add some new ones for BROWN
instead of renaming everything RED
. This is because the BLUE
data was added just for Crystal version, and it's only used for Kris, but the RED
data has certain uses besides for Chris.
4. Edit the sprite animation data
Now we'll edit the data files that correspond to those constants, as implied by the comments above each list.
Edit data/sprite_anims/objects.asm:
SpriteAnimObjects:
; entries correspond to SPRITE_ANIM_OBJ_* constants (see constants/sprite_anim_constants.asm)
table_width 3, SpriteAnimObjects
; frameset, sequence, tile
; SPRITE_ANIM_OBJ_PARTY_MON
db SPRITE_ANIM_FRAMESET_PARTY_MON, SPRITE_ANIM_FUNC_PARTY_MON, SPRITE_ANIM_DICT_DEFAULT
...
-; SPRITE_ANIM_OBJ_MAGNET_TRAIN_RED
- db SPRITE_ANIM_FRAMESET_MAGNET_TRAIN_RED, SPRITE_ANIM_FUNC_NULL, SPRITE_ANIM_DICT_DEFAULT
+; SPRITE_ANIM_OBJ_MAGNET_TRAIN_BROWN
+ db SPRITE_ANIM_FRAMESET_MAGNET_TRAIN_BROWN, SPRITE_ANIM_FUNC_NULL, SPRITE_ANIM_DICT_DEFAULT
...
-; SPRITE_ANIM_OBJ_BLUE_WALK
- db SPRITE_ANIM_FRAMESET_BLUE_WALK, SPRITE_ANIM_FUNC_NULL, SPRITE_ANIM_DICT_DEFAULT
+; SPRITE_ANIM_OBJ_GREEN_WALK
+ db SPRITE_ANIM_FRAMESET_GREEN_WALK, SPRITE_ANIM_FUNC_NULL, SPRITE_ANIM_DICT_DEFAULT
-; SPRITE_ANIM_OBJ_MAGNET_TRAIN_BLUE
- db SPRITE_ANIM_FRAMESET_MAGNET_TRAIN_BLUE, SPRITE_ANIM_FUNC_NULL, SPRITE_ANIM_DICT_DEFAULT
+; SPRITE_ANIM_OBJ_MAGNET_TRAIN_GREEN
+ db SPRITE_ANIM_FRAMESET_MAGNET_TRAIN_GREEN, SPRITE_ANIM_FUNC_NULL, SPRITE_ANIM_DICT_DEFAULT
...
; SPRITE_ANIM_OBJ_CELEBI
db SPRITE_ANIM_FRAMESET_CELEBI_LEFT, SPRITE_ANIM_FUNC_NULL, SPRITE_ANIM_DICT_DEFAULT
+; SPRITE_ANIM_OBJ_BROWN_WALK
+ db SPRITE_ANIM_FRAMESET_BROWN_WALK, SPRITE_ANIM_FUNC_NULL, SPRITE_ANIM_DICT_DEFAULT
assert_table_length NUM_SPRITE_ANIM_OBJS
Edit data/sprite_anims/framesets.asm:
SpriteAnimFrameData:
; entries correspond to SPRITE_ANIM_FRAMESET_* constants
table_width 2, SpriteAnimFrameData
dw .Frameset_00
...
dw .Frameset_RedWalk
...
- dw .Frameset_MagnetTrainRed
+ dw .Frameset_MagnetTrainBrown
...
- dw .Frameset_BlueWalk
- dw .Frameset_MagnetTrainBlue
+ dw .Frameset_GreenWalk
+ dw .Frameset_MagnetTrainGreen
...
dw .Frameset_CelebiRight
+ dw .Frameset_BrownWalk
assert_table_length NUM_SPRITE_ANIM_FRAMESETS
-.Frameset_BlueWalk:
- oamframe SPRITE_ANIM_OAMSET_BLUE_WALK_1, 8
- oamframe SPRITE_ANIM_OAMSET_BLUE_WALK_2, 8
- oamframe SPRITE_ANIM_OAMSET_BLUE_WALK_1, 8
- oamframe SPRITE_ANIM_OAMSET_BLUE_WALK_2, 8, OAM_X_FLIP
+.Frameset_GreenWalk:
+ oamframe SPRITE_ANIM_OAMSET_GREEN_WALK_1, 8
+ oamframe SPRITE_ANIM_OAMSET_GREEN_WALK_2, 8
+ oamframe SPRITE_ANIM_OAMSET_GREEN_WALK_1, 8
+ oamframe SPRITE_ANIM_OAMSET_GREEN_WALK_2, 8, OAM_X_FLIP
oamrestart
-.Frameset_MagnetTrainBlue:
- oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BLUE_1, 8
- oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BLUE_2, 8
- oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BLUE_1, 8
- oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BLUE_2, 8, OAM_X_FLIP
+.Frameset_MagnetTrainGreen:
+ oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_GREEN_1, 8
+ oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_GREEN_2, 8
+ oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_GREEN_1, 8
+ oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_GREEN_2, 8, OAM_X_FLIP
oamrestart
-.Frameset_MagnetTrainRed:
- oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_RED_1, 8
- oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_RED_2, 8
- oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_RED_1, 8
- oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_RED_2, 8, OAM_X_FLIP
+.Frameset_MagnetTrainBrown:
+ oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BROWN_1, 8
+ oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BROWN_2, 8
+ oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BROWN_1, 8
+ oamframe SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BROWN_2, 8, OAM_X_FLIP
oamrestart
+.Frameset_BrownWalk:
+ oamframe SPRITE_ANIM_OAMSET_BROWN_WALK_1, 8
+ oamframe SPRITE_ANIM_OAMSET_BROWN_WALK_2, 8
+ oamframe SPRITE_ANIM_OAMSET_BROWN_WALK_1, 8
+ oamframe SPRITE_ANIM_OAMSET_BROWN_WALK_2, 8, OAM_X_FLIP
+ oamrestart
And edit data/sprite_anims/oam.asm:
MACRO spriteanimoam
; vtile offset, data pointer
db \1
dw \2
ENDM
SpriteAnimOAMData:
; entries correspond to SPRITE_ANIM_OAMSET_* constants (see constants/sprite_anim_constants.asm)
table_width 3, SpriteAnimOAMData
spriteanimoam $00, .OAMData_RedWalk ; SPRITE_ANIM_OAMSET_RED_WALK_1
spriteanimoam $04, .OAMData_RedWalk ; SPRITE_ANIM_OAMSET_RED_WALK_2
...
- spriteanimoam $00, .OAMData_MagnetTrainRed ; SPRITE_ANIM_OAMSET_MAGNET_TRAIN_RED_1
- spriteanimoam $04, .OAMData_MagnetTrainRed ; SPRITE_ANIM_OAMSET_MAGNET_TRAIN_RED_2
+ spriteanimoam $00, .OAMData_MagnetTrainBrown ; SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BROWN_1
+ spriteanimoam $04, .OAMData_MagnetTrainBrown ; SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BROWN_2
...
- spriteanimoam $00, .OAMData_BlueWalk ; SPRITE_ANIM_OAMSET_BLUE_WALK_1
- spriteanimoam $04, .OAMData_BlueWalk ; SPRITE_ANIM_OAMSET_BLUE_WALK_2
+ spriteanimoam $00, .OAMData_GreenWalk ; SPRITE_ANIM_OAMSET_GREEN_WALK_1
+ spriteanimoam $04, .OAMData_GreenWalk ; SPRITE_ANIM_OAMSET_GREEN_WALK_2
- spriteanimoam $00, .OAMData_MagnetTrainBlue ; SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BLUE_1
- spriteanimoam $04, .OAMData_MagnetTrainBlue ; SPRITE_ANIM_OAMSET_MAGNET_TRAIN_BLUE_2
+ spriteanimoam $00, .OAMData_MagnetTrainGreen ; SPRITE_ANIM_OAMSET_MAGNET_TRAIN_GREEN_1
+ spriteanimoam $04, .OAMData_MagnetTrainGreen ; SPRITE_ANIM_OAMSET_MAGNET_TRAIN_GREEN_2
...
spriteanimoam $00, .OAMData_GameFreakLogo4_11 ; SPRITE_ANIM_OAMSET_GAMEFREAK_LOGO_11
+ spriteanimoam $00, .OAMData_BrownWalk ; SPRITE_ANIM_OAMSET_BROWN_WALK_1
+ spriteanimoam $04, .OAMData_BrownWalk ; SPRITE_ANIM_OAMSET_BROWN_WALK_2
assert_table_length NUM_SPRITE_ANIM_OAMSETS
-.OAMData_BlueWalk:
+.OAMData_GreenWalk:
db 4
- dbsprite -1, -1, 0, 0, $00, PAL_OW_BLUE
- dbsprite 0, -1, 0, 0, $01, PAL_OW_BLUE
- dbsprite -1, 0, 0, 0, $02, PAL_OW_BLUE
- dbsprite 0, 0, 0, 0, $03, PAL_OW_BLUE
+ dbsprite -1, -1, 0, 0, $00, PAL_OW_GREEN
+ dbsprite 0, -1, 0, 0, $01, PAL_OW_GREEN
+ dbsprite -1, 0, 0, 0, $02, PAL_OW_GREEN
+ dbsprite 0, 0, 0, 0, $03, PAL_OW_GREEN
-.OAMData_MagnetTrainBlue:
+.OAMData_MagnetTrainGreen:
db 4
- dbsprite -1, -1, 0, 0, $00, PAL_OW_BLUE | PRIORITY
- dbsprite 0, -1, 0, 0, $01, PAL_OW_BLUE | PRIORITY
- dbsprite -1, 0, 0, 0, $02, PAL_OW_BLUE | PRIORITY
- dbsprite 0, 0, 0, 0, $03, PAL_OW_BLUE | PRIORITY
+ dbsprite -1, -1, 0, 0, $00, PAL_OW_GREEN | PRIORITY
+ dbsprite 0, -1, 0, 0, $01, PAL_OW_GREEN | PRIORITY
+ dbsprite -1, 0, 0, 0, $02, PAL_OW_GREEN | PRIORITY
+ dbsprite 0, 0, 0, 0, $03, PAL_OW_GREEN | PRIORITY
+.OAMData_BrownWalk:
+ db 4
+ dbsprite -1, -1, 0, 0, $00, PAL_OW_BROWN
+ dbsprite 0, -1, 0, 0, $01, PAL_OW_BROWN
+ dbsprite -1, 0, 0, 0, $02, PAL_OW_BROWN
+ dbsprite 0, 0, 0, 0, $03, PAL_OW_BROWN
+
+.OAMData_MagnetTrainBrown:
+ db 4
+ dbsprite -1, -1, 0, 0, $00, PAL_OW_BROWN | PRIORITY
+ dbsprite 0, -1, 0, 0, $01, PAL_OW_BROWN | PRIORITY
+ dbsprite -1, 0, 0, 0, $02, PAL_OW_BROWN | PRIORITY
+ dbsprite 0, 0, 0, 0, $03, PAL_OW_BROWN | PRIORITY
Most of those changes were just renaming labels to avoid confusion. The only part that has a real effect on the game is the final part where the colors change.
5. Use the edited sprite animation data
This will mostly be changing RED
to BROWN
and BLUE
to GREEN
, now that we have the appropriate data definitions for it.
Edit engine/events/magnet_train.asm:
.InitPlayerSpriteAnim:
ld d, (8 + 2) * TILE_WIDTH + 5
ld a, [wMagnetTrainPlayerSpriteInitX]
ld e, a
- ld b, SPRITE_ANIM_OBJ_MAGNET_TRAIN_RED
+ ld b, SPRITE_ANIM_OBJ_MAGNET_TRAIN_BROWN
ldh a, [rSVBK]
push af
ld a, BANK(wPlayerGender)
ldh [rSVBK], a
ld a, [wPlayerGender]
bit PLAYERGENDER_FEMALE_F, a
jr z, .got_gender
- ld b, SPRITE_ANIM_OBJ_MAGNET_TRAIN_BLUE
+ ld b, SPRITE_ANIM_OBJ_MAGNET_TRAIN_GREEN
Edit engine/pokegear/pokegear.asm:
PokegearMap_InitPlayerIcon:
push af
depixel 0, 0
- ld b, SPRITE_ANIM_OBJ_RED_WALK
+ ld b, SPRITE_ANIM_OBJ_BROWN_WALK
ld a, [wPlayerGender]
bit PLAYERGENDER_FEMALE_F, a
jr z, .got_gender
- ld b, SPRITE_ANIM_OBJ_BLUE_WALK
+ ld b, SPRITE_ANIM_OBJ_GREEN_WALK
.got_gender
...
Pokedex_GetArea:
...
.ShowPlayerLoop:
...
push bc
- ld c, PAL_OW_RED
+ ld c, PAL_OW_BROWN
ld a, [wPlayerGender]
bit PLAYERGENDER_FEMALE_F, a
jr z, .male
- inc c ; PAL_OW_BLUE
+ ld c, PAL_OW_GREEN
.male
ld a, c
ld [hli], a ; attributes
pop bc
jr .ShowPlayerLoop
TownMapPlayerIcon:
; Draw the player icon at town map location in a
...
; Animation/palette
depixel 0, 0
- ld b, SPRITE_ANIM_OBJ_RED_WALK ; Male
+ ld b, SPRITE_ANIM_OBJ_BROWN_WALK ; Male
ld a, [wPlayerGender]
bit PLAYERGENDER_FEMALE_F, a
jr z, .got_gender
- ld b, SPRITE_ANIM_OBJ_BLUE_WALK ; Female
+ ld b, SPRITE_ANIM_OBJ_GREEN_WALK ; Female
.got_gender
...
Edit engine/menus/naming_screen.asm:
.LoadSprite:
...
ld b, SPRITE_ANIM_OBJ_RED_WALK
+ ld a, d
+ cp HIGH(ChrisSpriteGFX)
+ jr nz, .not_chris
+ ld a, e
+ cp LOW(ChrisSpriteGFX)
+ jr nz, .not_chris
+ ld b, SPRITE_ANIM_OBJ_BROWN_WALK
+ jr .not_kris
+.not_chris
ld a, d
cp HIGH(KrisSpriteGFX)
jr nz, .not_kris
ld a, e
cp LOW(KrisSpriteGFX)
jr nz, .not_kris
- ld b, SPRITE_ANIM_OBJ_BLUE_WALK
+ ld b, SPRITE_ANIM_OBJ_GREEN_WALK
.not_kris
ld a, b
depixel 4, 4, 4, 0
call InitSpriteAnimStruct
ret
Notice how the naming screen does not simply change SPRITE_ANIM_OBJ_RED_WALK
to SPRITE_ANIM_OBJ_BROWN_WALK
; it only does so if the current sprite is using ChrisSpriteGFX
. That's because the default SPRITE_ANIM_OBJ_RED_WALK
also gets used for naming other things, including your rival, your Pokémon, and Bill's PC boxes.
6. Sync the Pokégear and overworld palettes
The previous step should make the Town Map use the correct colors, but there's actually one more thing to do for it. The colors available within the party menu and Pokégear aren't the same as the ones for the overworld. By default, they didn't have to be; all the party Pokémon icons are red, and the Town Map icons can only be red or blue. But it's more convenient to have all the overworld colors available, and it won't break anything to do so.
Edit gfx/stats/party_menu_ob.pal:
RGB 27, 31, 27
RGB 31, 19, 10
- RGB 31, 07, 04
+ RGB 31, 07, 01
RGB 00, 00, 00
RGB 27, 31, 27
RGB 31, 19, 10
- RGB 10, 14, 20
+ RGB 10, 09, 31
RGB 00, 00, 00
RGB 27, 31, 27
RGB 31, 19, 10
- RGB 31, 07, 04
+ RGB 07, 23, 03
RGB 00, 00, 00
RGB 27, 31, 27
RGB 31, 19, 10
- RGB 31, 07, 04
+ RGB 15, 10, 03
RGB 00, 00, 00
RGB 27, 31, 27
RGB 31, 19, 10
- RGB 31, 07, 04
+ RGB 30, 10, 06
RGB 00, 00, 00
...
These are the same red, blue, green, brown, and pink colors as in gfx/overworld/npc_sprites.pal. So if you edit anything in one file, make the same edit in the other.
7. Edit the trainer constants
We're finally getting to edit the "trainer sprite" colors. Of course, the players Chris and Kris don't really have trainer data defined, but they use the same color table as trainers for their intro sprites, Trainer Cards, and battle back sprites.
Edit constants/trainer_constants.asm:
; trainer class ids
; `trainerclass` indexes are for:
; - TrainerClassNames (see data/trainers/class_names.asm)
; - TrainerClassAttributes (see data/trainers/attributes.asm)
; - TrainerClassDVs (see data/trainers/dvs.asm)
; - TrainerGroups (see data/trainers/party_pointers.asm)
; - TrainerEncounterMusic (see data/trainers/encounter_music.asm)
; - TrainerPicPointers (see data/trainers/pic_pointers.asm)
; - TrainerPalettes (see data/trainers/palettes.asm)
; - BTTrainerClassSprites (see data/trainers/sprites.asm)
; - BTTrainerClassGenders (see data/trainers/genders.asm)
; trainer constants are Trainers indexes, for the sub-tables of TrainerGroups (see data/trainers/parties.asm)
CHRIS EQU __trainer_class__
trainerclass TRAINER_NONE ; 0
const PHONECONTACT_MOM
const PHONECONTACT_BIKESHOP
const PHONECONTACT_BILL
const PHONECONTACT_ELM
const PHONECONTACT_BUENA
NUM_NONTRAINER_PHONECONTACTS EQU const_value - 1
-DEF KRIS EQU __trainer_class__
trainerclass FALKNER ; 1
const FALKNER1
...
trainerclass MYSTICALMAN ; 43
const EUSINE
+DEF KRIS EQU __trainer_class__
NUM_TRAINER_CLASSES EQU __trainer_class__ - 1
CHRIS
corresponds to the unusable TRAINER_NONE
, which is fine. But KRIS
was actually just an alias for FALKNER
—they use the exact same sprite color. Here we've moved KRIS
to be after the very last trainer class. Just like CHRIS
, she won't need any trainer data except a color palette, which we'll do next.
8. Create trainer sprite palettes
Create gfx/player/chris.pal:
+ RGB 30, 21, 14
+ RGB 22, 15, 00
And create gfx/player/kris.pal:
+ RGB 30, 22, 17
+ RGB 00, 18, 15
Then edit data/trainers/palettes.asm:
TrainerPalettes:
; entries correspond to trainer classes
; Each .gbcpal is generated from the corresponding .png, and
; only the middle two colors are included, not black or white.
table_width PAL_COLOR_SIZE * 2, TrainerPalettes
-PlayerPalette: ; Chris uses the same colors as Cal
-INCBIN "gfx/trainers/cal.gbcpal", middle_colors
+PlayerPalette:
+INCLUDE "gfx/player/chris.pal"
+
-KrisPalette: ; Kris shares Falkner's palette
INCBIN "gfx/trainers/falkner.gbcpal", middle_colors
...
+DudePalette:
INCBIN "gfx/trainers/cal.gbcpal", middle_colors
...
INCBIN "gfx/trainers/mysticalman.gbcpal", middle_colors
+
+KrisPalette:
+INCLUDE "gfx/player/kris.pal"
- assert_table_length NUM_TRAINER_CLASSES + 1
+ assert_table_length NUM_TRAINER_CLASSES + 2
Previously, Chris was using the same colors as Cal (the boy player lookalike whom you battle in Viridian City's Trainer House); and Kris was literally using Falkner's palette (which makes sense, given that KRIS
and FALKNER
were equivalent). Now they both have their own independent palettes.
Notice how they INCLUDE
.pal files, not INCBIN
.gbcpal files. That's because the .gbcpal files are automatically generated from trainer sprites' .png files, whereas we manually created the players' palettes.
Remember, Chris (the boy player) has to be the first entry, and Kris (the girl player) has to be the last. The ChrisPalette
and KrisPalette
labels are sometimes used to directly access their palettes, but sometimes the game relies on their index order. So if you add any new trainer classes, make sure they stay in order.
Also note the new DudePalette
label. Before editing the trainer card, let's see how that gets used.
9. Give the catch tutorial Dude his own palette
When the Dude on Route 29 shows you how to catch Pokémon, his back sprite shares the same color as the player (red for a boy, blue for a girl), even though his overworld sprite is always red. Now that we're changing the player colors, it would be nice if the Dude has a consistent color of his own.
Edit engine/gfx/color.asm:
GetPlayerOrMonPalettePointer:
and a
jp nz, GetMonNormalOrShinyPalettePointer
ld a, [wPlayerSpriteSetupFlags]
bit PLAYERSPRITESETUP_FEMALE_TO_MALE_F, a
jr nz, .male
+ ld a, [wBattleType]
+ cp BATTLETYPE_TUTORIAL
+ jr z, .dude
ld a, [wPlayerGender]
and a
jr z, .male
ld hl, KrisPalette
ret
.male
ld hl, PlayerPalette
ret
+
+.dude
+ ld hl, DudePalette
+ ret
I placed the DudePalette
label so that he shares Cal's palette, since that's what he used originally, but you can put that label anywhere. It doesn't even have to be among the trainer colors. If your catch tutorial NPC needs a custom color, just define it with two RGB
macros, the same as chris.pal and kris.pal, and INCLUDE
it with the DudePalette
label after the GetPlayerOrMonPalettePointer
routine.
Anyway, it's time for the last colors to change: the trainer card.
10. Edit the trainer card colors
The trainer card "cheats" a little bit. The Game Boy Color hardware only allows eight palettes at a time for BG tiles, as we can see with BGB's VRAM viewer:
But the trainer card wants ten: one for the player's sprite, one for the border, and eight unique Gym Leader faces. So what the game designers did is pick two pairs of those to share palettes. We already saw how Kris and Falkner used the exact same palette, and in fact, the trainer card used that palette for Clair's face too, even though her trainer sprite is a different shade of blue.
But now that Kris is her own shade of green, we'll have to rethink this design. In the example below, I chose to make Falkner and Clair share a palette, and Chuck and Pryce share a palette. Depending on how your Gym Leaders look, you may have a better choice available.
Edit engine/gfx/cgb_layouts.asm:
_CGB_TrainerCard:
ld de, wBGPals1
xor a ; CHRIS
call GetTrainerPalettePointer
call LoadPalette_White_Col1_Col2_Black
- ld a, FALKNER ; KRIS
+ ld a, KRIS
call GetTrainerPalettePointer
call LoadPalette_White_Col1_Col2_Black
ld a, BUGSY
call GetTrainerPalettePointer
call LoadPalette_White_Col1_Col2_Black
ld a, WHITNEY
call GetTrainerPalettePointer
call LoadPalette_White_Col1_Col2_Black
ld a, MORTY
call GetTrainerPalettePointer
call LoadPalette_White_Col1_Col2_Black
- ld a, CHUCK
+ ld a, FALKNER ; CLAIR
call GetTrainerPalettePointer
call LoadPalette_White_Col1_Col2_Black
ld a, JASMINE
call GetTrainerPalettePointer
call LoadPalette_White_Col1_Col2_Black
- ld a, PRYCE
+ ld a, PRYCE ; CHUCK
call GetTrainerPalettePointer
call LoadPalette_White_Col1_Col2_Black
ld a, PREDEFPAL_CGB_BADGE
call GetPredefPal
call LoadHLPaletteIntoDE
...
.got_gender2
call FillBoxCGB
- ; top-right corner still uses the border's palette
- hlcoord 18, 1, wAttrmap
- ld [hl], $1
hlcoord 2, 11, wAttrmap
lb bc, 2, 4
- ld a, $1 ; falkner
+ ld a, $5 ; falkner
call FillBoxCGB
hlcoord 6, 11, wAttrmap
lb bc, 2, 4
ld a, $2 ; bugsy
call FillBoxCGB
hlcoord 10, 11, wAttrmap
lb bc, 2, 4
ld a, $3 ; whitney
call FillBoxCGB
hlcoord 14, 11, wAttrmap
lb bc, 2, 4
ld a, $4 ; morty
call FillBoxCGB
hlcoord 2, 14, wAttrmap
lb bc, 2, 4
- ld a, $5 ; chuck
+ ld a, $7 ; chuck
call FillBoxCGB
hlcoord 6, 14, wAttrmap
lb bc, 2, 4
ld a, $6 ; jasmine
call FillBoxCGB
hlcoord 10, 14, wAttrmap
lb bc, 2, 4
ld a, $7 ; pryce
call FillBoxCGB
- ; clair uses kris's palette
- ld a, [wPlayerGender]
- and a
- push af
- jr z, .got_gender3
hlcoord 14, 14, wAttrmap
lb bc, 2, 4
- ld a, $1
+ ld a, $5 ; clair
call FillBoxCGB
-.got_gender3
- pop af
- ld c, $0
- jr nz, .got_gender4
- inc c
-.got_gender4
- ld a, c
+ ; top-right corner still uses the border's palette
hlcoord 18, 1, wAttrmap
+ ld a, [wPlayerGender]
+ and a
+ ld a, $1 ; kris
+ jr z, .got_gender3
+ ld a, $0 ; chris
+.got_gender3
ld [hl], a
call ApplyAttrmap
call ApplyPals
ld a, $1
ldh [hCGBPalUpdate], a
ret
Basically this routine has two stages: it loads eight trainers' palettes with GetTrainerPalettePointer
and LoadPalette_White_Col1_Col2_Black
, and then it applies those palettes to rectangular areas of the 20x18 screen with FillBoxCGB
. It happens to always load both the boy and girl palettes, using one for the player's sprite and the other for the card border, but you can always change that logic if you want a different border color. Just remember that only eight palettes in total can be used.
Anyway, with that, we're all done. Every player-related aspect of the game is recolored for both the boy and girl options.