-
Notifications
You must be signed in to change notification settings - Fork 92
Add female player character
In the sequel the player has the option to play as a female character, Mint. We'll see how we can port that feature to this game using event flags.
- Add female player graphics
- Create a new portrait
- Create a new OW sprite
- Add new event flag for player gender
- Create gender selection screen
- Make default name gender-specific
- Adjust player portrait
- Adjust player OW sprite
- Adjust Main Menu screen
As a first step we will add the new graphics. We will be using the following image for the portrait, place it in gfx/duelists/
and name it mint.png
:
Next the overworld sprite. Place it in gfx/overworld_sprites/
and name it mint.png
:
Since all Gfx
sections are pretty full, we will create a new section in ROM where we will put these graphics. Also we will add a palette for the new portrait. Edit src/gfx.asm:
FightingGfx::
dw $04
INCBIN "gfx/titlescreen/energies/fighting.2bpp"
+
+SECTION "Gfx 13", ROMX
+
+MintGfx::
+ dw 36
+ INCBIN "gfx/duelists/mint.2bpp"
+
+OWMintGfx::
+ dw $14
+ INCBIN "gfx/overworld_sprites/mint.2bpp"
+
+Palette161::
+ db 0
+ db 1
+
+ rgb 28, 28, 24
+ rgb 28, 16, 12
+ rgb 4, 8, 28
+ rgb 0, 0, 8
+
SECTION "Anims 1", ROMX
INCLUDE "data/duel/animations/anims1.asm"
And to include this section in ROM let's use bank $2f
, which is empty. Edit src/layout.link:
"Palettes1"
ROMX $2e
"Palettes2"
+ROMX $2f
+ "Gfx 13"
ROMX $31
"Card Gfx 1"
ROMX $32
Let's put everything in place to use the new portrait. For the tileset edit src/constants/tileset_constants.asm:
const TILESET_JESSICA ; $54
const TILESET_STEPHANIE ; $55
const TILESET_AARON ; $56
+ const TILESET_MINT ; $57
DEF NUM_TILESETS EQU const_value
And edit src/engine/gfx/tilesets.asm:
tileset JessicaGfx, 36 ; TILESET_JESSICA
tileset StephanieGfx, 36 ; TILESET_STEPHANIE
tileset AaronGfx, 36 ; TILESET_AARON
+ tileset MintGfx, 36 ; TILESET_MINT
assert_table_length NUM_TILESETS
For the palette edit src/constants/palette_constants.asm:
const PALETTE_158 ; $9e
const PALETTE_159 ; $9f
const PALETTE_160 ; $a0
+ const PALETTE_161 ; $a1
DEF NUM_PALETTES EQU const_value
And edit src/data/palette_pointers.asm:
palette_pointer Palette158, 1, 0 ; PALETTE_158
palette_pointer Palette159, 1, 0 ; PALETTE_159
palette_pointer Palette160, 1, 0 ; PALETTE_160
+ palette_pointer Palette161, 1, 0 ; PALETTE_161
assert_table_length NUM_PALETTES
For the portrait edit src/constants/npc_constants.asm:
const STEPHANIE_PIC ; $28
const AARON_PIC ; $29
const LINK_OPP_PIC ; $2a
+ const MINT_PIC ; $2b
DEF NUM_PICS EQU const_value
And finally we put everything together in src/data/duel/portraits.asm:
portrait TILESET_STEPHANIE, PALETTE_159, SGBData_StephaniePortraitPals ; STEPHANIE_PIC
portrait TILESET_AARON, PALETTE_160, SGBData_AaronPortraitPals ; AARON_PIC
portrait TILESET_PLAYER, PALETTE_120, SGBData_LinkOpponentPortraitPals ; LINK_OPP_PIC
+ portrait TILESET_MINT, PALETTE_161, SGBData_PlayerPortraitPals ; MINT_PIC
assert_table_length NUM_PICS
You may notice that we used the default player portrait pals for the SGB,
SGBData_PlayerPortraitPals
. The reason for this is that creating new SGB palettes is more involved and not as streamlined as regular palettes. We shall keep it as the default colours for the sake of simplicity.
We are now able to use MINT_PIC
to refer to this newly created portrait.
Similar to the previous step, we will create a new constant and pointer to the graphics related to the OW sprites. Edit src/constants/sprite_constants.asm:
const SPRITE_LIGHTNING ; $6f
const SPRITE_PSYCHIC ; $70
const SPRITE_FIGHTING ; $71
+ const SPRITE_OW_MINT ; $72
DEF NUM_SPRITES EQU const_value
Next edit src/engine/gfx/sprites.asm:
gfx_pointer LightningGfx, $04 ; SPRITE_LIGHTNING
gfx_pointer PsychicGfx, $04 ; SPRITE_PSYCHIC
gfx_pointer FightingGfx, $04 ; SPRITE_FIGHTING
+ gfx_pointer OWMintGfx, $14 ; SPRITE_OW_MINT
assert_table_length NUM_SPRITES
Here, SPRITE_OW_MINT
will now refer to the new OW graphics.
We will implement this in code by storing the player's gender in an event flag. This has the advantage that event flags are loaded and written to SRAM by the game by default, and they are as compact as possible to relay the information they represent. In our case, we need only a single bit: 0 for male, 1 for female. Edit src/constants/script_constants.asm and add a new event to the end of the list:
const EVENT_CONSOLE ; $74
const EVENT_SAM_MENU_CHOICE ; $75
const EVENT_AARON_DECK_MENU_CHOICE ; $76
+ const EVENT_PLAYER_GENDER ; $77
DEF NUM_EVENT_FLAGS EQU const_value
DEF EVENT_VAR_BYTES EQU $40
Next we will add it to the EventVarMasks
list. Edit src/engine/overworld/scripting.asm:
EventVarMasks:
...
event_def $1b, %11111111 ; EVENT_CONSOLE
event_def $1c, %11110000 ; EVENT_SAM_MENU_CHOICE
event_def $1c, %00001111 ; EVENT_AARON_DECK_MENU_CHOICE
+ event_def $1d, %00000001 ; EVENT_PLAYER_GENDER
assert_table_length NUM_EVENT_FLAGS
This makes it so that the lowest significant bit of byte $1d
in wEventVars
represents the player's gender choice.
Clearly the main point is that the player has a choice when starting a new game to select a gender. For this example we will present a screen similar to Pokémon TCG 2, before the player inputs their name. Create a new file src/engine/menus/choose_gender.asm:
PlayerGenderSelection:
; setup the screen
xor a
ld [wTileMapFill], a
call EmptyScreen
call ZeroObjectPositions
ld a, $01
ld [wVBlankOAMCopyToggle], a
call LoadSymbolsFont
lb de, $38, $bf
call SetupText
; draw male portrait
ld a, PLAYER_PIC
ld [wCurPortrait], a
ld a, TILEMAP_PLAYER
lb bc, 2, 4
call DrawPortrait
; draw female portrait
ld a, MINT_PIC
ld [wCurPortrait], a
ld a, TILEMAP_OPPONENT
lb bc, 12, 4
call DrawPortrait
; print text
ld hl, .TextItems
call PlaceTextItems
ldtx hl, AreYouBoyOrGirlText
call DrawWideTextBox_PrintText
; set parameters for the cursor
lb de, 3, 2 ; cursor x and y
lb bc, SYM_CURSOR_R, SYM_SPACE
call SetCursorParametersForTextBox
; start loop for selection
ld a, [wCurMenuItem]
jr .refresh_menu
.loop_input
call DoFrame
call RefreshMenuCursor
ldh a, [hKeysPressed]
bit A_BUTTON_F, a
jr nz, .selection_made
ldh a, [hDPadHeld]
and D_RIGHT | D_LEFT
jr z, .loop_input
ld a, SFX_CURSOR
call PlaySFX
call EraseCursor
ld hl, wCurMenuItem
ld a, [hl]
xor $1 ; toggle selected gender
ld [hl], a
.refresh_menu
or a
ld a, 3 ; "Boy" cursor x
jr z, .got_cursor_x
ld a, 13 ; "Girl" cursor x
.got_cursor_x
ld [wMenuCursorXOffset], a
xor a
ld [wCursorBlinkCounter], a
jr .loop_input
.selection_made
; set the gender event value
ld a, [wCurMenuItem]
or a
ld a, EVENT_PLAYER_GENDER
jr nz, .female
farcall ZeroOutEventValue ; bit unset
ret
.female
farcall MaxOutEventValue ; bit set
ret
.TextItems:
textitem 4, 2, BoyText
textitem 14, 2, GirlText
db $ff
The new texts used here are the following (check here for a tutorial on adding new texts):
AreYouBoyOrGirlText:
text "Are you a boy"
line "or a girl?"
done
BoyText:
text "Boy"
done
GirlText:
text "Girl"
done
And include this file in src/main.asm:
INCLUDE "engine/menus/wait_keys.asm"
INCLUDE "engine/gfx/default_palettes.asm"
INCLUDE "engine/menus/naming.asm"
+INCLUDE "engine/menus/choose_gender.asm"
SECTION "Sprite Animations", ROMX
INCLUDE "engine/gfx/sprite_animations.asm"
In summary, the code sets up everything needed to print text and show the portraits, then enters a loop to wait for player selection. When the selection is made, it will set or unset EVENT_PLAYER_GENDER
accordingly.
To open this gender selection screen, we'll call it when the player selects New Game from the Main Menu. Edit src/engine/menus/main_menu.asm:
MainMenu_NewGame:
farcall Func_c1b1
+ call PlayerGenderSelection
call DisplayPlayerNamingScreen
farcall InitSaveData
call EnableSRAM
And with that, we got a working selection screen which stores the player gender. Next we will make use of this event flag to influence various aspects of the gameplay.
Right away in the naming screen we can add some logic to attribute a default name to the player, depending on the selected gender. We shall keep the default male name as Mark, and add a new female name, Mint. Edit DisplayPlayerNamingScreen
in src/engine/menus/naming.asm:
DisplayPlayerNamingScreen:
...
ld a, [hl]
or a
; check if anything typed.
- jr nz, .no_name
- ld hl, .default_name
-.no_name
+ jr nz, .got_name
+
+ ld a, EVENT_PLAYER_GENDER
+ farcall GetEventValue
+ or a
+ ld hl, .default_name_male
+ jr z, .got_name
+ ld hl, .default_name_female
+
+.got_name
; set the default name.
ld de, sPlayerName
ld bc, NAME_BUFFER_LENGTH
...
call DisableSRAM
ret
-.default_name
- ; "MARK": default player name.
+.default_name_male
+ ; "MARK": default male player name.
textfw "MARK"
db TX_END, TX_END, TX_END, TX_END
+.default_name_female
+ ; "MINT": default female player name.
+ textfw "MINT"
+ db TX_END, TX_END, TX_END, TX_END
+
Unknown_128f7:
Now the player's portrait is also dependent on the gender choice. We'll add a branch in DrawPlayerPortrait
, in src/home/load_animation.asm:
; draws player's portrait at b,c
DrawPlayerPortrait::
+ ld a, EVENT_PLAYER_GENDER
+ farcall GetEventValue
+ or a
ld a, PLAYER_PIC
+ jr z, .got_pic
+ ld a, MINT_PIC
+.got_pic
ld [wCurPortrait], a
ld a, TILEMAP_PLAYER
; fallthrough
What's great about this modification is that it takes care of a lot of places where the portrait appears, such as in duels and in the Diary menu.
To adjust the OW sprite we'll tell the game to load SPRITE_OW_MINT
instead of the male player sprite. Edit src/engine/overworld/overworld.asm:
ld b, SPRITE_ANIM_LIGHT_NPC_UP
ld a, [wConsole]
cp CONSOLE_CGB
- jr nz, .not_cgb
+ jr nz, .got_anim
+
+ ld a, EVENT_PLAYER_GENDER
+ farcall GetEventValue
+ or a
ld b, SPRITE_ANIM_RED_NPC_UP
-.not_cgb
+ jr z, .got_anim
+ ld b, SPRITE_ANIM_BLUE_NPC_UP
+.got_anim
ld a, b
ld [wPlayerSpriteBaseAnimation], a
; load Player's sprite for overworld
+ ld a, EVENT_PLAYER_GENDER
+ farcall GetEventValue
+ or a
ld a, SPRITE_OW_PLAYER
+ jr z, .got_player_ow_sprite
+ ld a, SPRITE_OW_MINT
+.got_player_ow_sprite
farcall CreateSpriteAndAnimBufferEntry
ld a, [wWhichSprite]
ld [wPlayerSpriteIndex], a
Besides the change in sprite, we also attributed the blue NPC color to the player character.
Almost done! As the last step, we will tweak the Main Menu screen so that it reflects the gender selection made by the player if there is save data. It would be weird to show the male character when the gender choice was female!
You might expect that DrawPlayerPortrait
that was edited in a previous step would take care of this, but there's a caveat: wEventVars
isn't loaded into WRAM until the player chooses the Continue From Diary option. The result is that the bit is 0 by default and so the game assumes a male portrait. This doesn't work for what we intend to achieve since we have to know the value of the event flag. Change HandleTitleScreen
so that it loads only the event flags from SRAM (if there is actual save data), in src/engine/menus/start.asm:
HandleTitleScreen:
.start_menu
call CheckIfHasSaveData
+ ld a, [wHasSaveData]
+ or a
+ call nz, LoadEventsFromSRAM
call HandleStartMenu
...
cp START_MENU_CARD_POP
jr nz, .continue_duel
call ShowCardPopCGBDisclaimer
- jr c, HandleTitleScreen
+ jp c, HandleTitleScreen
.continue_duel
call ResetDoFrameFunction
call EnableAndClearSpriteAnimations
And just below write this new routine, LoadEventsFromSRAM
:
farcall ValidateBackupGeneralSaveData
ret
+LoadEventsFromSRAM:
+ ld hl, sEventVars
+ ld de, wEventVars
+ ld bc, EVENT_VAR_BYTES
+ call EnableSRAM
+ call CopyDataHLtoDE
+ jp DisableSRAM
+
; handles printing the Start Menu
; and getting player input and choice
HandleStartMenu:
And presto! You've successfully added a new player character option and selection screen. What's stopping you from creating more character options now?