(Note: This is Part 3 of my GBA by Example series. A list of my other GBA tutorials can be found here)
It’s Tuesday, which means it’s the arbitrary day of the week I chose to post GBA stuff!
Last week we got a sprite on the screen and moving around in a tiled video mode, but it still left our screen looking a little bit bare. This week we’re going to rectify that, and figure out how to work with Backgrounds! You can make really great looking stuff with backgrounds, or you can do what I did, and make something that looks like this:
This is two backgrounds (one gradient, and one checkerboard), overlapping one another, and moving in opposite directions. Snazzy eh? Today we’re going to cover the absolute minimum you need to know to make something like that.
To kick things off, let’s take a look at what a background actually is on the GBA:
Like Sprites, Backgrounds are rectangular collections of tiles. Unlike Sprites, they can be really, really big (relatively speaking). If you recall from last week, the largest sprite we can make is 64x64 pixels. Backgrounds can be up to 1024x1024 if we want them to. Since we only have 96k of VRAM on the GBA (and 32k of that is for Sprites), it stands to reason that to fit all our background data in, they look a bit different from Sprites in memory.
Just like with Sprites, all colours in a Background come from a Palette, which is a collection of up to 256 different colours, each stored as a 16 bit unsigned integer. Colours on the GBA are stored with 5 bits per channel, with the highest bit ignored, like this:
In code, a Palette might be defined like so:
One thing that hasn’t been mentioned in previous articles is that pixels that use the colour at index 0 are treated as transparent, so you only see the index 0 colour if nothing else gets drawn on top of that pixel. This will be important for us today because we’re going to overlap two backgrounds on top of each other.
A Background’s tiles are the same as a Sprite’s: 8x8 rectangular collections of indices, with each of these storing an index from the palette array. Backgrounds use a separate colour palette from sprites, so you can use an entirely different set of colours for your backgrounds than you do for other stuff in your game. This palette memory, just like with sprites, is large enough to store 256 colours. Since we can only have 256 possible values, tiles store each pixel as an 8 bit index. Tiles are laid out row by row, from top to bottom.
In code, that might look like this:
If you store your tile data in values larger than uint8s, like I did above, remember that the lowest byte in a value is the leftmost pixel.
All of that should be familiar to you if you read last week’s post, but unlike with Sprites, the order of the tiles doesn’t matter when we’re working with backgrounds. This is because backgrounds want to re-use tiles as much as possible. To accomplish this, backgrounds use a third data structure, called a Screen Block, which is a collection of indices into tile memory: One 16 bit value for every 8x8 tile that the background uses.
Screen Blocks are always 32x32 in size, but each of these values represents an 8x8 tile, meaning that backgrounds are made up of one or more blocks of 256x256 pixels.
In code, a Screen Block might look something like this:
As seen here, Screen Blocks are defined row by row, top to bottom, each value representing the index of a tile. When you’re working with 8bpp tiles, this is all there is to it. There’s more to think about in 4bpp mode, but since this is the first time we’re doing anything with bacgkrounds, let’s keep it simple and continue working in 8bpp mode.
The last thing to know is that we can only have between 0 and 4 backgrounds working at the same time. Yay hardware limitations!
This was a lot of theory, and I want to switch gears now and start to build some stuff, but just to recap:
Alright, let’s start putting this into practice!
Because Screen Blocks are so large, I’ve uploaded the data (including tiles and palette) that I’m going to use today to github instead of just including it here.
That gist contains all the information needed to get our first background (the checkerboard) onto the screen. We’ll generate the gradient background in code below.
We know what our data is going to look like, but we haven’t yet covered where it’s going to go. Let’s start with Palette memory, since it’s going to be the most like what we’ve done before.
As mentioned above, Backgrounds use a different palette than Sprites, which naturally means that the background palette is located at a different place in memory:
Perfect, the palette data was easy! Next we need to get our tiles into memory. You may recall from last week that the data for sprite tiles starts at the fifth tile-block in tile memory. This is because the first 4 of those blocks are reserved for backgrounds. So let’s put our tile data into the first one:
All of this is almost identical to last week, so let’s start doing something different and get our Screen Block data into memory. Screen blocks share memory with Tile memory. A Screen Block is 2048 bytes, which means that we can fit 8 of them into a single tile-block. It’s up to us to make sure that we don’t try to put a Screen Block and tile data into the same spot in memory.
If you’re using the example data, you’ll notice that we only have 2 tiles to upload into memory (a checkerboard tile, and a transparent tile), so it’s safe for us to just go 1 Screen Block away from the start of VRAM:
That should about do it for uploading the data we have for our tiles, but I also mentioned that I generated the gradient background in code. Here’s the code for that:
I was torn on whether or not to include this in the post, but I think it’s a good example of another way of working with all the types of memory we’re wrangling to get data on the screen. It also gives us an opportunity to work with a background that uses more than 1 Screen Block, since the gradient is 2 Screen Blocks wide.
If the above code is unclear, that’s ok! I don’t think it was particularly common to generate background data like this anyway. If you want to follow along, just copy and paste the above code and pretend we uploaded that data the same way we did the other data, since it has nothing to do with understanding how the GBA handles backgrounds.
The hard part is officially over! All that’s left now is to tell the hardware to use the data we’re feeding it, and glue all the snippets we have together.
Let’s talk about our friend the display control register (0x04000000), in addition to doing things like setting a video mode, or enabling objects, this value is also used to enable or disable backgrounds.
We get to work with up to four backgrounds at a time on the GBA, and you can enable them like so:
We’re only going to use the first two backgrounds today, but you can turn on all four backgrounds, or only 1 and 3, or any other combination that you want to use.
Also, we’re still in VideoMode_0, this is because it’s the easiest tiled mode to understand, and we (I) still don’t know enough to actually use any of the features in the other tiled modes.
If you’re in a bitmap mode, you need to enable Background 2 in order for anything to appear on the screen, but as far as I know, you can’t actually do anything with it, it’s just a flag needed to make bitmap modes work.
Just like with Sprites (err.. Objects that is), we need to set up a few values to define how the hardware should use our background data. Mercifully, backgrounds are actually much easier to work with than Sprites. They only need a single 16 bit value set.
Since there are only 4 backgrounds, these bits are at constant memory locations:
What each bit in these values means is as follows:
BG | 0x FEDC BA98 7654 3210 |
---|---|
FE | Size (defined below) |
D | Ignored today (see Tonc for info) |
CBA98 | What Screen Block to start at |
7 | Color mode: (1 for 8bpp, 0 for 4bpp) |
6 | Ignored today (see Tonc for info) |
54 | Nothing, empty bits |
32 | Tile Block to use |
10 | Z Depth |
Just like with Sprites, the sizes for backgrounds use the bits above to select a value from another table, for backgrounds, this table is as follows:
Bits | Size (in Tiles) |
---|---|
00 | 32x32 |
01 | 64x32 |
10 | 32x64 |
11 | 64x64 |
Using the above tables, if we wanted to define our first background (the checkerboard), as a 32x32 tile background which uses tiles starting at the first tile block, and uses the second Screen Block (since it’s offset from the start of VRAM to make space for tile memory), we would do the following:
Notice that we want our Z Depth to be 0 as well. The higher this value, the farther back in the drawing order a background is, so a BG at depth 0 will draw on top of backgrounds with any higher values. Since our checkerboard background has the transparent pixels in it, we want it to be drawn on top of whatever will fill in those transparent pixels.
If we put all this together (leaving out the code for the second background), we get:
And if you run that, you should see:
Which is excellent! We officially have our first background on the screen. Let’s add our second one now. Remember that we used 2 Screen Blocks to hold all the values for this background, and we want them laid out horizontally, so we’ll have a 64x32 background. We want it to be at priority 0, and use the data we populated with the gradient generating code above.
If we add the above line, and the required #defines and gradient code to what we have, we get the following (I’ve omitted GenerateGradient() function body for brevity). I promise after this to not paste any more large code blocks into the article :)
If you compile and run the above (filling in the GenerateGradient function), you should end up with this:
Which is almost exactly what we wanted to end up with when we started! All that’s left is to add some movement, and this is pretty easy to do:
In truth, backgrounds don’t really move, your viewport moves over top of the background. This makes sense with 1 background, but it gets a bit abstract when you think about multiple backgrounds moving at once. In essence, all you have to keep in mind is that increasing the X value of the background scrolling register is going to move it to the left, because what you’re doing is actually moving where your screen is to the right. The same is true for the vertical scrolling register.
As you may have guessed from that explanation, each background on the GBA has two additional registers, one for X offset, and one for Y offset. All backgrounds will repeat infinitely as you scroll them, so you can keep incrementing these values at will, without worrying about resetting them when you get to the edge of a background image. These registers are defined as follows:
These are pretty self explanatory, assign numbers to them to make the corresponding background move. The only weird part about them is that they are Write-Only, so you can’t simply increment the value in one of the registers, nor can you ever read the value in the register, you can just write to it.
Using these registers, it’s trivial to modify our code from earlier to make things scroll. For brevity’s sake, I’m just going to show how to modify the while(1){} section from the above code, rather than paste the whole thing again:
This is pretty much what you’d expect, background 0 is being assigned a value that is decreasing, which means it should appear to be moving right on the screen (since our viewport is moving left), and vice versa with the gradient background. This matches up with the scroll directions we had at the top of the post. Which is perfect, because that means we’re done!
I suppose I should link you to some tools that can be used to create backgrounds, but I do so only grudgingly, because I don’t think they’re great. Tonc suggests Mappy and MapEd. To be fair, I haven’t written a tile mapping tool so I don’t really have much of a leg to stand on when criticizing these, but I found them rather fiddly to use, which is why I ended up just hand building some really simple ones for this post.
I’d love to hear about better tools for doing this sort of work. I think Tiled might be better, but I don’t know how set up it is for GBA style stuff. In any case, I’d love to hear about what tools might be better on Twitter. See you next week!