-
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.
- Implement new overworld sprites
- Adding a new player state
- Create new logic for walking and running state transitions
- Reset player's state during events
First, it's important to have the sprites which will be used as the running animation. This 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.
The first step will be to add our new sprites as overworld sprites. The process is similar to this tutorial for adding a new regular sprite. First we go to constants/sprite_constants.asm:
; OverworldSprites indexes (see data/sprites/sprites.asm)
const_def
const SPRITE_NONE ; 00
...
const SPRITE_STANDING_YOUNGSTER ; 66
+ const SPRITE_CHRIS_RUN ; 67
+ const SPRITE_KRIS_RUN ; 68
DEF NUM_OVERWORLD_SPRITES EQU const_value - 1
And then create a new section in 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"
You don't have to worry about placing our new section into any bank in layout.link. These sprites' sections can be placed in any arbitrary bank, so we can let RGBLINK do it automatically whenever we run make
and it'll work perfectly.
Finally, we 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 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
For a detailed explanation about how the overworld_sprite
macro works, please check this tutorial again.
The new sprites are in the ROM and it can be assembled just fine, but we need a way to use them. Whenever our player sprite changes from normal to surfing and vice versa it's because of constants used as states and stored in wPlayerState
. This data is in constants/ram_constants.asm and we'll edit it to add a new constant to define the running state:
; wPlayerState::
DEF PLAYER_NORMAL EQU 0
DEF PLAYER_BIKE EQU 1
DEF PLAYER_SKATE EQU 2
DEF PLAYER_SURF EQU 4
DEF PLAYER_SURF_PIKA EQU 8
+DEF PLAYER_RUN EQU 16
We picked 16 here because it's an order of magnitude over 8 in binary to follow existing convention.
And now we have to associate the sprites to the new run state. Let's edit 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
From now on we'll edit engine/overworld/player_movement.asm.
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:
DoPlayerMovement::
...
+.StartRunning:
+ ld a, PLAYER_RUN
+ ld [wPlayerState], a
+ push bc
+ farcall UpdatePlayerSprite
+ pop bc
+ ret
+
+.StartWalking:
+ ld a, PLAYER_NORMAL
+ ld [wPlayerState], a
+ push bc
+ farcall UpdatePlayerSprite
+ pop bc
+ ret
+
CheckStandingOnIce::
ld a, [wPlayerTurningDirection]
cp 0
Next, we introduce a function to coordinate what we've created above. Still in engine/overworld/player_movement.asm:
DoPlayerMovement::
...
.unused ; unreferenced
xor a
ret
.bump
xor a
ret
+.HandleWalkAndRun
+ ld a, [wWalkingDirection]
+ cp STANDING
+ jr z, .ensurewalk
+ ldh a, [hJoypadDown]
+ and B_BUTTON
+ cp B_BUTTON
+ jr nz, .ensurewalk
+ ld a, [wPlayerState]
+ cp PLAYER_RUN
+ call nz, .StartRunning
+ jr .fast
+
+.ensurewalk
+ ld a, [wPlayerState]
+ cp PLAYER_NORMAL
+ call nz, .StartWalking
+ jr .walk
+
.TrySurf:
call .CheckSurfPerms
ld [wWalkingIntoLand], a
jr c, .surf_bump
First, we make sure the player returns to the walking state whenever they're standing still and appropriately go to the running state when the B button is held down. Then, by checking the player state before calling the transition functions, we only change the state when the player is actually transitioning 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
, introducing noticeable lag at each step by the continuous reloading.
Finally, we can trigger all of this whenever the player takes a step:
ld a, [wPlayerTile]
call CheckIceTile
jr nc, .ice
; Downhill riding is slower when not moving down.
call .BikeCheck
- jr nz, .walk
+ jr nz, .HandleWalkAndRun
ld hl, wBikeFlags
bit BIKEFLAGS_DOWNHILL_F, [hl]
jr z, .fast
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.
There's still a small detail. If the player happens to run towards an event tile and trigger it, the state won't reset back to PLAYER_NORMAL
and we'll be stuck in an awkward running frame. In order to address this, we need to add the logic in engine/overworld/events.asm:
PlayerEvents:
...
ld [wScriptRunning], a
call DoPlayerEvent
ld a, [wScriptRunning]
cp PLAYEREVENT_CONNECTION
jr z, .ok2
cp PLAYEREVENT_JOYCHANGEFACING
jr z, .ok2
xor a
ld [wLandmarkSignTimer], a
+ ; Have player stand (resets running sprite to standing if event starts while running)
+ ld a, [wPlayerState]
+ cp PLAYER_RUN
+ jr nz, .ok2
+ ld a, PLAYER_NORMAL
+ ld [wPlayerState], a
+ farcall UpdatePlayerSprite
.ok2
scf
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.