The first step is to always setup your project. The link provided here will show you step-by-step how to do that. If you've already downloaded Devkitpro, then you can skip that step and focus on setting up the folder structure. This is something you'll do for every project so in later projects I'll just assume you've completed this step.
Moving Pixels
Assuming you've followed along with the other tutorials and you know how to draw a pixel onto the screen, you may be wondering how we go about moving that pixel around using the keypad. Afterall, if you plan on making a game (at least one with pixels), then you'll need to know how to use the keys on the GBA to interact with your game. This will be required for some of the other projects we'll be doing.
Project Setup
Initial Code
We are going to start with the code we wrote for the second project.
As always, copy this code and then compile and run it. Once this is working without errors continue. You can make the pixel any color and have it start from anywhere on the screen.
main.cpp
#define u16 unsigned short
#define vu16 volatile u16
#define REG_DISP *(vu16*)0x04000000
#define VRAM (vu16*)0x06000000
u16 color15(u16 r, u16 g, u16 b);
const int WIDTH = 240;
const int HEIGHT = 160;
int x = 120;
int y = 80;
int main()
{
REG_DISP = 0x0403;
vu16 *vram = VRAM;
vram[x + WIDTH * y] = color15(31, 0, 0);
while(1);
return 0;
}
u16 color15(u16 r, u16 g, u16 b)
{
return r | g << 5 | b << 10;
}
GBA Buttons
It's important to understand how the keys work on the GBA. If you're unfamiliar with this, then please check out the link provided to learn more before continuing on. Otherwise, please continue.
REG_KEYS
Modify main.cpp
Also, up until now our while loop has been blank. Well, no more! Add the following code to the while loop. Let me explain what we're doing here. First, I'm taking the value from the REG_KEY define and inverting it. That's what the '~' (tilda) symbol does, just inverts all the bits. So instead of the default value of 0x03FF, inverting the bits turns it into 0xFC00. Then we're doing a 'bitwise and' with the value of 128. Why 128? Because we're checking to see if we're pressing the down button. The decimal value of 128 is the binary value 1000 0000. Notice that the '1' bit corresponds to the position of the down button in the register (bit 7). It would be better to create a mask and give it a name. For example, we could define something like this instead: #define DOWNMASK 0x0080. Remember that 80 is just hex for the decimal 128. We'll change it to this in the next section and also for the rest of the buttons.
Once you add the code below, run compile and run the program. When you press the down button what happens? Something weird and unexpected I bet. We'll take a look at why that's happening and fix it in the next section.
main.cpp
#define REG_KEY *(vu16*)0x04000130
while(1) {
if (~REG_KEY & 128) {
y++;
}
vram[x + WIDTH * y] = color15(31, 0, 0);
}
VBLANK and VSYNC
If you haven't already, then go ahead and read about how the VBLANK and VSYNC works. We're going to implement the VSYNC in the next section.
REG_VCOUNT
Slow down
So we'll have a dereferenced pointer to the vertical line counter so that we can know which line is currently being read at any time. Then in the while loop we'll create two more while loops. Seems weird I know, but what we're doing here is waiting until we know for a fact that we're in the Vertical Blank. That's where you want to be when doing calculations setting up for the next screen draw. That's where our screen HEIGHT variable comes into play. If we first detect that we're in the VBLANK then we wait until we're out of the VBLANK, then the next while loop will wait until all of the lines between 0 and 160 are drawn. Then the rest of our code is guaranteed to start at the very beginning of the next VBLANK. Probably not the best way to handle this, I'm sure there are better more sophisticated methods, but this works for our purposes.
Now compile and run the code and you'll be able to draw a line if you press the down button/key. One thing you may ask though, is why is it drawing a line? I thought we were only moving a single pixel. We are, but in drawing the pixel we're setting a specific color in the VRAM. When we move on to the next pixel we're not erasing the previous VRAM location, which you could do if you only want to show a single pixel. Once that's working we can go ahead and add in the rest of the buttons.
main.cpp
#define REG_VCOUNT *(vu16*)0x04000006
while(1) {
while(REG_VCOUNT >= HEIGHT);
while(REG_VCOUNT < HEIGHT);
if (~REG_KEY & 128) {
y++;
}
vram[x + WIDTH * y] = color15(31, 0, 0);
}
Full Movement
Then in the while loop I replace the other if statement with four if statements so that we can move up, down, left, and right.
main.cpp
#define MASK_A 0x0001
#define MASK_B 0x0002
#define MASK_SELECT 0X0004
#define MASK_START 0x0008
#define MASK_RIGHT 0x0010
#define MASK_LEFT 0x0020
#define MASK_UP 0x0040
#define MASK_DOWN 0x0080
#define MASK_R 0x0100
#define MASK_L 0x0200
while(1) {
while(REG_VCOUNT >= HEIGHT);
while(REG_VCOUNT < HEIGHT);
if (~REG_KEY & MASK_RIGHT) {
x++;
}
else if (~REG_KEY & MASK_LEFT) {
x--;
}
if (~REG_KEY & MASK_DOWN) {
y++;
}
else if (~REG_KEY & MASK_UP) {
y--;
}
vram[x + WIDTH * y] = color15(31, 0, 0);
}
Run and Finish
Compile and run the game and you'll be able to move the sprite anywhere. It almost looks like an Etch-A-Sketch right? It's super easy to build our own Etch-A-Sketch now and we'll do that in a future tutorial.
