Make Your Own Arduboy Game: Part 6 - Graphics!


(Holmes) #1

Previous parts of this series: Part 1, Part 2, Part 3, Part 4, and Part 5

This Tutorial

In this tutorial, I’m going to show you how to make a game demo where you can move a sprite around a screen that has a tiled background. I’ll also teach you some more basic programming fundamentals.

Starting Code

Let’s use this code to start our program. Open the Arduino IDE, start a new project, and make sure the code looks like this:

//Jonathan Holmes (crait)
//November 1st, 2016
//Moving Character Demo
#include <Arduboy.h>
Arduboy arduboy;
void setup() {
	arduboy.begin();
	arduboy.clear();
}
void loop() {
	arduboy.clear();
	
	arduboy.display();
}

Starting Images

Right-click and download these two images to your computer.

This image will be your background image. We’re going to tile it across the entire Arduboy screen.

This image will be your player sprite, which is the image that represents your character. You’ll move this face around your screen on the Arduboy.

Convert Images

Remember how I said that you can store data and variables in several formats? Remember, one format that we used before for numbers was int. Well, to store images, we actually want to convert them into raw data that we can store them in byte arrays. Don’t worry too much about what that means, just know it’s different than an int and can hold a different amount of data.

I’ve seen some people on this forum say that they convert images to raw data by hand, but luckily, I created an online tool that will do it for us called ToChars. Open up http://www.crait.net/tochars/ in a new tab and follow these instructions for each image.

1) Click Start

2) Click Browse

3) Set Size

For the background image, set the height and width to both be 8 pixels. For the player sprite, set them to be 16 pixels.

4) Click Convert

5) Grab Data

Here’s the raw data for background image:

0x84, 0x20, 0x9, 0x00, 0x24, 0x00, 0x10, 0x80, 

Here’s the raw data for the player sprite:

0xfe, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0x1, 0xc1, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0xfe, 0x7f, 0x80, 0x9c, 0xbc, 0xb0, 0xb0, 0xb2, 0xb2, 0xb3, 0xb0, 0xb0, 0xb0, 0xbc, 0x9c, 0x80, 0x7f,

Hold onto these for a second and I’ll teach you where to put them.

Talking About Initializing Variables

Alright, I kinda left out some information about initializing variables. So far, I’ve had you initialize variables at the top of the sketch, right under the Arduboy arduboy; line. But, it’s important to know that you can actually initialize them pretty much anywhere. We’ll do that later on, so I don’t want it to surprise you.

I want to teach you a shortcut, though. When we’ve been initializing variables, we have been doing them in the following format:

int counter;
counter = 5;

This is valid, but there’s a shortcut that you can take. You can actually set a value to a variable at the same time you initialize it.

int counter = 5;

Cool, huh? You can also do this with constants.

Constants

A constant is like a variable in the sense that you can have it store data that you can also initialize. The only difference is that the data can’t be changed. It saves memory to store data in constants instead of variables if you aren’t going going to change the data that it holds. We’ll do that with our image data since we won’t change the images at all.

To create a constant, you use the const keyword in front of the initialization.

int counter = 5;
const int height = 5;

Since you can’t change the data inside of a constant, height cannot be changed or given a new value. If you were to use the following code, you would get an error and the code will not work.

height = 12;

Storing Images

Whew! That was a lot to tell you real quick, but let’s move on to storing the raw image data into byte arrays. Where you normally initialize variables, put the following code:

const unsigned char background[] PROGMEM  = {
    0x84, 0x20, 0x9, 0x00, 0x24, 0x00, 0x10, 0x80,
};
const unsigned char player[] PROGMEM  = {
    0xfe, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0x1, 0xc1, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0xfe, 0x7f, 0x80, 0x9c, 0xbc, 0xb0, 0xb0, 0xb2, 0xb2, 0xb3, 0xb0, 0xb0, 0xb0, 0xbc, 0x9c, 0x80, 0x7f,
};

To summarize this code, we converted 2 images to 2 groups of characters. One is named background and the other is named player. Let’s break this code down a little and go word-by-word.

  • const: We already know that we are creating a variable that cannot be changed.
  • unsigned: This means that we cannot use negative values. Just ignore this for a while.
  • char: The way we store byte data is by putting it into a char, which stands for character. This is the kind of variable that we are creating instead of using an int like the last tutorial. :wink:
  • background: Like all variables, we need to give these two character arrays names. The top one is called background and the bottom one is called player.
  • []: The brackets that you see here means that we’re creating an array. An array is a group of variables. We’re creating groups of char variables. We can call them character arrays.
  • =: Like in the shortcut that I explained above, when you initialize a variable, you can assign a value to it at the same time. We can do this with arrays. We’ll actually assign the characters directly into the array.
  • { }: Anything inside of the braces is what we’re actually putting inside of the array.
  • 0x84, 0x20,...: This is the data that we got from the ToChars site. It’s an image stored in multiple bytes/characters. Each character is separated by a comma. ToChars converted the images to characters. Get it? :laughing:

What about PROGMEM ?? No need to worry too much about what that means. Leave that in, though! It’s a special word called a macro that will tell the compiler where to store the array in memory. It makes storing images more efficient for us.

Your game’s code should look like this:

//Jonathan Holmes (crait)
//November 1st, 2016
//Moving Character Demo
#include <Arduboy.h>
Arduboy arduboy;
const unsigned char background[] PROGMEM  = {
    0x84, 0x20, 0x9, 0x00, 0x24, 0x00, 0x10, 0x80,
};
const unsigned char player[] PROGMEM  = {
    0xfe, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0x1, 0xc1, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0xfe, 0x7f, 0x80, 0x9c, 0xbc, 0xb0, 0xb0, 0xb2, 0xb2, 0xb3, 0xb0, 0xb0, 0xb0, 0xbc, 0x9c, 0x80, 0x7f,
};
void setup() {
    arduboy.begin();
    arduboy.clear();
}
void loop() {
    arduboy.clear();
    arduboy.display();
}

Parameters

I can’t wait to show you how to draw the above images to the Arduboy screen! But I can’t just yet! :open_mouth: I need to tell you about a function’s parameters! Remember that a function is an instruction for the computer. Sometimes, they look like this:

arduboy.clear();

That clears the Arduboy’s screen. It’s a function that is easy to understand. Here’s another:

arduboy.print(randomnumber);

This is from the last tutorial. The randomnumber is a variable that we put into the function. We told the Arduboy what to print. That’s called a parameter. And in fact, functions can have multiple parameters. We already saw one earlier that did this in the last tutorial, too!

arduboy.setCursor(0, 0);

The arduboy.setCursor() function requires two parameters that are separated by a comma.

The function to draw an image to the Arduboy’s screen has a lot more parameters that you need to use. This is actually pretty common.

Drawing Images

So, we want to draw an image to the Arduboy screen, finally! Let’s start by drawing the player sprite that we called player. Inside of the loop() area, after we clear the screen, let’s add this line of code:

arduboy.drawBitmap(5, 10, player, 16, 16, WHITE);
  • The first two parameters are 5 and 10. These numbers represent the X and Y location that the image will be drawn to. Changing these numbers will change where the image appears.
  • The next parameter that’s given is the image that we want to draw, which is player.
  • The next two parameters are the width and height of the image that is being drawn. For the player image, we’ll have 16, 16, but for the background image, we’ll have 8, 8 since that’s how big those images are.
  • the last parameter is what color we want to draw to the screen. WHITE means that we’re drawing all the white pixels in the image as white. BLACK means that we’re drawing all the white pixels in the image as black. It seems counter-intuitive, but it becomes useful later on.

Okay! If your code looks like the following, then go ahead and put it on your Arduboy!

//Jonathan Holmes (crait)
//November 1st, 2016
//Moving Character Demo
#include <Arduboy.h>
Arduboy arduboy;
const unsigned char background[] PROGMEM  = {
    0x84, 0x20, 0x9, 0x00, 0x24, 0x00, 0x10, 0x80,
};
const unsigned char player[] PROGMEM  = {
    0xfe, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0x1, 0xc1, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0xfe, 0x7f, 0x80, 0x9c, 0xbc, 0xb0, 0xb0, 0xb2, 0xb2, 0xb3, 0xb0, 0xb0, 0xb0, 0xbc, 0x9c, 0x80, 0x7f,
};
void setup() {
    arduboy.begin();
    arduboy.clear();
}
void loop() {
    arduboy.clear();
    arduboy.drawBitmap(5, 10, player, 16, 16, WHITE);
    arduboy.display();
}

Your Arduboy should properly display as:

Control The Sprite

Okay, remember how we controlled the value of a variable with the Up and Down buttons? To move the sprite around, we’ll do something very similar.

To start, let’s initialize 2 variables at the top of the sketch:

int playerx = 5;
int playery = 10;

The playerx and playery variables will hold the coordinates for our image as it’s moving around. In the arduboy.drawBitmap() function, let’s replace the X and Y parameter with these variables.

arduboy.drawBitmap(palyerx, playery, player, 16, 16, WHITE);

Let’s increase/decrease those variables’ values when the Up/Down and Left/Right buttons are pressed.

if(arduboy.pressed(LEFT_BUTTON)) {
    playerx = palyerx - 1;
}
if(arduboy.pressed(RIGHT_BUTTON)) {
    playerx = palyerx + 1;
}
if(arduboy.pressed(UP_BUTTON)) {
    playery = palyery - 1;
}
if(arduboy.pressed(DOWN_BUTTON)) {
    playery = palyery + 1;
}

That’s all it takes to move an image around the screen! Test out the code on your Arduboy and have fun with it!

//Jonathan Holmes (crait)
//November 1st, 2016
//Moving Character Demo
#include <Arduboy.h>
Arduboy arduboy;
int playerx = 5;
int playery = 10;
const unsigned char background[] PROGMEM  = {
    0x84, 0x20, 0x9, 0x00, 0x24, 0x00, 0x10, 0x80,
};
const unsigned char player[] PROGMEM  = {
    0xfe, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0x1, 0xc1, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0xfe, 0x7f, 0x80, 0x9c, 0xbc, 0xb0, 0xb0, 0xb2, 0xb2, 0xb3, 0xb0, 0xb0, 0xb0, 0xbc, 0x9c, 0x80, 0x7f,
};
void setup() {
    arduboy.begin();
    arduboy.clear();
}
void loop() {
    arduboy.clear();
    if(arduboy.pressed(LEFT_BUTTON)) {
        playerx = playerx - 1;
    }
    if(arduboy.pressed(RIGHT_BUTTON)) {
        playerx = playerx + 1;
    }
    if(arduboy.pressed(UP_BUTTON)) {
        playery = playery - 1;
    }
    if(arduboy.pressed(DOWN_BUTTON)) {
        playery = playery + 1;
    }
    arduboy.drawBitmap(playerx, playery, player, 16, 16, WHITE);
    arduboy.display();
}

For Loop

Okay, remember how I said that loops tell the computer to repeat a set of actions over and over again? I’m going to teach you about one called the for loop. This loop allows you to repeat a set of instructions for a specified amount of times.

Why is this important? We want to make a background for this game, but the background image that we have is too small to fill the entire screen, so we’ll print it many times in different places until it fills up the entire screen.

Let’s say we want to use the arduboy.print(); function to print your name 10 times in a row. We’d have to use a loop keyword and we’ll need a variable to keep track of how many times we’ve printed so far, and a line of code to increase that variable every time we loop through. Luckily, the for loop has a lot of that built-in. Take a look at it, below:

for( int counter = 0; counter < 10; counter = counter + 1 ) {
    arduboy.print("crait");
}

Alright, again, let’s break this down:

  • for: This is kinda like a function. It lets the Arduboy know you want to use a for loop.
  • int counter = 0;: Whenever you run a for loop, you’ll need to initialize/set a variable. We create a counter variable that is equal to 0. This code gets executed before the loop is entered.
  • counter < 10;: This is the check to see if the loop should run or not. If counter is less than 10, then it should run.
  • counter = counter + 1: After all of the instructions inside of the loop are followed, this code is ran. It increases counter by 1. Eventually, counter will grow big enough so that the loop does not execute anymore.
  • { }: Anything inside of these braces will be considered part of the loop and only be executed when the Arduboy is running this for loop.

Instead of this code printing my name 10 times, we can have it count!

for( int counter = 0; counter < 10; counter = counter + 1 ) {
    arduboy.print( counter );
}

If we run the above loop, the Arduboy would print out the numbers 0 to 9! You can change the number of times it will loop, you can change what number the counter starts with, and you can even change how many numbers counter is increased by every time the loop is run.

Tile Background

Alrighty! Let’s get the background implemented! We’re almost done!

The Arduboy’s screen resolution is 128 pixels wide by 64 pixels tall, which means if we use a background image of 8 pixels by 8 pixels, we would have 16 columns wide by 8 rows high.

Let’s tile the background image 8 times across the top of the screen.

for( int backgroundx = 0; backgroundx < 128; backgroundx = backgroundx + 8 ) {
    arduboy.drawBitmap( backgroundx, 0, background, 8, 8, WHITE );
}

Do you understand what’s going on, here? :slight_smile: Notice that the backgroundx variable is used to count the loop, but also used when drawing the background image. Since we want to span the background across the width of the screen, we want to count up to 128. Because we want to put a new image every 8 pixels, we’ll increase it by 8 every time a tile is drawn.

Add this code before you draw your sprite and run it on your Arduboy. It should appear like this:

Next, let’s add another for loop. Instead of doing this after the previous loop, we’ll have this loop inside of the other!

for( int backgroundx = 0; backgroundx < 128; backgroundx = backgroundx + 8 ) {
    for( int backgroundy = 0; backgroundy < 64; backgroundy = backgroundy + 8 ) {
        arduboy.drawBitmap( backgroundx, backgroundy, background, 8, 8, WHITE );
    }
}

To summarize the above code, for each column on the screen, we will draw several rows of background images until the screen is full. This is the result:

Here’s the full code:

//Jonathan Holmes (crait)
//November 1st, 2016
//Moving Character Demo
#include <Arduboy.h>
Arduboy arduboy;
int playerx = 5;
int playery = 10;
const unsigned char background[] PROGMEM  = {
    0x84, 0x20, 0x9, 0x00, 0x24, 0x00, 0x10, 0x80,
};
const unsigned char player[] PROGMEM  = {
    0xfe, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0x1, 0xc1, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0xfe, 0x7f, 0x80, 0x9c, 0xbc, 0xb0, 0xb0, 0xb2, 0xb2, 0xb3, 0xb0, 0xb0, 0xb0, 0xbc, 0x9c, 0x80, 0x7f, 
};
void setup() {
    arduboy.begin();
    arduboy.clear();
}
void loop() {
    arduboy.clear();
    if(arduboy.pressed(LEFT_BUTTON)) {
        playerx = playerx - 1;
    }
    if(arduboy.pressed(RIGHT_BUTTON)) {
        playerx = playerx + 1;
    }
    if(arduboy.pressed(UP_BUTTON)) {
        playery = playery - 1;
    }
    if(arduboy.pressed(DOWN_BUTTON)) {
        playery = playery + 1;
    }
    //For each column on the screen
    for( int backgroundx = 0; backgroundx < 128; backgroundx = backgroundx + 8 ) {
        //For each row in the column
        for( int backgroundy = 0; backgroundy < 64; backgroundy = backgroundy + 8 ) {
                //Draw a background tile
            arduboy.drawBitmap( backgroundx, backgroundy, background, 8, 8, WHITE );
        }
    }
    //Draw player sprite
    arduboy.drawBitmap(playerx, playery, player, 16, 16, WHITE);
    arduboy.display();
}

Clean Up

Uh, oh! You notice the problem? Our character’s face is clear and you can see the background though it. How can we fix that? Let’s just simply draw a black square behind it to block the background images. Put this before we draw the player’s sprite.

arduboy.fillRect(playerx, playery, 16, 16, BLACK);

Finished Code

Here it is! Run this code on your Arduboy to make sure it works properly! If it does, then feel free to modify this code to start making your own game! :smiley:

//Jonathan Holmes (crait)
//November 1st, 2016
//Moving Character Demo
#include <Arduboy.h>
Arduboy arduboy;
int playerx = 5;
int playery = 10;
const unsigned char background[] PROGMEM  = {
    0x84, 0x20, 0x9, 0x00, 0x24, 0x00, 0x10, 0x80,
};
const unsigned char player[] PROGMEM  = {
    0xfe, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0x1, 0xc1, 0x1, 0x3d, 0x25, 0x25, 0x3d, 0x1, 0xfe, 0x7f, 0x80, 0x9c, 0xbc, 0xb0, 0xb0, 0xb2, 0xb2, 0xb3, 0xb0, 0xb0, 0xb0, 0xbc, 0x9c, 0x80, 0x7f, 
};
void setup() {
    arduboy.begin();
    arduboy.clear();
}
void loop() {
    arduboy.clear();
    if(arduboy.pressed(LEFT_BUTTON)) {
        playerx = playerx - 1;
    }
    if(arduboy.pressed(RIGHT_BUTTON)) {
        playerx = playerx + 1;
    }
    if(arduboy.pressed(UP_BUTTON)) {
        playery = playery - 1;
    }
    if(arduboy.pressed(DOWN_BUTTON)) {
        playery = playery + 1;
    }
    //For each column on the screen
    for( int backgroundx = 0; backgroundx < 128; backgroundx = backgroundx + 8 ) {
        //For each row in the column
        for( int backgroundy = 0; backgroundy < 64; backgroundy = backgroundy + 8 ) {
                //Draw a background tile
            arduboy.drawBitmap( backgroundx, backgroundy, background, 8, 8, WHITE );
        }
    }
    //Draw black square
    arduboy.fillRect(playerx, playery, 16, 16, BLACK);
    //Draw player sprite
    arduboy.drawBitmap(playerx, playery, player, 16, 16, WHITE);
    arduboy.display();
}

What’s Next?

What’s next? I’ll teach you how to make your own Pong game from scratch!! Click here to go check it out!

Credits

I wrote this tutorial in order to give back to the programming community that taught me to get into it about 10 years ago. If you’d like to follow me on Twitter, please do so at http://www.twitter.com/crait . I’d greatly appreciate that. :smile:


Make Your Own Arduboy Game: Part 7 - Make Pong From Scratch!
Make Your Own Arduboy Game: Part 5 - Your First Game!
Example of Arduboy2 Sprites
Make Your Own Arduboy Game: Part 7 - Make Pong From Scratch!
Circuit Dude [2.1] - Awesome Puzzle Game With 50 Levels!
Make Your Own Arduboy Game: Part 9 - Mapping DinoSmasher
How Easy Are These To Program
How to make your own sprites
Make Your Own Arduboy Game: Part 8 - Starting DinoSmasher
#2

Thank you for making these!!


(Holmes) #3

Thanks for the support! I’m working on the 7th tutorial right now! I hope I’m done with it tomorrow!


#4

Perfect! I’m just getting caught up with these tonight!


(Holmes) #5

Great! What kind of game are you hoping to make?


#6

I’d like to make a platform game or maybe an action game. I’ve got no C/C++ experience but I am OK at using Game Maker: Studio’s language (which I think is based on Lua?). I bought an Arduboy so that I could start learning ‘proper’ programming and challenge myself to make small projects that fit the console.


(John) #7

Love your tutorials, can’twait!


(Holmes) #8

Hey, ya’ll! Part 7 is out, now! Make Your Own Arduboy Game: Part 7 - Make Pong From Scratch!


(B Alan Eisen) #9

Thank you very much. I might try to reproduce part of the book of graphics that I designed 30 years ago. I will put it out as freeware, of course!


(Holmes) #10

The more the merrier! :smiley:


(Terry) #11

Thank you so much for your very helpful tutorials! I’m a total noob so this has been great to learn the basics.

I have been trying to animate a sprite with multiple frames but cannot figure this out. Been looking at other games code but it’s still confuses me! Can you please make a tutorial (or just explain how to code) on how a sprite can be animated and moved the same time?

Thanks again:)


(Mike McRoberts) #12

I have to admit that even with my experience in programming, trying to read the documentation for the Arduboy library and work this out isn’t easy for me never mind for beginners. There are several different commands for drawing sprites with or without masks and the documentation does a bad job of explaining how to use them. Also, how are the actual bitmap files generated and what format are the sprites, frames, masks, tiles, etc. all stored in that bitmap> it’s a mystery not made easy at all. A good clear tutorial on these things would be a massive help to us all.


(Celine) #13

I get this to work but my character goes out of the screen if i move it too much to the right for example… is there a way to set a boundary?


(curly) #14

yes

im probably doing it wrong but i use locx and locy as variables
(loc short for location)
and only let the charicter move left or right if locx is greater than zero and less than 128
and only up and down if locy is greater than zero and less than 64

// move player forward one pixel if (ab.pressed(RIGHT_BUTTON) && (locx < 100)){ locx += 1; }

(though in that i didnt want the player to move more than 100 px right from zero)


(Holmes) #15

@curly’s response is very close to what you want.

Instead of the standard left, right, up, and down input, I’d suggest something along the lines of…

    if( arduboy.pressed(LEFT_BUTTON) && playerx > 0 ) {
        playerx = playerx - 1;
    }
    if( arduboy.pressed(RIGHT_BUTTON)  && playerx + 16 <= 128 ) {
        playerx = playerx + 1;
    }
    if( arduboy.pressed(UP_BUTTON) && playery > 0 ) {
        playery = playery - 1;
    }
    if( arduboy.pressed(DOWN_BUTTON) && playery + 16 <= 64 ) {
        playery = playery + 1;
    }

Basically, if you push up, you’re checking to see if the player’s X position is at the left of the screen or not. If it’s not at the left-side, you’ll be able to move to the left. To check the right side, you need to add the width of the player to the player’s X location in order to check the right side of the player with the right side of the screen. Use the same logic for the top and bottom of the screen. If you’re at the top of the screen (playery would be 0), you shouldn’t be able to move up. If the player is at the bottom, then the playery + 16 will be equal to the height of the screen.


(curly) #16

im not seeing any difference besides you call your variable something different than i call mine and yours is bigger , from what i understand

playery = playery + ;

will do the exact same thing as
playery += 1;


(Holmes) #17

You’re right in saying that playery = playery + 1; is the same as playery += 1; . I was just being more specific with my example as it pertains to the original code, just in case someone else was looking over these responses and wondering the same thing.


(Celine) #18

Thank you @crait for expanding on your example! Thank you too @curly, understood the logic behind your code but i was wondering how to define loc… :sweat_smile:


(curly) #19

now that one im 90% shure im doing wrong :stuck_out_tongue: i define all variables right after including the arduboy2 library, though i THINK you can define things as you need them, and that may even be better… er im a noob :stuck_out_tongue:


(Scott) #20

And to make it even simpler:

playery++;

or

++playery;

(Ever wonder why it’s called C++? It’s because it’s one language higher than C, or an increment of the C language.)