Skip to content

Remove Pokémon sprite animations

AndrewC101 edited this page Sep 3, 2023 · 20 revisions

One of Pokémon Crystal's improvements to Gold and Silver was animating the Pokémon sprites. There are various reasons why you might want to remove this feature:

  • You're changing a lot of Pokémon and don't want to design animations for them all
  • You want the game to more closely resemble RBY or GS
  • You need the space for something else

If you just want some of the Pokémon to not be animated, then as mentioned in the new Pokémon tutorial, you can just put endanim as the full contents of anim.asm and anim_idle.asm for those particular Pokémon. But if none if them will be animated, then you can save considerable ROM space by removing the entire animation system.

Contents

  1. Delete the sprite animation system and data
  2. Remove references to deleted files
  3. Remove code and data that is no longer used
  4. Don't load animation graphics
  5. Play cries instead of animations
  6. Fix the Pokémon stats screen
  7. Remove the unused animation tile graphics

1. Delete the sprite animation system and data

Delete all of these files:

Then run these commands to delete files that might have been created by make:

rm -f gfx/pokemon/*/front.animated.2bpp
rm -f gfx/pokemon/*/front.animated.2bpp.lz
rm -f gfx/pokemon/*/front.animated.tilemap
rm -f gfx/pokemon/*/bitmask.asm
rm -f gfx/pokemon/*/frames.asm

2. Remove references to deleted files

Edit main.asm:

-SECTION "Pic Animations 1", ROMX
-
-INCLUDE "engine/gfx/pic_animation.asm"
-...
-INCLUDE "gfx/pokemon/unown_bitmasks.asm"
-
-
-SECTION "Pic Animations 2", ROMX
-
-INCLUDE "gfx/pokemon/frame_pointers.asm"
-INCLUDE "gfx/pokemon/kanto_frames.asm"
-
-
 SECTION "Font Inversed", ROMX

 FontInversed:
 INCBIN "gfx/font/font_inversed.1bpp"
-
-
-SECTION "Pic Animations 3", ROMX
-
-INCLUDE "gfx/pokemon/johto_frames.asm"
-INCLUDE "gfx/pokemon/unown_frame_pointers.asm"
-INCLUDE "gfx/pokemon/unown_frames.asm"

And edit layout.link:

-ROMX $34
-	"Pic Animations 1"
-ROMX $35
-	"Pic Animations 2"
 ROMX $36
 	"Font Inversed"
-	"Pic Animations 3"

3. Remove code and data that is no longer used

Edit wram.asm:

-; PokeAnim data
-wPokeAnimStruct::
-wPokeAnimSceneIndex:: db
-...
-wPokeAnimStructEnd::

Edit constants/gfx_constants.asm:

-; PokeAnims indexes (see engine/gfx/pic_animation.asm)
-	const_def
-	const ANIM_MON_SLOW
-	...
-	const ANIM_MON_EGG2

Edit data/predef_pointers.asm:

 PredefPointers::
 	...
-	add_predef Unused_AnimateMon_Slow_Normal
	add_predef PlaceStatusString
-	add_predef LoadMonAnimation
-	add_predef AnimateFrontpic
-	add_predef Unused_HOF_AnimateAlignedFrontpic ; $48
-	add_predef HOF_AnimateFrontpic
-	dbw -1, InexplicablyEmptyFunction ; ???

4. Don't load animation graphics

Edit engine/gfx/load_pics.asm:

 GetAnimatedFrontpic:
 	ld a, [wCurPartySpecies]
 	ld [wCurSpecies], a
 	call IsAPokemon
 	ret c
 	ldh a, [rSVBK]
 	push af
 	xor a
 	ldh [hBGMapMode], a
 	call _GetFrontpic
-	call GetAnimatedEnemyFrontpic
 	pop af
 	ldh [rSVBK], a
 	ret

...

-GetAnimatedEnemyFrontpic:
-	...
-	ret
-
-LoadFrontpicTiles:
-	...
-	ret

5. Play cries instead of animations

If you read engine/gfx/pic_animation.asm before deleting it, you'll see how the animation system works. Usually the AnimateFrontpic routine is called with an ANIM_MON_* value loaded into e. It then runs a series of subroutines based on the value of e, selected from the PokeAnims array:

PokeAnims:
; entries correspond to ANIM_MON_* constants
	dw .Slow
	dw .Normal
	dw .Menu
	dw .Trade
	dw .Evolve
	dw .Hatch
	dw .HOF
	dw .Egg1
	dw .Egg2

.Slow:   pokeanim StereoCry, Setup2, Play
.Normal: pokeanim StereoCry, Setup, Play
.Menu:   pokeanim CryNoWait, Setup, Play, SetWait, Wait, Idle, Play
.Trade:  pokeanim Idle, Play2, Idle, Play, SetWait, Wait, Cry, Setup, Play
.Evolve: pokeanim Idle, Play, SetWait, Wait, CryNoWait, Setup, Play
.Hatch:  pokeanim Idle, Play, CryNoWait, Setup, Play, SetWait, Wait, Idle, Play
.HOF:    pokeanim CryNoWait, Setup, Play, SetWait, Wait, Idle, Play
.Egg1:   pokeanim Setup, Play
.Egg2:   pokeanim Idle, Play

Notice that not all of the subroutines are related to graphics: Cry, CryNoWait, and StereoCry play the audio cries. So since we've deleted the whole implementation of AnimateFrontpic, we have to restore the cry-playing functionality to wherever AnimateFrontpic was called.

Edit engine/gfx/trademon_frontpic.asm:

 AnimateTrademonFrontpic:
 	...
 	ld a, [wOTTrademonSpecies]
 	ld [wCurPartySpecies], a
-	hlcoord 7, 2
-	ld d, $0
-	ld e, ANIM_MON_TRADE
-	predef AnimateFrontpic
-	ret
+	jp PlayMonCry

Edit engine/movie/evolution_animation.asm:

 EvolutionAnimation:
 	...

 	ld a, [wPlayerHPPal]
 	ld [wCurPartySpecies], a
-	hlcoord 7, 2
-	ld d, $0
-	ld e, ANIM_MON_EVOLVE
-	predef AnimateFrontpic
+	call PlayMonCry2

 	pop af
 	ld [wCurPartySpecies], a
 	pop af
 	ld [wBoxAlignment], a
 	ret

Edit engine/pokemon/breeding.asm:

 EggHatch_AnimationSequence:
 	...
 	ld a, [wJumptableIndex]
 	ld [wCurPartySpecies], a
-	hlcoord 6, 3
-	ld d, $0
-	ld e, ANIM_MON_HATCH
-	predef AnimateFrontpic
+	call PlayMonCry2
 	pop af
 	ld [wCurSpecies], a
 	ret

Edit engine/battle/core.asm:

 Function_SetEnemyMonAndSendOutAnimation:
 	...

 .not_shiny
 	ld bc, wTempMonSpecies
 	farcall CheckFaintedFrzSlp
 	jr c, .skip_cry

-	farcall CheckBattleScene
-	jr c, .cry_no_anim
-
-	hlcoord 12, 0
-	ld d, $0
-	ld e, ANIM_MON_SLOW
-	predef AnimateFrontpic
-	jr .skip_cry
-
-.cry_no_anim
 	ld a, $f
 	ld [wCryTracks], a
 	ld a, [wTempEnemyMonSpecies]
 	call PlayStereoCry

 .skip_cry
 	call UpdateEnemyHUD
 	ld a, $1
 	ldh [hBGMapMode], a
 	ret

 ...

BattleStartMessage:
	...

.not_shiny
	farcall CheckSleepingTreeMon
	jr c, .skip_cry

-	farcall CheckBattleScene
-	jr c, .cry_no_anim
-
-	hlcoord 12, 0
-	ld d, $0
-	ld e, ANIM_MON_NORMAL
-	predef AnimateFrontpic
-	jr .skip_cry ; cry is played during the animation
-
-.cry_no_anim
 	ld a, $f
 	ld [wCryTracks], a
 	ld a, [wTempEnemyMonSpecies]
 	call PlayStereoCry

 .skip_cry
 	...

Turning the "Battle Scene" option off would already disable sprite animations here, so all we had to do was make its case the default and only one.

Edit engine/events/halloffame.asm:

 AnimateHallOfFame:
 	...

 .DisplayNewHallOfFamer:
 	call DisplayHOFMon
 	ld de, .String_NewHallOfFamer
 	hlcoord 1, 2
 	call PlaceString
 	call WaitBGMap
-	decoord 6, 5
-	ld c, ANIM_MON_HOF
-	predef HOF_AnimateFrontpic
-	ld c, 60
+	call HOF_PlayCry
+	ld c, 180
 	call DelayFrames
 	and a
 	ret

 ...

 _HallOfFamePC:
 	...

 .finish
 	ld de, .EmptyString
 	call PlaceString
 	call WaitBGMap
 	ld b, SCGB_PLAYER_OR_MON_FRONTPIC_PALS
 	call GetSGBLayout
 	call SetPalettes
-	decoord 6, 5
-	ld c, ANIM_MON_HOF
-	predef HOF_AnimateFrontpic
+	call HOF_PlayCry
 	and a
 	ret

 ...

+HOF_PlayCry::
+	ld a, [wCurPartySpecies]
+	cp EGG
+	jr z, .fail
+	call IsAPokemon
+	jr c, .fail
+	ld a, [wCurPartySpecies]
+	call PlayMonCry2
+	ret
+
+.fail
+	ld a, 1
+	ld [wCurPartySpecies], a
+	ret

The Hall of Fame uses its own HOF_AnimateFrontpic instead of the usual AnimateFrontpic, so we copied its extra behavior. We also increased the delay for displaying new Hall of Famers from 60 frames to 180, the same as G/S, since sprite animations aren't contributing to the delay any more.

Edit mobile/mobile_42.asm:

 Function108219:
 	ld [wCurPartySpecies], a
-	hlcoord 7, 2
-	ld d, $0
-	ld e, ANIM_MON_TRADE
-	predef AnimateFrontpic
-	ret
+	jp PlayMonCry

 Function108229:
 	ld [wCurPartySpecies], a
-	hlcoord 7, 2
-	ld d, $0
-	ld e, ANIM_MON_TRADE
-	predef LoadMonAnimation
 	ret

...

 Function1082db:
 .loop
 	farcall PlaySpriteAnimations
-	farcall SetUpPokeAnim
-	farcall HDMATransferTilemapToWRAMBank3
 	jr nc, .loop
 	ret

Finally, edit mobile/mobile_5f.asm:

 Function17d93a:
 	...
 	decoord 0, 0
 	add hl, de
 	ld e, l
 	ld d, h
-	farcall HOF_AnimateFrontpic
+	farcall HOF_PlayCry
 	pop af
 	ldh [rSVBK], a
 	call Function17e349
 	ret

6. Fix the Pokémon stats screen

The stats screen doesn't call AnimateFrontpic or HOF_AnimateFrontpic. Instead, it calls individual low-level subroutines of the sprite animation engine. So fixing it to just play cries is a bit more tricky than with the other subsystems.

Edit engine/pokemon/stats_screen.asm:

 StatsScreen_WaitAnim:
 	ld hl, wcf64
 	bit 6, [hl]
 	jr nz, .try_anim
 	bit 5, [hl]
 	jr nz, .finish
 	call DelayFrame
 	ret
 
 .try_anim
-	farcall SetUpPokeAnim
-	jr nc, .finish
 	ld hl, wcf64
 	res 6, [hl]
 .finish
 	ld hl, wcf64
 	res 5, [hl]
 	farcall HDMATransferTilemapToWRAMBank3
 	ret
 
 ...

 StatsScreen_PlaceFrontpic:
 	ld hl, wTempMonDVs
 	predef GetUnownLetter
 	call StatsScreen_GetAnimationParam
 	jr c, .egg
 	and a
 	jr z, .no_cry
 	jr .cry
 	...
 
 .get_animation
 	ld a, [wCurPartySpecies]
 	call IsAPokemon
 	ret c
 	call StatsScreen_LoadTextboxSpaceGFX
 	ld de, vTiles2 tile $00
 	predef GetAnimatedFrontpic
-	hlcoord 0, 0
-	ld d, $0
-	ld e, ANIM_MON_MENU
-	predef LoadMonAnimation
 	ld hl, wcf64
 	set 6, [hl]
 	ret

 StatsScreen_GetAnimationParam:
 	ld a, [wMonType]
 	ld hl, .Jumptable
 	rst JumpTable
 	ret
 
 .Jumptable:
 	dw .PartyMon
 	dw .OTPartyMon
 	dw .BoxMon
 	dw .Tempmon
 	dw .Wildmon
 
 ...
 
 .CheckEggFaintedFrzSlp:
 	ld a, [wCurPartySpecies]
 	cp EGG
 	jr z, .egg
 	call CheckFaintedFrzSlp
 	jr c, .FaintedFrzSlp
+	jr .Wildmon
+
 .egg
 	xor a
 	scf
 	ret
 
 .Wildmon:
 	ld a, $1
 	and a
 	ret
 
 .FaintedFrzSlp:
 	xor a
 	ret

 ...

 StatsScreen_AnimateEgg:
 	...
-	hlcoord 0, 0
-	ld d, $0
-	predef LoadMonAnimation
 	ld hl, wcf64
 	set 6, [hl]
 	ret

Before, the farcall SetUpPokeAnim in StatsScreen_WaitAnim would play the Pokémon cry. Since we deleted that, we have to fix StatsScreen_GetAnimationParam.CheckEggFaintedFrzSlp so that it will return nz and nc for Pokémon that are neither Eggs, nor fainted, nor frozen, nor asleep. That way StatsScreen_PlaceFrontpic will correctly play cries for those Pokémon.

7. Remove the unused animation tile graphics

If we stopped here, the animations would be gone and the cries would play correctly, but the ROM would still have leftover graphics data for the sprite animations.

One way to fix this would be to edit all 276 gfx/pokemon/*/front.png files (251 Pokémon, with 26 forms for Unown), cropping each one to just be the first frame. But doing this manually is tedious, and doing it automatically (a) requires installing GraphicsMagick and (b) risks messing up the images' indexed color palettes.

Another way is to crop the sprites after they've been converted from .png to .2bpp format. That's what we're going to do.

(If you're replacing the 251 Gen 2 Pokémon with your own set, then skip this step. And of course when you're drawing front sprites, just draw them as square still sprites, without any animated frames.)

Create tools/trim_animation.sh:

#!/bin/sh
# Usage: trim_animation.sh front.animated.2bpp front.dimensions

case $(cat $2) in
	U) bytes=400;; # $55="U"; 5*5*16=400
	f) bytes=576;; # $66="f"; 6*6*16=576
	w) bytes=784;; # $77="w"; 7*7*16=784
	*) bytes=-0;;  # invalid size; don't trim
esac

temp_file=$(mktemp)
head -c $bytes $1 > $temp_file
mv $temp_file $1

(Be sure to use Unix line endings, not DOS/Windows, just like all the other text files in pokecrystal!)

You will need to run: chmod 777 trim_animation.sh

Then edit the Makefile:

 ### Pokemon pic animation rules

 gfx/pokemon/%/front.animated.2bpp: gfx/pokemon/%/front.2bpp gfx/pokemon/%/front.dimensions
 	tools/pokemon_animation_graphics -o $@ $^
+	tools/trim_animation.sh $@ $(word 2,$^)
-gfx/pokemon/%/front.animated.tilemap: gfx/pokemon/%/front.2bpp gfx/pokemon/%/front.dimensions
-	tools/pokemon_animation_graphics -t $@ $^
-gfx/pokemon/%/bitmask.asm: gfx/pokemon/%/front.animated.tilemap gfx/pokemon/%/front.dimensions
-	tools/pokemon_animation -b $^ > $@
-gfx/pokemon/%/frames.asm: gfx/pokemon/%/front.animated.tilemap gfx/pokemon/%/front.dimensions
-	tools/pokemon_animation -f $^ > $@
-
-
-### Terrible hacks to match animations. Delete these rules if you don't care about matching.
-
-# Dewgong has an unused tile id in its last frame. The tile itself is missing.
-gfx/pokemon/dewgong/frames.asm: gfx/pokemon/dewgong/front.animated.tilemap gfx/pokemon/dewgong/front.dimensions
-	tools/pokemon_animation -f $^ > $@
-	echo "	db \$$4d" >> $@
-
-# Lugia has two unused tile ids in its last frame. The tiles themselves are missing.
-gfx/pokemon/lugia/frames.asm: gfx/pokemon/lugia/front.animated.tilemap gfx/pokemon/lugia/front.dimensions
-	tools/pokemon_animation -f $^ > $@
-	echo "	db \$$5e, \$$59" >> $@
-
-# Girafarig has a redundant tile after the end. It is used in two frames, so it must be injected into the generated graphics.
-# This is more involved, so it's hacked into pokemon_animation_graphics.
-gfx/pokemon/girafarig/front.animated.2bpp: gfx/pokemon/girafarig/front.2bpp gfx/pokemon/girafarig/front.dimensions
-	tools/pokemon_animation_graphics --girafarig -o $@ $^
-gfx/pokemon/girafarig/front.animated.tilemap: gfx/pokemon/girafarig/front.2bpp gfx/pokemon/girafarig/front.dimensions
-	tools/pokemon_animation_graphics --girafarig -t $@ $^

(Be sure to use tabs, not spaces, for indenting commands in the Makefile!)

(Older versions of pokecrystal also had rules to build gfx/pokemon/%/normal.pal, gfx/pokemon/%/normal.gbcpal, and gfx/pokemon/%/back.2bpp, but as of September 2018 they were removed. They do not need to be changed for this tutorial.)

Let's go over what trim_animation.sh is doing. Before, this was the workflow when building the ROM:

  1. tools/gfx converts each front.png to front.2bpp
  2. tools/png_dimensions reads each front.png and writes its size as a single byte in front.dimensions
  3. tools/pokemon_animation_graphics takes each pair of front.2bpp and front.dimensions and creates front.animated.2bpp, which is modified from front.2bpp in two ways:
    • The first frame is transposed so its tiles are in column order, not row order
    • The subsequent frames are treated as a series of animation tiles, and duplicate tiles are left out
  4. tools/lzcomp compresses each front.animated.2bpp into front.animated.2bpp.lz

(Actually, if a front.animated.2bpp.lz.XXXXXXXX file exists, and the MD5 hash of front.animated.2bpp is XXXXXXXX, then it will just copy front.animated.2bpp.lz.XXXXXXXX as the compressed file. This is done because tools/lzcomp can't always reproduce the exact same compression as the original Pokémon Crystal ROM.)

Anyway, trim_animation.sh takes each front.animated.2bpp file and removes the animation tiles, keeping only the transposed tiles for the first frame. It reads front.dimensions to know how many tiles the first frames uses. Then each tile uses 16 bytes, so it keeps that many bytes of the head of the file.

That's it, we're done! This change frees up 102 KB of a 2 MB ROM: nearly 5% of the total space. Considering that an original Crystal ROM has 20% of its space unused, we've increased the free space by nearly 25%.

(As of July 23, 2018, you can measure free space after building a ROM with make by running tools/free_space.awk pokecrystal.map. The script tools/free_space.awk parses a .map file and adds up all the unused "slack" space.)

Clone this wiki locally