-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Attack Animation Tiles (Up to 79 more)
Greetings, this tutorial was inspired by the original "Attack animation tiles" tutorial which offers two methods to add more tiles, with each method allowing for the addition of 22 and 32 tiles respectively. The method I describe below is based on the original Method 2, but allows you to add 79 tiles instead, and also make a lot of space in bank1E that can be prone to getting full. You also won't need to remove any of the "special effects" animations from the game.
A big limitation until now has been the amount of space in bank1E. We will resolve this by moving all of the battle animation tiles to their own bank. In order to make that work, we will also move the code that loads the tiles into the new bank.
Modify engine/battle/animations.asm as follows:
...
; loads tile patterns for battle animations
LoadMoveAnimationTiles:
- ld a, [wWhichBattleAnimTileset]
- add a
- add a
- ld hl, MoveAnimationTilesPointers
- ld e, a
- ld d, 0
- add hl, de
- ld a, [hli]
- ld [wTempTilesetNumTiles], a ; number of tiles
- ld a, [hli]
- ld e, a
- ld a, [hl]
- ld d, a ; de = address of tileset
- ld hl, vSprites tile $31
- ld b, BANK(MoveAnimationTiles0) ; ROM bank
- ld a, [wTempTilesetNumTiles]
- ld c, a ; number of tiles
- jp CopyVideoData ; load tileset
-MACRO anim_tileset
- db \1
- dw \2
- db -1 ; padding
-ENDM
-
-MoveAnimationTilesPointers:
- ; number of tiles, gfx pointer
- anim_tileset 79, MoveAnimationTiles0
- anim_tileset 79, MoveAnimationTiles1
- anim_tileset 64, MoveAnimationTiles2
-MoveAnimationTiles0:
-MoveAnimationTiles2:
- INCBIN "gfx/battle/move_anim_0.2bpp"
-MoveAnimationTiles1:
- INCBIN "gfx/battle/move_anim_1.2bpp"
+ callfar _LoadMoveAnimationTiles
+ ret
Now create a new file in engine/battle/ called load_move_animation_tiles.asm. The contents should be as follows:
_LoadMoveAnimationTiles::
ld a, [wWhichBattleAnimTileset]
add a
add a
ld hl, MoveAnimationTilesPointers
ld e, a
ld d, 0
add hl, de
ld a, [hli]
ld [wTempTilesetNumTiles], a ; number of tiles
ld a, [hli]
ld e, a
ld a, [hl]
ld d, a ; de = address of tileset
ld hl, vSprites tile $31
ld b, BANK(MoveAnimationTiles0) ; ROM bank
ld a, [wTempTilesetNumTiles]
ld c, a ; number of tiles
jp CopyVideoData ; load tileset
MACRO anim_tileset
db \1
dw \2
db -1 ; padding
ENDM
MoveAnimationTilesPointers:
; number of tiles, gfx pointer
anim_tileset 79, MoveAnimationTiles0
anim_tileset 79, MoveAnimationTiles1
anim_tileset 64, MoveAnimationTiles2
MoveAnimationTiles0:
MoveAnimationTiles2:
INCBIN "gfx/battle/move_anim_0.2bpp"
MoveAnimationTiles1:
INCBIN "gfx/battle/move_anim_1.2bpp"
Keep this file open, we'll be making more changes later. And yes, that is more or less the code we just removed from engine/battle/animations.asm. Note that, because this code has the INCLUDE commands for the graphics files, we have relocated the graphics to this other script, but they are still together with the script that needs them.
Next, we need to put the new script in the game.
Edit main.asm:
...
SECTION "bank1E", ROMX
INCLUDE "engine/battle/animations.asm"
INCLUDE "engine/overworld/cut2.asm"
INCLUDE "engine/overworld/dust_smoke.asm"
INCLUDE "gfx/fishing.asm"
INCLUDE "data/moves/animations.asm"
INCLUDE "data/battle_anims/subanimations.asm"
INCLUDE "data/battle_anims/frame_blocks.asm"
INCLUDE "engine/movie/evolution.asm"
INCLUDE "engine/overworld/elevator.asm"
INCLUDE "engine/items/tm_prices.asm"
+SECTION "bankBattleAnim", ROMX
+INCLUDE "engine/battle/load_move_animation_tiles.asm"
At this stage, I recommend saving everything and building your pokered ROM as usual. Go into a battle and ensure that move animations are still working correctly. In particular, you want to ensure that move animations that use tiles from each of the two battle animation tilesets are loaded and used correctly. For example, you might try using Water Gun and Horn Attack.
If your emulator of choice has a Tile Viewer function, you can see that the battle animation tiles are swapped out as required by different move animations.

Once you have confirmed this is working correctly, the next step is to add a third, full size set of 79 tiles to the game!
If you already have a set of tiles, you can use that, otherwise, go to gfx/battle and make a copy of move_anim_0.png. Rename the copy to move_anim_2.png. Whenever you want to add new tiles in the future, directly edit this png file.
In the load_move_animation_tiles.asm file from step 1, add these additional lines. (Apologies for splitting this into two, but I believe this will help to reduce bug related headaches)
MoveAnimationTilesPointers:
; number of tiles, gfx pointer
anim_tileset 79, MoveAnimationTiles0
anim_tileset 79, MoveAnimationTiles1
anim_tileset 64, MoveAnimationTiles2
+ anim_tileset 79, MoveAnimationTiles3
MoveAnimationTiles0:
MoveAnimationTiles2:
INCBIN "gfx/battle/move_anim_0.2bpp"
MoveAnimationTiles1:
INCBIN "gfx/battle/move_anim_1.2bpp"
+MoveAnimationTiles3:
+ INCBIN "gfx/battle/move_anim_2.2bpp"
The next step is extremely important and comes with a "please take care" point that you need to understand when using tiles from your new set.
Open constants/move_animation_constants.asm and make the following changes:
const_def $C0
+ const_def $D8
DEF FIRST_SE_ID EQU const_value
- const_skip $18
const SE_WAVY_SCREEN ; $D8 used in Psywave/Night Shade/Psychic etc.
const SE_SUBSTITUTE_MON ; $D9 used in Substitute (turns the pokemon into a mini sprite)
What did we just do? Well, the special effects are originally set to begin from constant $C0, then then, $18 values are skipped before the first one is actually defined. All we've done is removed that wasted space, and this has the impact that the constant FIRST_SE_ID
becomes equal to $D8 instead of $C0.
I will attempt to explain why this is necessary but do feel free to skip the explanation. However, please ensure you always follow the following rule when using animations with tileset 3:
Never use Delay values above 23
If you are working in pokered-gbc and don't want to read the long explanation, please proceed to Part 4.
If you are not working in pokered-gbc, and don't want to read my essay below, please proceed to Part 5.
engine/battle/animations.asm is responsible for interpreting the animations in data/moves/animations.asm and making the animations happen on screen.
The crucial part of this that I want to draw your attention to is:
cp FIRST_SE_ID ; is this subanimation or a special effect?
jr c, .playSubanimation
This code decides whether to interpret the animation data as a special effect or a subanimation.
When FIRST_SE_ID
is equal to $C0 as it originally was, any animations that use tileset 3 will get accidentally interpreted as special effects.
To understand why this is happening, let's have a look at some move animation data from data/moves/animations.asm
WhirlwindAnim:
battle_anim WHIRLWIND, SUBANIM_1_TORNADO, 1, 6
battle_anim NO_MOVE, SE_SLIDE_ENEMY_MON_OFF
db -1 ; end
Whirlwind consists of one subanimation, the tornado, and one special effect, that slides the opponents off the screen. Finally the "-1" terminator tells the engine that it has reached the end of the animations.
When the engine reads this data, it begins with the first byte of each line. if that is equal to -1 (=255, or $FF) then the animation is over.
If the byte is equal to FIRST_SE_ID
or higher, it is interpreted as a special effect, and this can easily get mixed up because of what the first byte is.
On each row of a move animation, you can see the macro "battle_anim", this stores the data that follows it into for format that the engine requires.
Here is the Macro:
MACRO battle_anim
IF _NARG == 4
db (\3 << 6) | \4
db \1 - 1
db \2
ELSE
db \2
db \1 - 1
ENDC
ENDM
Let's break this down.
IF _NARG == 4
The there are four arguments provided with battle_anim, the data is configured one way, otherwise it is configured a different way. In the case of a special effect, we only specify two arguments to the macro:
battle_anim NO_MOVE, SE_SLIDE_ENEMY_MON_OFF
This means that it is configured the second way. The data is configured into two bytes, the first one is the animation index (which you can see in move_animation_contants.asm), and the second byte is the index for the SFX.
When the engine reads the first byte, it sees that it is above FIRST_SE_ID
, as all special effect animations are, and proceeds to treat it as a special effect.
Subanimations require four arguments to be provided to battle_anim:
battle_anim WHIRLWIND, SUBANIM_1_TORNADO, 1, 6
The data that gets placed in the first byte is a combination of the third and fourth arguments. The Tileset ID, and the delay. The tileset ID is shifted 6 bits to the left and the delay is placed in the lower 6 bits. So for the tornado subanimation in our example the tileset ID is 1, which is 00000001 in binary. This is shifted 6 places to the left resulting in 01000000. The delay is 6, which is 00000110 in binary. So put together, the byte contains 01000110, which is $46 in hex.
The engine sees this is below FIRST_SE_ID
and treats it as a subanimation.
But how about when we use our new tileset?
Because the tileset number is 3, which is 00000011 in binary, even without adding in the delay component,
when shifted 6 places to the left, we get 11000000 which is $C0 in hex. The same value that FIRST_SE_ID
was before we modified it.
This means that without the modification to the FIRST_SE_ID
value, we could never use subanimations with tileset 3.
Now let's add in some delay, for the purposes of testing I modified Water Gun:
WaterGunAnim:
- battle_anim WATER_GUN, SUBANIM_0_WATER_DROPLETS, 0, 6
+ battle_anim WATER_GUN, SUBANIM_0_WATER_DROPLETS, 3, 24
db -1 ; end
Here I've used a delay value of 24, which is extremely long, even Bubble - which is very slow - only uses 22.
The tileset potion of the byte is 11000000 after the bit shift.
The delay portion is 00011000.
Put together, that is 11011000, $D8 in hex, which is our new value of FIRST_SE_ID
after the mod.
This means that if we use tileset 3 with values of delay that are 24 or above, it will once again accidentally interpret the data as a Special Effect.
As long as the delay is 23 or less, you can use these new tiles without any issue:
WaterGunAnim:
- battle_anim WATER_GUN, SUBANIM_0_WATER_DROPLETS, 0, 6
- battle_anim WATER_GUN, SUBANIM_0_WATER_DROPLETS, 3, 24
+ battle_anim WATER_GUN, SUBANIM_0_WATER_DROPLETS, 3, 23
db -1 ; end
If you are not working with pokered-gbc, please proceed to part 5.
In pokered-gbc, the battle animation tiles also get colour palettes assigned to them and we need to handle the palettes for the new tileset.
Edit color/sprites.asm
call LoadAttackSpritePalettes
; Indices 0 and 2 both refer to "AnimationTileset1", just different amounts of it.
; 0 is in-battle, 2 is during a trade.
; Index 1 refers to "AnimationTileset2".
ld a, c
cp 1
ld hl, AnimationTileset2Palettes
jr z, .gotPalette
+ cp 3
+ ld hl, AnimationTileset3Palettes
+ jr z, .gotPalette
ld hl, AnimationTileset1Palettes
.gotPalette
ld de, W2_SpritePaletteMap
ld b, $80
.copyLoop
ld a, [hli]
ld [de], a
inc e
dec b
jr nz, .copyLoop
And later in the same file:
AnimationTileset1Palettes:
INCBIN "color/data/animtileset1palettes.bin"
AnimationTileset2Palettes:
INCBIN "color/data/animtileset2palettes.bin"
+AnimationTileset3Palettes:
+ INCBIN "color/data/animtileset3palettes.bin"
Navigate to color/data/ and make a copy of animtileset1palettes.bin. Rename the new file to animtileset3palettes.bin
This tutorial does not cover modifying the contents of that file, but you can open it in your favourite hex editor and assign colour palettes to each of the tiles in tileset3 as you see fit.
Also inengine/battle/animations.asm, use this slight variation on what I showed above:
LoadMoveAnimationTiles:
ld a, [wWhichBattleAnimTileset]
add a
add a
ld e, a
ld d, 0
; HAX: Load corresponding palettes as well
call _LoadAnimationTilesetPalettes
callfar _LoadMoveAnimationTiles
ret
When creating your move animation data specify the tileset as number 3, and it will use your new tiles.
+WillOWispAnim:
+ battle_anim WILL_O_WISP, SUBANIM_3_WILL_O_WISP, 3, 6
db -1 ; end
Thank you for reading this tutorial, I have been your host, Mr. Poryman of Porygondolier, and I'd like to wish you a wonderful day.