-
Notifications
You must be signed in to change notification settings - Fork 806
Remove Pokémon sprite animations
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.
- Delete the sprite animation system and data
- Remove references to deleted files
- Remove code and data that is no longer used
- Don't load animation graphics
- Play cries instead of animations
- Fix the Pokémon stats screen
- Remove the unused animation tile graphics
Delete all of these files:
- engine/gfx/pic_animation.asm
- gfx/pokemon/anim_pointers.asm
- gfx/pokemon/anims.asm
- gfx/pokemon/idle_pointers.asm
- gfx/pokemon/idles.asm
- gfx/pokemon/bitmask_pointers.asm
- gfx/pokemon/bitmasks.asm
- gfx/pokemon/frame_pointers.asm
- gfx/pokemon/johto_frames.asm
- gfx/pokemon/kanto_frames.asm
- gfx/pokemon/unown_anim_pointers.asm
- gfx/pokemon/unown_anims.asm
- gfx/pokemon/unown_idle_pointers.asm
- gfx/pokemon/unown_idles.asm
- gfx/pokemon/unown_bitmask_pointers.asm
- gfx/pokemon/unown_bitmasks.asm
- gfx/pokemon/unown_frame_pointers.asm
- gfx/pokemon/unown_frames.asm
- gfx/pokemon/unown/bitmask.asm
- gfx/pokemon/unown/frames.asm
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
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"
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 ; ???
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
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
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.
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:
-
tools/gfx
converts each front.png to front.2bpp -
tools/png_dimensions
reads each front.png and writes its size as a single byte in front.dimensions -
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
-
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.)