Note: At the time of writing GBVM scripting is not fully documented and some of my statements regarding GBVM scripting might be incorrect or incomplete. This tutorial was developed using GB Studio V3.0.3 and is applicable to that specific version. If you are using GB Studio V3.1, please refer to this article instead.
This tutorial is based on NalaFala (Yousurname)’s Background Tile Swapping Guide for GB Studio 3. Many thanks for providing the basis.
To set up what we’re going to try and achieve in this article, let’s take a look at DuckTales 2’s Niagara level. It features multiple large animated waterfalls that go far beyond the sprite limit. The Game Boy has some neat tricks, so how did they achieve this? Knowing that, how can we recreate it in GB Studio?
Let’s start by breaking down the effect:
- There are multiple animated waterfalls concurrently on screen.
- The player character renders in front of the waterfall without flickering.
- Given it is not possible with sprites, then it must be using background tiles.
- What GB Studio features can we use to animate background tiles?
With this in mind I’ll jump right to it: The animation is created by swapping background tiles for another in memory, and I’ll explain how you can do this in GB Studio.
They are 5 basic aspects of tile swapping I’ll be covering:
- The function used to swap a tile.
- How to create a simple animation.
- The impact and limitations of replacing a tile.
- How to replace a single tile.
- Replacing multiple unique tiles.
Additionally these GBVM Script features:
- Accessing local and global variables.
- Updating local and global variables.
GBVM Script Event
While this might be considered a given to write code using GBVM add the [Miscellaneous: GBVM Script] event.
The event interface is basic (not the language), all you get is a text box where you can write whatever GBVM commands you need. I would love to see future versions implement a simple code auto complete, otherwise known as intellisense in a traditional IDE.
For this tutorial we will be using these GBVM commands
I won’t be explaining all of them in detail for this article, but if you have more questions about them it’s best to asked them in the #gbvm-help Discord channel. The GB Studio Discord is full of helpful members, and it’s a great resource and you’ll see me there often asking question after question.
Tile Swapping Functions
There are two functions within GBVM that can be used to swap a tile for another in-memory:
Of the two, I prefer to use VM_REPLACE_TILE_XY, since it partially solves one of the challenges, locating and defining the target tile to swap. The syntax, or “way to write” the command is as follows, which each part explained. For this tutorial, I refer to the tile currently on screen as the “target”, and the tile which will be replacing it as the “source”.
VM_REPLACE_TILE_XY X, Y, TILEDATA_BANK, TILEDATA, START_IDX
|X||The X position of the target tile to be swapped, e.g. 3|
|Y||The Y position of the target tile to be swapped, e.g. 8|
|TILEDATA_BANK||TILEDATA_BANK is a ROM bank label where the source tile is stored in the ROM’s memory,, e.g. ___bank_tileset_0|
|TILEDATA||TILEDATA is a label where the source tile is addressed from, e.g. _tileset_0|
|START_IDX||The index location of the source tile. Must be value between 0 to 255|
Tilesets are created automatically by GB Studio when your game is built. To make one that can be easily used for a tile swap command, create a scene containing only unique background tiles you want available for swapping. The same rules for a regular scene applies, only unique tiles count to the tile limit, and GB Studio will optimize repeating tiles, which will make it harder to determine a tile’s index – so it’s best to avoid that. Don’t worry about the scene’s “look”, it’s purely being used for reference by the GBVM tile swapping script.
Tilesets are assigned an index (ROM bank number) in alphabetical order according to the name of the image file used for the background, so I’d added an exclamation point (!) at the beginning of the background file’s name to make sure it’s built before the other scenes. Future versions of GB Studio will allow you to use labels for TILEDATA_BANK and TILEDATA from within the UI as mentioned in NalaFala (Yousurname)’s original tutorial.
Examples of Multiple Tilesets
Take the two scenes above: !letters(.png) and !waterfall(.png). The compiler generates the GBVM TILEDATA_BANK reference of ___bank_tileset_0 and ___bank_tileset_1 respectively according to their file names in alphabetical order. The same logic applies for the TILEDATA references: _tileset_0 and _tileset_1.
While GB Studio supports multiple tilesets, I wouldn’t recommend this unless you have more than 256 unique tiles you want to swap.
Each unique tile within the set is assigned an index. To determine a tile’s index imagine putting all the unique tiles within the set on a single row.
Like most programming languages GB Studio uses zero based numbering for its indexes.
The first tile has an index of 0, the second 1, third 2 and so on. If you go to index position 35 for example, you will find the first lowercase ‘a’ tile, located at position X=15 Y=1 in !letters scene.
If you want a more “hands on” visual way of finding a tile’s index, try using the VRAM viewer in bgb, it’s come in handy many times.
The logic for calculating a tile’s index is rather simple if all tiles are unique:
(TileY * BackgroundWidth) + TileX = TileIndex
- The TileX and TileY can be found within the scene editor, showing up as a popup in the bottom left.
- BackgroundWidth is the number of tiles your scene is wide, !letters is 20.
- Letter ‘a’ is at position 15,1 so: (1 * 20) + 15 = 35
- Letter ‘v’ is at position 17,2 so: (2 * 20) + 17 = 57
Let’s Swap A Tile
Now you know the command and its parameters, let’s try swapping the tile in the box located at X=14 Y=9 in the below scene with the tile in ___bank_tileset_0 at index 57 (letter ‘v’ in the !letters scene)
GBVM Script Example
; start of GBVM script; lines starting with ; are comments and are ignored by the compiler ; push the START_IDX value we want onto the stack,; then it can be accessed by the alias .ARG0 ; This take up memory, be sure to release it when done with VM_POPVM_PUSH_CONST 57 ; call the swap function by passing the START_IDX using .ARG0 aliasVM_REPLACE_TILE_XY 14, 9, ___bank_tileset_0, _tileset_0, .ARG0 ; free memory assigned to .ARG0VM_POP 1 ; end of GBVM script
And just like that we have swapped the tile!
Now let’s try replacing the same tile with a tile in !waterfall tileset at index 2.
; start of GBVM scriptVM_PUSH_CONST 2VM_REPLACE_TILE_XY 14, 9, ___bank_tileset_1, _tileset_1, .ARG0VM_POP 1; end of GBVM script
GBVM Script Example
As you can see the above script uses very similar code. The first command loads a value of “2” to the stack. It then uses the same REPLACE_TILE command, but references the second bank and tileset and then finally “pops” the stack 1 “level” to clear the memory used.
Using Global Variables in GBVM Scripts
It’s okay to use hardcoded values in GBVM scripts, but there may be times where you will need access to variables for more flexibility. To use global variables within GBVM Script you must prefix VAR_ to your variable name and replace spaces with underscores. Calls to global variables are also in ALL CAPS.
For example if we have a global variable called Loop Index, to reference it in a GBVM Script we would write it as:
; Loop Index is referenced in GBVM via: VAR_LOOP_INDEX
GBVM Script Example
To you use the Loop Index in VM_REPLACE_TILE_XY we need to reference like so:
VM_REPLACE_TILE_XY 14, 9, ___bank_tileset_0, _tileset_0, VAR_LOOP_INDEX
Animating A Tile
Now that you know how to swap a tile, the next logical thing you might want to try is animating a tile. You can do this by looping a variable’s value and referencing it with a swap tile script..
In the above example, the variable to track the animation state will be stored in the global variable Loop_Index. Each time we loop, increment Loop_Index by 1. When Loop_Index becomes greater than 255 it will wrap back to 0. Normally we would need to check if an animation index is too large for the bounds of the animation, but as there are 256 tiles in !letters, this works fine for this example.
Animating Through A Specific Sequence of Tiles
So how do you handle animating over a range of tiles? Providing that all the tiles are in a sequence, it’s just a matter of defining the upper and lower values of the animation’s range, then use logic to set the value to the lower value if it exceeds the upper value.
The above Timer Event loops over the first 10 tiles, once the Loop_Index is greater than 10, it is reset to 0. Simply vary the upper and lower values to your desired tile indexes as needed. If your tiles are not in sequence, then your logic for calculating the next tile will need to reflect that. For your sanity though, simplest is best and the fastest for a Game Boy to calculate, so try using tiles in sequence.
Replacing a Single Tile
If you want to change a single tile, it must be unique in the target artwork. The tile drawn in the above example that’s cycling between 1, 2 and 3 is unique for that scene. Even if tile 1 is replaced with 2 or 3 from the tileset, since it started with a different memory address it will remain unique after the swap (handy!).
Updating Multiple Unique Tiles
If you want to animate more than 1 unique tile, you need to replace each individually. You will have to use multiple GBVM scripts, but multiple scripts can be written in a single GBVM Script command.
A playable version of this tutorial is available https://phinioxglade.itch.io/gbstudio-3-animated-tile-tutorial and the source code https://github.com/phinioxGlade/gbstudio-3-animated-tile-tutorial Go ahead and download the files and play with them yourself to see everything in action. We glossed over a lot of how GBVM works in this article, so look forward to another article going over how to understand GBVM’s uses. With such a robust toolset, it’s possible to write entire animations entirely in GBVM.
Tile swapping is also the basis on more advanced UI and menuing – so we’ll be looking at that soon.
Aussie software developer, retro game collector, chiptune vinyl enthusiast, did someone say reinvent the wheel but buggier with less features?