Add a new battle transition - pret/pokecrystal GitHub Wiki
This tutorial is for how to add a new battle transition, like the Poké Ball that covers the screen for a trainer battle. As an example, we'll add an "R" for battles with Team Rocket.
Contents
- Add a new transition
- Create a custom palette for that transition
- Simplify transition jumptable
- New transition animation
1. Add a new transition
First, edit engine/battle/battle_transition.asm:
StartTrainerBattle_LoadPokeBallGraphics:
...
call .loadpokeballgfx
...
.loadpokeballgfx
+ ld de, TeamRocketTransition
ld a, [wOtherTrainerClass]
+ cp GRUNTM
+ ret z
+ cp GRUNTF
+ ret z
+ cp EXECUTIVEM
+ ret z
+ cp EXECUTIVEF
+ ret z
+ cp SCIENTIST
+ ret z
ld de, PokeBallTransition
ret
PokeBallTransition:
; 16x16 overlay of a Poke Ball
pusho
opt b.X ; . = 0, X = 1
bigdw %......XXXX......
bigdw %....XXXXXXXX....
bigdw %..XXXX....XXXX..
bigdw %..XX........XX..
bigdw %.XX..........XX.
bigdw %.XX...XXXX...XX.
bigdw %XX...XX..XX...XX
bigdw %XXXXXX....XXXXXX
bigdw %XXXXXX....XXXXXX
bigdw %XX...XX..XX...XX
bigdw %.XX...XXXX...XX.
bigdw %.XX..........XX.
bigdw %..XX........XX..
bigdw %..XXXX....XXXX..
bigdw %....XXXXXXXX....
bigdw %......XXXX......
popo
+TeamRocketTransition:
+pusho
+opt b.X ; . = 0, X = 1
+ bigdw %XXXXXXXXXXXX....
+ bigdw %XXXXXXXXXXXXXX..
+ bigdw %XXXXXXXXXXXXXXX.
+ bigdw %XXXXXXXXXXXXXXX.
+ bigdw %XXXXX.....XXXXXX
+ bigdw %XXXXX......XXXXX
+ bigdw %XXXXX.....XXXXXX
+ bigdw %XXXXXXXXXXXXXXX.
+ bigdw %XXXXXXXXXXXXXXX.
+ bigdw %XXXXXXXXXXXXXX..
+ bigdw %XXXXXXXXXXXXX...
+ bigdw %XXXXX....XXXXX..
+ bigdw %XXXXX....XXXXX..
+ bigdw %XXXXX.....XXXXX.
+ bigdw %XXXXX......XXXXX
+ bigdw %XXXXX......XXXXX
+popo
Older versions of pokecrystal define the PokeBallTransition pattern with literal binary numbers:
PokeBallTransition:
db %00000011, %11000000
db %00001111, %11110000
db %00111100, %00111100
db %00110000, %00001100
db %01100000, %00000110
db %01100011, %11000110
db %11000110, %01100011
db %11111100, %00111111
db %11111100, %00111111
db %11000110, %01100011
db %01100011, %11000110
db %01100000, %00000110
db %00110000, %00001100
db %00111100, %00111100
db %00001111, %11110000
db %00000011, %11000000
Either way works, but the new method shows the 16x16 tile pattern more clearly.
Notice that StartTrainerBattle_LoadPokeBallGraphics.loadpokeballgfx already has the line ld a, [wOtherTrainerClass] but doesn't do anything with it. Maybe trainer-based patterns like this were a scrapped feature.
Anyway, that's all it takes to change the overlay pattern:

2. Create a custom palette for that transition
We can create a custom palette for any transition. Let's try creating a file called rocket_battle.pal in gfx/overworld.
RGB 31, 08, 08
RGB 31, 07, 06
RGB 31, 02, 03
RGB 07, 07, 07
Next, edit engine/battle/battle_transition.asm again:
.cgb
+ ld hl, .rocketpals
+ ld a, [wOtherTrainerClass]
+ cp GRUNTM
+ jr z, .load_rocket_pals
+ cp GRUNTF
+ jr z, .load_rocket_pals
+ cp EXECUTIVEM
+ jr z, .load_rocket_pals
+ cp EXECUTIVEF
+ jr z, .load_rocket_pals
+ cp SCIENTIST
+ jr z, .load_rocket_pals
ld hl, .pals
+.load_rocket_pals
ld a, [wTimeOfDayPal]
maskbits NUM_DAYTIMES
cp DARKNESS_F
jr nz, .not_dark
ld hl, .darkpals
.not_dark
...
.pals:
INCLUDE "gfx/overworld/trainer_battle.pal"
.darkpals:
INCLUDE "gfx/overworld/trainer_battle_dark.pal"
+.rocketpals:
+INCLUDE "gfx/overworld/rocket_battle.pal"
And that's it! Now the overlay colors for the pattern have been changed!

You can create more palettes to handle different trainer classes.
Changing the transition animations (the ones that fade, wipe, distort, or otherwise erase the screen to begin a battle) is more complicated.
3. Simplify transition jumptable
Let's look at the top of engine/battle/battle_transition.asm:
; BattleTransitionJumptable.Jumptable indexes
DEF BATTLETRANSITION_CAVE EQU $01
DEF BATTLETRANSITION_CAVE_STRONGER EQU $09
DEF BATTLETRANSITION_NO_CAVE EQU $10
DEF BATTLETRANSITION_NO_CAVE_STRONGER EQU $18
DEF BATTLETRANSITION_FINISH EQU $20
These are indexes into the jumptable for each "phase" of the battle transition sequence:
BATTLETRANSITION_CAVEused in any "dungeon" map or cave. Distorts the screen in a wavy pattern.BATTLETRANSITION_CAVE_STRONGERSame maps as above but the opponent is stronger. Zooms to black.BATTLETRANSITION_NO_CAVEused on any non-dungeon map. Spins to black.BATTLETRANSITION_NO_CAVE_STRONGERSame maps as above but the opponent is stronger. Black speckles on screen until black.BATTLETRANSITION_FINISHis the end of the entire battle transition sequence. TODO: Show GIFs
Now let's take a look at BattleTransitionJumptable.Jumptable in the same file and see what's going on when these are used:
; BATTLETRANSITION_CAVE
dw StartTrainerBattle_LoadPokeBallGraphics ; 01
dw StartTrainerBattle_SetUpBGMap ; 02
dw StartTrainerBattle_Flash ; 03
dw StartTrainerBattle_Flash ; 04
dw StartTrainerBattle_Flash ; 05
dw StartTrainerBattle_NextScene ; 06
dw StartTrainerBattle_SetUpForWavyOutro ; 07
dw StartTrainerBattle_SineWave ; 08
; BATTLETRANSITION_CAVE_STRONGER
dw StartTrainerBattle_LoadPokeBallGraphics ; 09
dw StartTrainerBattle_SetUpBGMap ; 0a
dw StartTrainerBattle_Flash ; 0b
dw StartTrainerBattle_Flash ; 0c
dw StartTrainerBattle_Flash ; 0d
dw StartTrainerBattle_NextScene ; 0e
; There is no setup for this one
dw StartTrainerBattle_ZoomToBlack ; 0f
; BATTLETRANSITION_NO_CAVE
dw StartTrainerBattle_LoadPokeBallGraphics ; 10
dw StartTrainerBattle_SetUpBGMap ; 11
dw StartTrainerBattle_Flash ; 12
dw StartTrainerBattle_Flash ; 13
dw StartTrainerBattle_Flash ; 14
dw StartTrainerBattle_NextScene ; 15
dw StartTrainerBattle_SetUpForSpinOutro ; 16
dw StartTrainerBattle_SpinToBlack ; 17
; BATTLETRANSITION_NO_CAVE_STRONGER
dw StartTrainerBattle_LoadPokeBallGraphics ; 18
dw StartTrainerBattle_SetUpBGMap ; 19
dw StartTrainerBattle_Flash ; 1a
dw StartTrainerBattle_Flash ; 1b
dw StartTrainerBattle_Flash ; 1c
dw StartTrainerBattle_NextScene ; 1d
dw StartTrainerBattle_SetUpForRandomScatterOutro ; 1e
dw StartTrainerBattle_SpeckleToBlack ; 1f
Each transition has 7-8 phases:
- Load Poké Ball overlay with red palette if it's a trainer (or Team Rocket logo if Team Rocket if using the changes above)
- Set up the background map (TODO: explain what's happening)
- Flash for 3 phases
- Jump to next scene (This basically does nothing)
- Do set up for animation (if set up is needed)
- Do animation
So each anim basically has the same 6 phases. 3 of those are doing the same thing and another that basically does nothing. If we were to just to add a new anim like it is currently, we would need the 6 "set up" phases and the actual function used in the animation. Because of this, we are going to make some extra changes to simplify the process by doing it so the set up is always done first and then it will determine which animation to use so let's see how the animation is determined:
; transition animations
const_def
const TRANS_CAVE
const TRANS_CAVE_STRONGER
const TRANS_NO_CAVE
const TRANS_NO_CAVE_STRONGER
; transition animation bits
DEF TRANS_STRONGER_F EQU 0 ; bit set in TRANS_CAVE_STRONGER and TRANS_NO_CAVE_STRONGER
DEF TRANS_NO_CAVE_F EQU 1 ; bit set in TRANS_NO_CAVE and TRANS_NO_CAVE_STRONGER
StartTrainerBattle_DetermineWhichAnimation:
; The screen flashes a different number of times depending on the level of
; your lead Pokemon relative to the opponent's.
; BUG: Battle transitions fail to account for enemy's level (see docs/bugs_and_glitches.md)
ld de, 0
ld a, [wBattleMonLevel]
add 3
ld hl, wEnemyMonLevel
cp [hl]
jr nc, .not_stronger
set TRANS_STRONGER_F, e
.not_stronger
ld a, [wEnvironment]
cp CAVE
jr z, .cave
cp ENVIRONMENT_5
jr z, .cave
cp DUNGEON
jr z, .cave
set TRANS_NO_CAVE_F, e
.cave
ld hl, .StartingPoints
add hl, de
ld a, [hl]
ld [wJumptableIndex], a
ret
The comment says the screen flashes a different number times depending on your first mon's level relative to the opponent's. This isn't actually true. All this function does is determine what the respective starting point is going to be (Starting point being the first function in the animation). The starting points are listed below:
.StartingPoints:
; entries correspond to TRANS_* constants
db BATTLETRANSITION_CAVE
db BATTLETRANSITION_CAVE_STRONGER
db BATTLETRANSITION_NO_CAVE
db BATTLETRANSITION_NO_CAVE_STRONGER
If the opponent is a higher level than us, it will use anything above ending with _STRONGER. How this is determined depends on the transition animation flags set above.
NOTE: due to a bug, the game fails to account for the opponent's level.
Though not required, it might be worth applying the fix for that. (TODO: link fix in bugs_and_glitches.md) (TODO: give better explanation)
What we are going to be doing is doing it so this happens after the game has done the set up phases mentioned above.
Replace ; BattleTransitionJumptable.Jumptable indexes at the top of engine/battle/battle_transition.asm with the following:
; BattleTransitionJumptable.Jumptable indexes
const_def
const BATTLETRANSITION_LOAD_POKEBALL_GFX ; 00
const BATTLETRANSITION_SET_UP_BG_MAP ; 01
const BATTLETRANSITION_FLASH ; 02
const BATTLETRANSITION_DETERMINE_ANIM ; 03
DEF NUM_SET_UP_PHASES EQU const_value
const BATTLETRANSITION_WAVY ; 04
const_skip ; 05
const BATTLETRANSITION_ZOOM_TO_BLACK ; 06
; no set up so no skip
const BATTLETRANSITION_SPIN ; 07
const_skip ; 08
const BATTLETRANSITION_SCATTER ; 09
const_skip ; 0a
const BATTLETRANSITION_FINISH ; 0b
Transitions with two phases use const_skip to skip to the next starting point
; StartTrainerBattle_DetermineWhichAnimation.StartingPoints indexes
DEF BATTLETRANSITION_CAVE EQU BATTLETRANSITION_WAVY
DEF BATTLETRANSITION_CAVE_STRONGER EQU BATTLETRANSITION_ZOOM_TO_BLACK
DEF BATTLETRANSITION_NO_CAVE EQU BATTLETRANSITION_SPIN
DEF BATTLETRANSITION_NO_CAVE_STRONGER EQU BATTLETRANSITION_SCATTER
; Number of screen flashes
DEF NUM_BATTLETRANSITION_FLASHES EQU 3
Instead of just defining the "starting points", we are going to be defining them all and give them proper names with aliases for each use case so it's a bit easier to modify which one we want to use. NUM_BATTLETRANSITION_FLASHES is the number of times the screen flashes during the transition sequence which we will be covering.
Modify .Jumptable:
.Jumptable:
+ ; Changed order of operations:
+ ; - Load Poke Ball gfx and red palette if it's a trainer battle.
+ ; - Set up bg map.
+ ; - Flash the screen n times. The number of flashes is determined by "NUM_BATTLETRANSITION_FLASHES"
+ ; - Determine which anim to use from ".StartingPoints".
+ ; - Run anim determined above. All starting points except ZOOM_TO_BLACK have two phases.
+ ; - Finish and clean up.
+; Set up phases
+ ; BATTLETRANSITION_LOAD_POKEBALL_GFX
+ dw StartTrainerBattle_LoadPokeBallGraphics
+ ; BATTLETRANSITION_SET_UP_BG_MAP
+ dw StartTrainerBattle_SetUpBGMap
+ ; BATTLETRANSITION_FLASH
+ dw StartTrainerBattle_Flash
+ ; BATTLETRANSITION_DETERMINE_ANIM
+ dw StartTrainerBattle_DetermineWhichAnimation
+; End of set up phases
+; Starting points
- dw StartTrainerBattle_DetermineWhichAnimation ; 00
- ; BATTLETRANSITION_CAVE
- dw StartTrainerBattle_LoadPokeBallGraphics ; 01
- dw StartTrainerBattle_SetUpBGMap ; 02
- dw StartTrainerBattle_Flash ; 03
- dw StartTrainerBattle_Flash ; 04
- dw StartTrainerBattle_Flash ; 05
- dw StartTrainerBattle_NextScene ; 06
- dw StartTrainerBattle_SetUpForWavyOutro ; 07
- dw StartTrainerBattle_SineWave ; 08
+ ; BATTLETRANSITION_WAVY
+ dw StartTrainerBattle_SetUpForWavyOutro
+ dw StartTrainerBattle_SineWave
- ; BATTLETRANSITION_CAVE_STRONGER
- dw StartTrainerBattle_LoadPokeBallGraphics ; 09
- dw StartTrainerBattle_SetUpBGMap ; 0a
- dw StartTrainerBattle_Flash ; 0b
- dw StartTrainerBattle_Flash ; 0c
- dw StartTrainerBattle_Flash ; 0d
- dw StartTrainerBattle_NextScene ; 0e
+ ; BATTLETRANSITION_ZOOM_TO_BLACK
; There is no setup for this one
- dw StartTrainerBattle_ZoomToBlack ; 0f
+ dw StartTrainerBattle_ZoomToBlack
- ; BATTLETRANSITION_NO_CAVE
- dw StartTrainerBattle_LoadPokeBallGraphics ; 10
- dw StartTrainerBattle_SetUpBGMap ; 11
- dw StartTrainerBattle_Flash ; 12
- dw StartTrainerBattle_Flash ; 13
- dw StartTrainerBattle_Flash ; 14
- dw StartTrainerBattle_NextScene ; 15
- dw StartTrainerBattle_SetUpForSpinOutro ; 16
- dw StartTrainerBattle_SpinToBlack ; 17
+ ; BATTLETRANSITION_SPIN
+ dw StartTrainerBattle_SetUpForSpinOutro
+ dw StartTrainerBattle_SpinToBlack
- ; BATTLETRANSITION_NO_CAVE_STRONGER
- dw StartTrainerBattle_LoadPokeBallGraphics ; 18
- dw StartTrainerBattle_SetUpBGMap ; 19
- dw StartTrainerBattle_Flash ; 1a
- dw StartTrainerBattle_Flash ; 1b
- dw StartTrainerBattle_Flash ; 1c
- dw StartTrainerBattle_NextScene ; 1d
- dw StartTrainerBattle_SetUpForRandomScatterOutro ; 1e
- dw StartTrainerBattle_SpeckleToBlack ; 1f
+ ; BATTLETRANSITION_SCATTER
+ dw StartTrainerBattle_SetUpForRandomScatterOutro
+ dw StartTrainerBattle_SpeckleToBlack
+; End of starting points
; BATTLETRANSITION_FINISH
- dw StartTrainerBattle_Finish ; 20
+ dw StartTrainerBattle_Finish
We have changed the order of operations. All of the set up phases (load Poké Ball gfx etc) are done before determining which animation to use. Though, we have a problem. The screen flashing doesn't match. This is because we're only using the function once. We need to make some changes.
StartTrainerBattle_Flash:
call .DoFlashAnimation
ret nc
call StartTrainerBattle_NextScene
ret
.DoFlashAnimation:
ld a, [wTimeOfDayPalset]
cp DARKNESS_PALSET
jr z, .done
ld hl, wBattleTransitionCounter
ld a, [hl]
inc [hl]
srl a
ld e, a
ld d, 0
ld hl, .pals
add hl, de
ld a, [hl]
cp %00000001
jr z, .done
ld [wBGP], a
call DmgToCgbBGPals
and a
ret
.done
xor a
ld [wBattleTransitionCounter], a
scf
ret
This is set up to only flash once and then go to the next scene. Because it was previously in the jumptable 3 times in sequence per transition, it resulted in 3 flashes.
+; Flash screen until b reaches "NUM_BATTLETRANSITION_FLASHES".
+; It can be changed to increase or decrease the amount of flashes.
+; Use 0 to just go straight to the next scene.
+; Credits to Narishma-gb for helping in getting this working.
StartTrainerBattle_Flash:
+ ld b, -1
+.loop
+ inc b
+ ld a, b
+ cp NUM_BATTLETRANSITION_FLASHES
+ jp z, StartTrainerBattle_NextScene
+.inner_loop
+ ; This used to be done by having 3 scenes in sequence for each battle transition.
call .DoFlashAnimation
- ret nc
- call StartTrainerBattle_NextScene
- ret
+ jr c, .loop
+ call DelayFrame ; Prevents flashes from being a lot faster than intended
+ jr .inner_loop
.DoFlashAnimation:
. . .
With those changes made, it should loop 3 times and flash the screen in each iteration through the loop. We can also modify how many times the flashes occur so if you feel like 3 is too many or too less, change the definition of NUM_BATTLETRANSITION_FLASHES that was added to the top of the file.
Due to the changed order in the jumptable, there's a few extra changes we can make:
StartTrainerBattle_DetermineWhichAnimation:
; The screen flashes a different number of times depending on the level of
; your lead Pokemon relative to the opponent's.
; BUG: Battle transitions fail to account for enemy's level (see docs/bugs_and_glitches.md)
. . .
+ ; Due to the changed order of operations, the overall set up like the screen flashing occurs before this instead of after.
+ ; So there's no need to use the vc hook and despawn objects on each starting point.
+ ; Instead, it can be done just before the starting point executes and get the same result.
+ vc_hook Stop_reducing_battle_transition_flashing_WavyOutro
+ farcall RespawnPlayerAndOpponent
ret
StartTrainerBattle_SetUpForWavyOutro:
- vc_hook Stop_reducing_battle_transition_flashing_WavyOutro
- farcall RespawnPlayerAndOpponent
. . .
StartTrainerBattle_SetUpForSpinOutro:
- vc_hook Stop_reducing_battle_transition_flashing_SpinOutro
- farcall RespawnPlayerAndOpponent
. . .
StartTrainerBattle_SetUpForRandomScatterOutro:
- vc_hook Stop_reducing_battle_transition_flashing_ScatterOutro
- farcall RespawnPlayerAndOpponent
. . .
StartTrainerBattle_ZoomToBlack:
- vc_hook Stop_reducing_battle_transition_flashing_ZoomToBlack
- farcall RespawnPlayerAndOpponent
. . .
Because we have changed the order of when things are executed, we have been able to move the despawning of all objects except the player and opponent and the vc hook for reducing to the end of StartTrainerBattle_DetermineWhichAnimation because the phases of where they would've mattered have already occurred. This also means we do not need to worry about making sure that is handled for new anims we add. You might need to modify the vc patch template because of the removal of some hooks.
With those changes done, everything should function as normal but we have simplified the process. Now we can freely modify the screen flashes, it's easier to modify the transition used for each case and we have made it easier for us to add new animations.
4. New transition animation
TODO: new transition animation