Skip to content
SeasickShore edited this page Jun 10, 2023 · 11 revisions

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.

  1. [Adding a check for running][sec-1]
  2. [Loading the sprites into ROM][sec-2]
  3. [Contextualizing the assets and adding player run state][sec-3]
  4. [Creating logic for run and walk state transitions with sprite transitions][sec-4]
  5. [Triggering and un-triggering player state transitions][sec-5]

1. Adding a check for running

 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.

2. Loading the sprites into ROM

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.

Let's create a new section where we'll load the assets we just created:

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

3. Contextualizing the assets and adding player run state

Contextualizing the assets as sprites:

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

Creating constants for the 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

Creating a new player running state

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.

And finally associating the sprites to the new run state for each Chris and Kris

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

At this stage, it's a good idea to compile and make sure everything is a-ok:

make

4. Creating logic for run and walk state transitions with sprite transitions

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.

At this point, we've the following inventory of helpers:

  1. .StartRunning which changes the player state to PLAYER_RUN and triggers a sprite refresh
  2. .StartWalking which changes the palyer state to PLAYER_NORMAL and triggers a sprite refresh
  3. .CheckBHeldDown which checks if... B is held down
  4. .CheckStandingStill which checks if the player is standing still

5. Triggering and un-triggering player state transitions

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.

Next, we introduce a function to coordinate everything we've created above:

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.

Finally, we can trigger all of this whenever the player takes a step:

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.

Compile and try it out!

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.

Clone this wiki locally