Before You Start
Hey! Before you start! Do you mind following me on Twitter? I really spent a lot of time working on this tutorial series. I had to re-write this tutorial twice. It would be great if you took look at the other projects I work on. http://www.twitter.com/crait
And when you get done, please respond and let me know that you’re done!
Previous parts of this series:
Part 1, Part 2, Part 3, Part 4, Part 5, Part 6
This Tutorial
In this tutorial, we’re going to learn more about writing our own functions, switches, and basically combining everything we’ve learned up to this point in order to make a playable version of Pong that you can play against a simple AI.
Switch
Before we begin, I want to teach you about the switch
keyword. This one is easy and can really save the amount of code you have to write.
Take a look at this code:
if (dude == 0 ) {
a = 10;
b = 2;
}
else if (dude == 1) {
c = 3;
}
else if (dude == 2) {
a = 0;
b = 3;
c = 4;
}
else if (dude == 3) {
a = 1;
}
else if (dude == 4) {
a = 2;
}
Just by looking at this code, you can tell that there are different sections that are executed depending on the value of dude
. You can re-write this same code using the switch
keyword. Check this out:
switch (dude) {
case 0:
a = 10;
b = 2;
break;
case 1:
c = 3;
break;
case 2:
a = 0;
b = 3;
c = 4;
break;
case 3:
a = 1;
break;
case 4:
a = 2;
break;
}
The different sections of code are called cases. The value of dude
is checked and then then the right case is executed. For example, if dude
is equal to 3 the code in the case 3
is run, which is a = 1;
. Whenever the break;
instruction is reached, the Arduboy jumps out of the switch
.
Easy, right? We’ll use this to structure our game’s code.
Planning Our Game
Whew! We’re almost ready to start coding! Let’s plan this game out and then start coding.
By breaking down the project into a series of small steps, it helps me realize how much further I have to go as well as keep me motivated to finish the next step. After each major step, I typically test my game to make sure I didn’t break anything and that the new mechanics are working properly.
To break down our Pong game, we’ll need to follow these steps in development:
- Set up the new sketch
- Create a title screen, game screen, win screen, and lose screen
- Create a ball that bounces around the screen
- Create and control a paddle
- Create the computer’s paddle
- Programming collision
- Adjusting the AI
- Scoring
- Creating a new ball and resetting the game
Don’t worry if these don’t all make sense! I will explain them as we move forward!
1. Creating A New Sketch
Open up your Arduino IDE and create a new sketch. ( File > New ) In the sketch, like always, we’re going to include the Arduboy library, create an Arduboy object, as well as updating our setup()/loop() functions to include the standart display functions. If that sounds like a lot, don’t worry! Just make sure your code looks like this!
//Jonathan Holmes (crait)
//December 7th, 2016
//A simple Pong clone
#include <Arduboy2.h>
Arduboy2 arduboy;
//Variables declared here
void setup() {
arduboy.begin();
//Set-up here
arduboy.clear();
}
void loop() {
arduboy.clear();
//Game code here
arduboy.display();
}
Notice that I added some comments at the top for the project as well as some placeholders. Up until now, this is usually how we started each project, but let’s add a little more to this to give us a better starting point for any future projects.
Frame Rates
Most game consoles can play games at 30 or 60 frames per second. That means that the screen is updated 30 or 60 times a second. The more instructions that a computer has to run in a second, the slower that framerate can be. If the frame rate is too slow, some games are unplayable. Have you ever played a game and it played so slowly that it wasn’t fun anymore?
During the setup() function, we can declare what framerate we want our Arduboy game to be using the arduboy.setFrameRate() function. To make the Arduboy game run at 60 frames per second, include this line of code.
arduboy.setFrameRate(60);
A game slowing down can ruin the gameplay experience, but if a game runs too fast, that can be bad, too! We want the game to run at a consistant framerate. In order to do that, add this to the beginning of the loop() function.
if (!arduboy.nextFrame()) {
return;
}
This isn’t really important, but what this does is check if it’s too early to display the next frame. If it is, the Arduboy will loop again until it’s ready. Pretty much making sure that everything runs as smoothly as possible.
You’re ready!
During the setup() function, we also want to seed the random number generator so that we can grab some random numbers later. Include the srand(7/8);
to your setup() function.
Okay! You should be ready! Here’s the code that you should be able to use at the start of all your projects!
//Jonathan Holmes (crait)
//December 7th, 2016
//A simple Pong clone
#include <Arduboy2.h>
Arduboy2 arduboy;
//Variables declared here
void setup() {
arduboy.begin();
//Seed the random number generator
arduboy.initRandomSeed();
//Set the game to 60 frames per second
arduboy.setFrameRate(60);
arduboy.clear();
}
void loop() {
//Prevent the Arduboy from running too fast
if (!arduboy.nextFrame()) {
return;
}
arduboy.clear();
//Game code here
arduboy.display();
}
2. Structuring The Game
We need to set up a title screen for our game as well as a win and game over screen. We also need a section for our gameplay. These will be called game states. Let’s create a variable to keep track of the game state as well as use the switch
keyword to set up different blocks of code for each state.
Let’s start by declaring a variable called gamestate
.
int gamestate = 0;
In the area of the loop() for the game code , let’s add the switch
and check the value of gamestate
.
switch (gamestate) {
}
If gamestate
is 0, we want the title screen. If it’s 1, we want the gameplay state. If it’s 2 or 3, then we’ll show a win screen or a game over screen. Let’s set up those cases:
switch (gamestate) {
case 0:
//Title screen
break;
case 1:
//Gameplay screen
break;
case 2:
//Win screen
break;
case 3:
//Game over screen
break;
}
Let’s also add text so that we can test this.
switch (gamestate) {
case 0:
//Title screen
arduboy.setCursor(0, 0);
arduboy.print("Title Screen");
break;
case 1:
//Gameplay screen
arduboy.setCursor(0, 0);
arduboy.print("Gameplay");
break;
case 2:
//Win screen
arduboy.setCursor(0, 0);
arduboy.print("Win Screen");
break;
case 3:
//Game over screen
arduboy.setCursor(0, 0);
arduboy.print("Game Over Screen");
break;
}
Here’s the code so far! Test it out and see how it works!
//Jonathan Holmes (crait)
//December 7th, 2016
//A simple Pong clone
#include <Arduboy2.h>
Arduboy2 arduboy;
//Variables declared here
void setup() {
arduboy.begin();
//Seed the random number generator
arduboy.initRandomSeed();
//Set the game to 60 frames per second
arduboy.setFrameRate(60);
arduboy.clear();
}
void loop() {
//Prevent the Arduboy from running too fast
if (!arduboy.nextFrame()) {
return;
}
arduboy.clear();
switch (gamestate) {
case 0:
//Title screen
arduboy.setCursor(0, 0);
arduboy.print("Title Screen");
break;
case 1:
//Gameplay screen
arduboy.setCursor(0, 0);
arduboy.print("Gameplay");
break;
case 2:
//Win screen
arduboy.setCursor(0, 0);
arduboy.print("Win Screen");
break;
case 3:
//Game over screen
arduboy.setCursor(0, 0);
arduboy.print("Game Over Screen");
break;
}
arduboy.display();
}
So, the title screen appears, but nothing else works. Let’s add the ability to go to different screens when you push the A button.
Inside of each case
, let’s check if the A button is pressed, and if it is, then change the value of gamestate
. Usually, we’ll increase gamestate
by 1, but during the last case
, we’ll set it back to 0 in order to cycle through all of the screens.
switch (gamestate) {
case 0:
//Title screen
arduboy.setCursor(0, 0);
arduboy.print("Title Screen");
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 1;
}
break;
case 1:
//Gameplay screen
arduboy.setCursor(0, 0);
arduboy.print("Gameplay");
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 2;
}
break;
case 2:
//Win screen
arduboy.setCursor(0, 0);
arduboy.print("Win Screen");
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 3;
}
break;
case 3:
//Game over screen
arduboy.setCursor(0, 0);
arduboy.print("Game Over Screen");
if (arduboy.pressed(A_BUTTON)) {
gamestate = 0;
}
break;
}
Okay, let’s run the completed code to test it!
3. Bouncing Ball
Alright, let’s create the ball for this game. It’ll bounce around the screen. If it hits the top, bottom, or sides of the screen, it’ll change direction.
We’ll need to declare variables for the ball’s location as well as the ball’s size.
int ballx = 62;
int bally = 0;
int ballsize = 4;
We’ll also create one for the vertical and one for the horizontal direction of on the screen.
int ballright = 1;
int balldown = 1;
Whenever ballright
is 1, we’ll move the ball to the right. If ballright
is -1, we’ll move the ball to the left. Likewise, when balldown
is 1, we’ll move the ball down. When balldown
is -1, we’ll move the ball up.
Inside of the gameplay case, we’ll start by drawing the ball using the arduboy.fillRect()
function. Add this line:
arduboy.fillRect(ballx, bally, ballsize, ballsize, WHITE);
Next, let’s handle the movement of the ball horizontally by changing the value of ballx
.
if (ballright == 1) {
ballx = ballx + 1;
}
if (ballright == -1) {
ballx = ballx - 1;
}
Whenever ballx
is at the left or right edge of the screen, we’ll change the value of ballright
. Where are the left and right edges of the screen? Well, the Arduboy screen is 128 pixels wide, so if ballx
is 0, the ball is on the left side of the screen. If ballx
is 127, then it’s on the right side of the screen.
if (ballx == 0) {
ballright = 1;
}
if (ballx == 127) {
ballright = -1;
}
This is what your code should look like. Give it a test and see the ball bounce from side to side!
//Jonathan Holmes (crait)
//December 7th, 2016
//A simple Pong clone
#include <Arduboy2.h>
Arduboy2 arduboy;
//Variables declared here
int gamestate = 0;
int ballx = 62;
int bally = 0;
int ballsize = 4;
int ballright = 1;
int balldown = 1;
void setup() {
arduboy.begin();
//Seed the random number generator
arduboy.initRandomSeed();
//Set the game to 60 frames per second
arduboy.setFrameRate(60);
arduboy.clear();
}
void loop() {
//Prevent the Arduboy from running too fast
if(!arduboy.nextFrame()) {
return;
}
arduboy.clear();
arduboy.pollButtons();
//Game code here
switch( gamestate ) {
case 0:
//Title screen
arduboy.setCursor(0, 0);
arduboy.print("Title Screen");
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 1;
}
break;
case 1:
//Gameplay screen
arduboy.setCursor(0, 0);
arduboy.print("Gameplay");
//Draw the ball
arduboy.fillRect(ballx, bally, ballsize, ballsize, WHITE);
//Move the ball right
if(ballright == 1) {
ballx = ballx + 1;
}
//Move the ball left
if(ballright == -1) {
ballx = ballx - 1;
}
//Reflect the ball off of the left side of the screen
if(ballx == 0) {
ballright = 1;
}
//Reflect the ball off of the right side of the screen
if(ballx == 127) {
ballright = -1;
}
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 2;
}
break;
case 2:
//Win screen
arduboy.setCursor(0, 0);
arduboy.print("Win Screen");
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 3;
}
break;
case 3:
//Game over screen
arduboy.setCursor(0, 0);
arduboy.print("Game Over Screen");
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 0;
}
break;
}
arduboy.display();
}
Cool! Let’s add vertical movement! It’s pretty much the exact same thing that we did with the ballright
variable.
When balldown
is 1, we’ll add 1 to bally
. When balldown
is -1, we’ll subtract 1 from bally
.
if (balldown == 1) {
bally = bally + 1;
}
if (balldown == -1) {
bally = bally - 1;
}
Of course, we’ll also have to reflect it off of the top and bottom of the screen, right? The Arduboy’s screen is 64 pixels high, so let’s add this code:
if (bally == 0) {
balldown = 1;
}
if (bally == 63) {
balldown = -1;
}
That makes sense, right? Good! Here’s all the code we have so far! Let’s test it out!
//Jonathan Holmes (crait)
//December 7th, 2016
//A simple Pong clone
#include <Arduboy2.h>
Arduboy2 arduboy;
//Variables declared here
int gamestate = 0;
int ballx = 62;
int bally = 0;
int ballsize = 4;
int ballright = 1;
int balldown = 1;
void setup() {
arduboy.begin();
//Seed the random number generator
arduboy.initRandomSeed();
//Set the game to 60 frames per second
arduboy.setFrameRate(60);
arduboy.clear();
}
void loop() {
//Prevent the Arduboy from running too fast
if(!arduboy.nextFrame()) {
return;
}
arduboy.clear();
arduboy.pollButtons();
//Game code here
switch( gamestate ) {
case 0:
//Title screen
arduboy.setCursor(0, 0);
arduboy.print("Title Screen");
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 1;
}
break;
case 1:
//Gameplay screen
arduboy.setCursor(0, 0);
arduboy.print("Gameplay");
//Draw the ball
arduboy.fillRect(ballx, bally, ballsize, ballsize, WHITE);
//Move the ball right
if(ballright == 1) {
ballx = ballx + 1;
}
//Move the ball left
if(ballright == -1) {
ballx = ballx - 1;
}
//Reflect the ball off of the left side of the screen
if(ballx == 0) {
ballright = 1;
}
//Reflect the ball off of the right side of the screen
if(ballx == 127) {
ballright = -1;
}
//Move the ball down
if(balldown == 1) {
bally = bally + 1;
}
//Move the ball up
if(balldown == -1) {
bally = bally - 1;
}
//Reflect the ball off of the top of the screen
if(bally == 0) {
balldown = 1;
}
//Reflect the ball off of the bottom of the screen
if(bally == 63) {
balldown = -1;
}
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 2;
}
break;
case 2:
//Win screen
arduboy.setCursor(0, 0);
arduboy.print("Win Screen");
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 3;
}
break;
case 3:
//Game over screen
arduboy.setCursor(0, 0);
arduboy.print("Game Over Screen");
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 0;
}
break;
}
arduboy.display();
}
There’s one more little tiny thing I want use to change before moving on. If you watch the ball carefully as it moves, you’ll notice that it moves off of the screen on the bottom and right-side.
The reason it does that is because we change the ball’s vertical direction based on the top of the ball hitting the top of the screen or the top of the ball hitting the bottom of the screen as well as checking if the left-side of the ball hits the left-side of the screen or the left-side of the ball hitting the right-side of the screen.
We need to make sure the bottom of the ball bounces against the bottom of the screen and make sure the right-side of the ball bounces off of the right-side of the screen. This is an easy adjustment.
Instead of checking if ballx
is equal to 127, we need to check if ballx + ballsize
is equal to 127. Same for bally
.
Change the reflection cod to…
if (ballx + ballsize == 127) {
ballright = -1;
}
… and…
if (bally + ballsize == 63) {
balldown = -1;
}
Run the code on your Arduboy! Mesmorizing, isn’t it?
//Jonathan Holmes (crait)
//December 7th, 2016
//A simple Pong clone
#include <Arduboy2.h>
Arduboy2 arduboy;
//Variables declared here
int gamestate = 0;
int ballx = 62;
int bally = 0;
int ballsize = 4;
int ballright = 1;
int balldown = 1;
void setup() {
arduboy.begin();
//Seed the random number generator
arduboy.initRandomSeed();
//Set the game to 60 frames per second
arduboy.setFrameRate(60);
arduboy.clear();
}
void loop() {
//Prevent the Arduboy from running too fast
if(!arduboy.nextFrame()) {
return;
}
arduboy.clear();
arduboy.pollButtons();
//Game code here
switch( gamestate ) {
case 0:
//Title screen
arduboy.setCursor(0, 0);
arduboy.print("Title Screen");
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 1;
}
break;
case 1:
//Gameplay screen
arduboy.setCursor(0, 0);
arduboy.print("Gameplay");
//Draw the ball
arduboy.fillRect(ballx, bally, ballsize, ballsize, WHITE);
//Move the ball right
if(ballright == 1) {
ballx = ballx + 1;
}
//Move the ball left
if(ballright == -1) {
ballx = ballx - 1;
}
//Reflect the ball off of the left side of the screen
if(ballx == 0) {
ballright = 1;
}
//Reflect the ball off of the right side of the screen
if(ballx + ballsize == 127) {
ballright = -1;
}
//Move the ball down
if(balldown == 1) {
bally = bally + 1;
}
//Move the ball up
if(balldown == -1) {
bally = bally - 1;
}
//Reflect the ball off of the top of the screen
if(bally == 0) {
balldown = 1;
}
//Reflect the ball off of the bottom of the screen
if(bally + ballsize == 63) {
balldown = -1;
}
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 2;
}
break;
case 2:
//Win screen
arduboy.setCursor(0, 0);
arduboy.print("Win Screen");
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 3;
}
break;
case 3:
//Game over screen
arduboy.setCursor(0, 0);
arduboy.print("Game Over Screen");
//Change the gamestate
if (arduboy.justPressed(A_BUTTON)) {
gamestate = 0;
}
break;
}
arduboy.display();
}