Skip to content
duckie edited this page Sep 6, 2024 · 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.

Gold running around

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.

Contents

  1. Implement new overworld sprites
  2. Adding a new player state
  3. Create new logic for walking and running state transitions
  4. Reset player's state during events

1. Implement new overworld sprites

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.

chris_run kris_run

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.

2. Adding a new player state

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

3. Create new logic for walking and running state transitions

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.

4. Reset player's state during events

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.

Clone this wiki locally