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


Modify main.cpp

In the main.cpp file at the top with the other defines, make a new define called REG_KEY. This needs to get set to the register shown above. As explained, this is the register that monitors key presses. We are dereferencing it so we can read it more easily.

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


Slow down

If you ran the previous code and pressed the down button, you would have noticed that you'd see a bunch of vertical stripes. That's because our pixel is moving way too fast. We need to control the speed better. That's where the vcount register comes in. There's not much to say about this register other than it just contains the line number that is currently being written to.

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

We'll add the other directional buttons so that we can move our pixel in any direction. I defined a bunch of masks like I said I would. Notice how each mask is double the previous. This is because everytime you move a bit to the left it doubles the actual value. Notice how these buttons are in the same order as the keys on the page where we talk about the key register. We're not going to use the A and B keys here, but I'm including all of them because we will be using them all eventually.

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.

VisualBoy Advance