-
Notifications
You must be signed in to change notification settings - Fork 806
Running Shoes
The simplest possible way to implement Running Shoes is to double the player's walking speed, just like the Bicycle, if you're holding the B button. Don't bother to disable running indoors like Gen 3, or have a permanent toggle like Gen 4.
Special thanks to Victoria Lacroix for figuring out how to add this new movement type and Tom Wang for figuring out how to add new animations while running.
- [Adding a check for running][sec-1]
- [Loading the sprites into ROM][sec-2]
- [Contextualizing the assets and adding player run state][sec-3]
- [Creating logic for run and walk state transitions with sprite transitions][sec-4]
- [Triggering and un-triggering player state transitions][sec-5]
DoPlayerMovement::
...
; Downhill riding is slower when not moving down.
+ call .RunCheck
+ jr z, .fast
call .BikeCheck
jr nz, .walk
...
.fast
ld a, STEP_BIKE
call .DoStep
scf
ret
.walk
ld a, STEP_WALK
call .DoStep
scf
ret
...
.BikeCheck:
ld a, [wPlayerState]
cp PLAYER_BIKE
ret z
cp PLAYER_SKATE
ret
+
+.RunCheck:
+ ld a, [wPlayerState]
+ cp PLAYER_NORMAL
+ ret nz
+ ldh a, [hJoypadDown]
+ and B_BUTTON
+ cp B_BUTTON
+ ret
That's it!
If you want Running Shoes to have a speed in-between walking and biking... that's more complicated. I'll update this tutorial if I ever figure out how to do that.
First, it's important to have the pictures which will be used as the running animation. The tutorial will name the files as "chris_run" and "kris_run", but they can be named as you see fit. You can also use the sprites below, as long as you credit Tom Wang for Chris' sprite and Seasick for Kris' sprite.
gfx/sprites.asm
EnteiSpriteGFX:: INCBIN "gfx/sprites/entei.2bpp"
RaikouSpriteGFX:: INCBIN "gfx/sprites/raikou.2bpp"
StandingYoungsterSpriteGFX:: INCBIN "gfx/sprites/standing_youngster.2bpp"
+
+SECTION "Sprites 3", ROMX
+
+ChrisRunSpriteGFX:: INCBIN "gfx/sprites/chris_run.2bpp"
+KrisRunSpriteGFX:: INCBIN "gfx/sprites/kris_run.2bpp"
We can now declare where in the ROM the "Sprites 3" should be loaded. In our case, we're using bank $7e
:
layout.link
"Mobile News Data"
ROMX $7e
"Crystal Events"
+ "Sprites 3"
ROMX $7f
org $7de0
"Stadium 2 Checksums"
If you're curious as to how I decided on this bank, I just tried adding "Sprites 3" to all the ROMX sections bottom up, compiling between each attempt until I found a bank that could fit the new assets. :P
data/sprites/sprites.asm
overworld_sprite EnteiSpriteGFX, 4, STILL_SPRITE, PAL_OW_RED
overworld_sprite RaikouSpriteGFX, 4, STILL_SPRITE, PAL_OW_RED
overworld_sprite StandingYoungsterSpriteGFX, 12, STANDING_SPRITE, PAL_OW_BLUE
+ overworld_sprite ChrisRunSpriteGFX, 12, WALKING_SPRITE, PAL_OW_RED
+ overworld_sprite KrisRunSpriteGFX, 12, WALKING_SPRITE, PAL_OW_BLUE
assert_table_length NUM_OVERWORLD_SPRITES
constants/sprite_constants.asm
const SPRITE_ENTEI ; 64
const SPRITE_RAIKOU ; 65
const SPRITE_STANDING_YOUNGSTER ; 66
+ const SPRITE_CHRIS_RUN; 67
+ const SPRITE_KRIS_RUN; 68
DEF NUM_OVERWORLD_SPRITES EQU const_value - 1
If you're wondering here how SPRITE_X_RUN connects to XRunSpriteGFX, I think this is done via the order of declaration in data/sprites/sprites.asm
constants/wram_constants.asm
DEF PLAYER_SKATE EQU 2
DEF PLAYER_SURF EQU 4
DEF PLAYER_SURF_PIKA EQU 8
+DEF PLAYER_RUN EQU 16
I picked 16 here because it's an order of magnitude over 8 in binary to follow existing convention.
data/sprites/player_sprites.asm
ChrisStateSprites:
db PLAYER_BIKE, SPRITE_CHRIS_BIKE
db PLAYER_SURF, SPRITE_SURF
db PLAYER_SURF_PIKA, SPRITE_SURFING_PIKACHU
+ db PLAYER_RUN, SPRITE_CHRIS_RUN
db -1 ; end
KrisStateSprites:
db PLAYER_BIKE, SPRITE_KRIS_BIKE
db PLAYER_SURF, SPRITE_SURF
db PLAYER_SURF_PIKA, SPRITE_SURFING_PIKACHU
+ db PLAYER_RUN, SPRITE_KRIS_RUN
db -1 ; end
make
We'll need transition functions to sequentially change the player state to PLAYER_RUN and trigger a sprite refresh to load the sprites for the new state:
engine/overworld/player_movement.asm
pop bc
ret
+.StartRunning:
+ push bc
+ ld a, PLAYER_RUN
+ ld [wPlayerState], a
+ call UpdatePlayerSprite
+ pop bc
+ ret
+
+.StartWalking:
+ push bc
+ ld a, PLAYER_NORMAL
+ ld [wPlayerState], a
+ call UpdatePlayerSprite
+ pop bc
+ ret
+
CheckStandingOnIce::
ld a, [wPlayerTurningDirection]
cp 0
Next, adding a function to check that B is held down and another to check if the player is standing still:
engine/overworld/player_movement.asm
cp PLAYER_SKATE
ret
+.CheckBHeldDown:
+ ldh a, [hJoypadDown]
+ and B_BUTTON
+ cp B_BUTTON
+ ret
+
+.CheckStandingStill
+ ld a, [wWalkingDirection]
+ cp STANDING
+ ret
+
.CheckWalkable:
; Return 0 if tile a is land. Otherwise, return carry.
-
.StartRunning
which changes the player state toPLAYER_RUN
and triggers a sprite refresh -
.StartWalking
which changes the palyer state toPLAYER_NORMAL
and triggers a sprite refresh -
.CheckBHeldDown
which checks if... B is held down -
.CheckStandingStill
which checks if the player is standing still
Almost there!
Let's create functions that'll conditionally trigger our .StartRunning
and .StartWalking
transition functions and set the appropriate movement speeds for the player:
engine/overworld/player_movement.asm
+.ensurerun
+ ld a, [wPlayerState]
+ cp PLAYER_RUN
+ call nz, .StartRunning
+ jr .fast
+
+.ensurewalk
+ ld a, [wPlayerState]
+ cp PLAYER_NORMAL
+ call nz, .StartWalking
+ jr .walk
+
.fast
ld a, STEP_BIKE
call .DoStep
Calling .ensurerun
and .ensurewalk
instead of the transition functions directly ensures that we only trigger transitions when the player is actually transiting from walk to run or vice versa. For instance, calling .StartRunning
when the player is already running would cause the sprite to be reloaded via UpdatePlayerSprite
but that's not the case with .ensurerun
. Without this intermediate step, noticeable lag is introduced at each step by the continous reloading.
engine/overworld/player_movement.asm
DoPlayerMovement::
scf
ret
+.HandleWalkAndRun
+ call .CheckStandingStill
+ jr z, .ensurewalk
+ call .CheckBHeldDown
+ jr z, .ensurerun
+ jr .ensurewalk
Here, we ensure the player returns to the walking state whenever they're standing still and appropriatly jumps to the running state when B is held down or the walking state otherwise.
engine/overworld/player_movement.asm
call CheckIceTile
jr nc, .ice
+ call .BikeCheck
+ jr nz, .HandleWalkAndRun
+
; Downhill riding is slower when not moving down.
call .BikeCheck
jr nz, .walk
We use the existing .BikeCheck
so that we only consider transiting when we're not on a bike. Not doing so leads to the player getting off the bike immediately after getting on.
make
If everything went well, the player will now jump onto a bike whenever B is held down while they're moving in the overworld.