LiraNuna's Development Blog

Nintendo DS 2D HW Tutorials
By LiraNuna

Lesson 4: Multiple Backgrounds and scrolling

Table of content
Introduction
Allocating Tile bases
Allocating map bases
Using multiple palettes
Scrolling
Lesson demo


Introduction
On previous lessons, we focused on displaying single layer of background. To make full use of the DS’s HW parallax-scrolling potential, we need to learn how to set up more then a single layer.
On this lesson, we will learn better about how tile bases and map bases work, as well as what scrolling is all about. I advise you to review Lesson 1 before you begin, as this lesson will make use of most of the terms that were discussed on said lesson.

Allocating Tile bases
When showing more then a single background layer, we must know how many tiles each layer uses in order to allocate the proper amount of tile bases in order to prevent tile collision and copy over the tiles of the previous background. If you are unsure of how many tiles your layer takes, there are several ways to figure it out. On this lesson I will show you the two most practical ones:

  • If you got your graphics in a pre-processed form (Such as a bitmap), and your conversion tool outputs necessary information, then the answer is pretty simple. Since you know what color format you are going to use (either 16cols or 256cols), you should know how many tiles you could have at most. In this example we will convert the top layer of this lesson’s demo using gfx2gba, 16col image. As you can see, gfx2gba spits some useful information about the image it has processed. This image has 255 tiles in 16col mode, which easily fits inside a single tile base, so we will mark that the top layer takes up one tile base.
$ gfx2gba -m -c16 -t8 cloudsUp.bmp
 
=======================
gfx2gba Converter v0.13
-----------------------
(C) 2oo1-2oo2 [TRiNiTY]
=======================
 
Reading: cloudsUp.bmp             (256x256 pixel,  16 colors)
Number of tiles before optimization: 1024
Number of tiles after  optimization: 0255
Saving tiled raw bitmap data to: cloudsUp.raw ... ok
Saving map data to: cloudsUp.map ... ok
 
Saving masterpalette to..: master.pal ... ok
 
Total files read & converted.:   1
Colors used before converting:  12
Colors used after converting.:  12
Colors saved.................:   0
  • Another scenario, is when we only have the processed graphics and no conversion information. Intimidating as it looks, this is actually easy then it seems, assuming you got the color mode. Once you have that, all you need to check is the tile’s converted size! On this example we will check the bottom layer and assume we got it pre-processed. Take a look at the tiles file size, you will notice it’s 8160 Bytes (7.96KB). Knowing that a tile base is 16KB in size, we can certainly say that we only need a single tile base for the bottom layer.

Allocating map bases
Going back to Lesson 2, Copying the data, which explains why we have to save at least one tile base for map data. This time, we need two maps, because we are using two background layers. Real problems rises when your backgrounds are not sized 32×32, and therefor using more then a single map base. This lesson will not cover large backgrounds, so for now this is not a problem.
Since we got two backgrounds that uses different maps, and we got 8 free map bases in the first tile base (numbered from 0 to 7), it would be wise to use map base 0 for BG0 (top layer) and map base 1 for BG1 (bottom layer) so we know what map belongs to what background. But of course, any map base is use-able, as long as it doesn’t overlap on a used tile base.

Using multiple palettes
Since we are using two backgrounds, each using it’s own palette of 16 colors, we’ll have to convert each background independently. The first background will be converted normally as you would have done on Lesson 2 (Though in this case we’ll use the -c16 flag (16 color mode). But since the second background is using another palette set (Palette entry 1, in this case), we will have to set the offset of the palette in the map.
This is where our problem arises, since gfx2gba doesn’t have a palette offset flag (and because TRiNiTY didn’t bother to release gfx2gba’s source…), we have two choices – Use a custom tool (Soon to be coded by me) or exploit a bug in gfx2gba to achieve the desired output.
The said bug in gfx2gba is based on the -vN tile add flag. This flag adds N tiles to the output map, but it does not limit the output – so if we set a valuse bigger then 1024 (the GBA/NDS HW tile limit), we will only get a warning. To exploit this bug to our needs, knowing that palette data offset is located at bits 12-15 of the map data, all we have to do is multiply the desired palette offset by 4096 (1 << 12), and use -vN with it.
In this example, we need to set the second background to use palette offset 1, which results to 4096.
Converting the first background:

gfx2gba -m -c16 -t8 cloudsUp.bmp

The only new switch here is the -c16 switch, which means 16 colors output format.
To convert the second background, we’ll use the bug in gfx2gba:

$ gfx2gba -v4096 -m -c16 -t8 cloudsDown.bmp
=======================
gfx2gba Converter v0.13
———————–
(C) 2oo1-2oo2 [TRiNiTY]
=======================
Reading: cloudsUp.bmp             (256×256 pixel,  16 colors)
Number of tiles before optimization: 1024
Number of tiles after  optimization: 0254
***WARNING: adding offset 4069 will exceed max tile index of 1024!
Saving tiled raw bitmap data to: cloudsUp.raw ... ok
Saving map data to: cloudsUp.map ... ok
Saving masterpalette to..: master.pal ... ok
Total files read &amp; converted.:   1
Colors used before converting:  12
Colors used after converting.:  12
Colors saved.................:   0

You can notice the said warning, which should not concern you.

Scrolling
Scrolling is a powerful feature of the hardware, that is used to move a background layer around in a 2D plane (Translation). A background layer can move both on X and Y axises.
Moving a background is as easy as setting a register. there are 8 scrolling registers (2 for each background – H and V – Horizontal and Vertical), named (in libnds) REG_BGxHOFS and REG_BGxVOFS where x is the background layer number (0-3).

If we would like to move BG1 10 pixels to the left, we will use the scrolling register like that:

REG_BG1HOFS = 10;

As simple as setting a variable.
But there is only one drawback when dealing with scrolling, the registers are write only – which means we can not do stuff like:

REG_BG0HOFS = REG_BG0HOFS + 10;
REG_BG1VOFS += 16;
int scrollXBg3 = REG_BG3HOFS;

To keep track of the current scroll value that is located in the register, we will use a dedicated variable for that. For example, constantly moving BG2 4 pixels to the left each frame would be done like that:

int bgScroll = 0;// Infinate loop to move the background
while(1) {
// Adding 4 to the offset
bgScroll += 4;
 
// Writing value to register
BG2HOFS = bgScroll;
 
// Waiting for the next frame
swiWaitForVBlank();
}

That is all that can be teached about scrolling. Yeah, seriously… No I mean it, that is all, it’s that easy!

Lesson demo
This lesson’s demo will be made out of two backgrounds that holds the graphics of clouds.
Top layer of the clouds is scrolling in a rate of 2 pixels per frame, using tile base 1 and map base 0.
The bottom layer of the clouds is scrolling in a slower rate of 1 pixel per frame, using tile base 2 and map base 1.
The effect achieved is a fake sense of depth, which makes the demo looks really nice.

	// Including libnds’ set of defines
#include <nds.h>
 
	// Include basic memory functions
#include <string.h>
 
	// Including our converted graphics
 
	// Bottom layer
#include "cloudsDownTiles_bin.h"
#include "cloudsDownMap_bin.h"
#include "cloudsDownPal_bin.h"
 
	// Top level
#include "cloudsUpTiles_bin.h"
#include "cloudsUpMap_bin.h"
#include "cloudsUpPal_bin.h"
 
int main()
{
		// Enable Vertical blank interrupt for waitForVBlank BIOS call
	irqInit();
	irqEnable(IRQ_VBLANK);
 
		// Setting up video mode
	REG_DISPCNT = MODE_0_2D | DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE;
 
		// Now setup each backgrown seperately:
 
		// This would be the top cloud layer
	REG_BG0CNT = BG_32x32 | BG_COLOR_16 | BG_MAP_BASE(0) | BG_TILE_BASE(1);
 
		// This would be the bottom cloud layer
	REG_BG1CNT = BG_32x32 | BG_COLOR_16 | BG_MAP_BASE(1) | BG_TILE_BASE(2);
 
		// Setting VRAM Bank A for Background display
	VRAM_A_CR = VRAM_ENABLE | VRAM_A_MAIN_BG;
 
		// Copy first backgroud - top layer
		// Copying the tiles to tile base 1
	memcpy((void*)BG_TILE_RAM(1), cloudsUpTiles_bin, cloudsUpTiles_bin_size);
		// Copying the map to map base 0
	memcpy((void*)BG_MAP_RAM(0), cloudsUpMap_bin, cloudsUpMap_bin_size);
		// Copying the palette to the palette area
	memcpy((void*)BG_PALETTE, cloudsUpPal_bin, cloudsUpPal_bin_size);
 
		// Copying second backgroud - bottom layer
		// Copying the tiles to tile base 2
	memcpy((void*)BG_TILE_RAM(2), cloudsDownTiles_bin, cloudsDownTiles_bin_size);
		// Copying the map to map base 1
	memcpy((void*)BG_MAP_RAM(1), cloudsDownMap_bin, cloudsDownMap_bin_size);
		// Copying the palette to the second set of 16col palette
	memcpy((void*)&BG_PALETTE[16], cloudsDownPal_bin, cloudsDownPal_bin_size);
 
		// Declare track variables for both layers
	int topScrollX = 0;
	int bottomScrollX = 0;
 
		// Now to infinatly scrool them
	while(1) {
			// Increase both in diffeernt speeds
		topScrollX += 2;
		bottomScrollX += 1;
 
			// And write the data to the scroll register (Write only!)
		REG_BG0HOFS = topScrollX;
		REG_BG1HOFS = bottomScrollX;
 
			// Wait for the next frame
		swiWaitForVBlank();
	}
}

Download the current lesson demo HERE.

If you got still have questions or thoughts regarding multiple backgrounds layers or scrolling, you can find ways on how to reach me from the contact me page.