Coding for Arduino 2
Asteroid Attack
Module 5, Project 1: Asteroid Attack
Use the following guide to build the CTC-101 Asteroid Attack project.

Create a retro style space shooter game!
In this project, You will create a retro style space shooter game. In the end you will be able to tweak values and rewrite the program to redesign the game so it suits your taste. You will create the game step-by-step, adding complexity as you move along. The goal of the game is to shoot asteroids to collect points, and at the same time avoid collisions.
Materials


- 1 x control board
- 1 x control board
- 1 x education shield
- 1 x push button module
- 1 x module cable
- 1 x Asteroid Attack kit
Step 1
Gather all the construction materials for the Asteroid attack.

Step 2
Let’s start building the Asteroid attack!

Step 3

Step 4

Step 5
Attach the shield onto the top of the board, with the slotted holder piece in between the shield and the board.

Step 6
Connect the push button module to D6, to the shield.

Step 7
Use the module cable to connect the module to the shield.

Step 8

Step 9
Check that your wiring is ready and connect the board to the computer.

Step 10
Find the Asteroidattack program and open it.

/*
* AsteroidAttack
*
* You will create a retro style space
* shooter game, and learn about how to use classes and functions. In the end you will be able to
* tweak values and rewrite the program to redesign the game so it suits your taste.
*
* (c) 2013-2016 Arduino LLC.
*/
#include <EducationShield.h>
IMU imu; // the IMU
Button button(6);
int buttonState = 0;
const int ledPin = 13; // activity LED pin
boolean blinkState = false; // state of the LED
void setup() {
Serial.begin(9600); // initialize Serial communication
imu.begin();
button.begin();
// configure Arduino LED for activity indicator
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH); // turn on led to show that the board has executed
}
void loop() {
// read raw gyro measurements from device
imu.run();
// get the gyro result from the filter and convert them into INT
int pitch = imu.getPitch();
int roll = imu.getRoll();
//Check buttons
if(button.isPressed()){
buttonState=true;
}
// when Serial is open, send a "s" to processing
if (Serial.available() > 0) {
int val = Serial.read();
if (val == 's') { // if incoming serial is "s"
Serial.print(roll); Serial.print(",");
Serial.print(buttonState); Serial.print(",");
Serial.println("");
buttonState=0;
}
}
// blink LED to indicate activity
blinkState = !blinkState;
digitalWrite(ledPin, blinkState);
delay(30);
}
Step 11
Then upload the program to the board.

Step 12
Now your Asteroid attack controller is ready, time to build the rest of Asteroid attack.

/*
* AsteroidAttack
*
* You will create a retro style space
* shooter game, and learn about how to use classes and functions. In the end you will be able to
* tweak values and rewrite the program to redesign the game so it suits your taste.
*
* (c) 2013-2016 Arduino LLC.
*/
import processing.serial.*;
// Serial communication
Serial myPort; // Serial port variable
int newLine = 13; // New line character in ASCII
float angleVal; // Stores the incoming angle value
int buttonState; // Stores the incoming button state
String message; // String that stores all incoming data
String [] valArray = new String [2]; // Array to store all incoming values
//player variables
float playerWidth=100; // Width of the player
float playerHeight=20; // Height of the player
float playerX; // Player x position
float playerY; // Player y position
float posVar=0; // Variable for angle based position
void settings() {
size(800, 600);
}
void setup()
{
smooth();
fill(180);
noStroke();
// List all the available serial ports
println(Serial.list());
// Open the port you are using at the rate you want:
myPort = new Serial(this, Serial.list()[2], 9600);
//We write an 's' to receive data from the board
myPort.write("s");
}
void draw() {
//We read the incoming serial message
serialEvent();
//We update the visuals
updateVisuals();
//We want the following changes to apply to the same matrix only
pushMatrix();
//Update player position
playerPos();
//Update player rotation
playerRotation();
//Draw the player
drawPlayer();
popMatrix();
}
void serialEvent() {
message = myPort.readStringUntil(newLine); // Read from port until new line (ASCII code 13)
if (message != null) {
valArray = split(message, ","); // Slit message by commas and store in String array
angleVal = float(valArray[0]); // Convert to float angeVal
buttonState = int(valArray[1]); // Convert to int buttonState
myPort.write("s"); // Write an "s" to receive more data from the board
}
}
void playerPos() {
//Add position every frame
posVar+=angleVal;
//Position the player avatar
playerX=width/2+posVar; // Set center as the players starting x position
playerY=height-100; // Set the y position of the player
// Limit the X coordinates
if (playerX <= 0) {
playerX = 0;
}
if (playerX > width) {
playerX = width;
}
translate(playerX, playerY);
}
void playerRotation() {
//We add the current rotation
rotate(radians(angleVal));
}
void drawPlayer() {
rectMode(CENTER);
fill(200);
//Draw the player
rect(0, 0, playerWidth, playerHeight);
}
void updateVisuals() {
background(40);
}
Step 13
In the IDE, click Sketch > Show Sketch folder. Open the folders Processing > Asteroidattack.

Step 14
Open the first part of the Asteroidattack.pde and follow the programing instructions to build the rest of Asteroid attack.

How it works
- The serial variables are created to receive and store incoming data.
- A couple of float variables are used to store the position and size of the player avatar.
- In setup() we define the window size and the style of the graphics. Then we print the available serial ports so we can adjust the serial port number based on the printed information.
- When the correct port is set, we use myPort.write(“s”) to send an “s” to our board
- In draw(), we call all our methods in the specific order we want them to be executed. We use pushMatrix() and popMatrix() to control what transformations apply to what graphical elements
- In the serialEvent() function, we read the port and save it into our message variable.
- If the message contains information, and is not equal to null, the content will be split into parts using the commas in the incoming String. The split parts are then assorted to be stored in variables according to the same order they come in. Before they are stored, they are converted from String to float and int.
- After the port information has been stored, we send a new “s” to ask the board for the updated values.
- In playerPos(), we update the players position using angeVal that we received from the port reading.
- We constrict the players movement to the screen.
- We use translate(playerX, playerY) to position the player
- In playerPos(), the players rotation(angle) is updated using angeVal that we received from the port reading.
- In drawPlayer(), we use simply create a graphical representation of the player
- We create the function updateVisuals() to redraw the background early in the draw() loop.
Step 15 – Creating an infinite universe
In order to get the ultimate space hover experience, we want the background to move as the player moves. In this step, we will create an infinite amount of stars. Then we can travel as far to the left or the right as we want without ending up in a starless void.
Find the code in File>Examples>EducationShield>Module5-Space>Projects>AsteroidAttack>processing>AsteroidAttack_part3
/*
* AsteroidAttack
*
* You will create a retro style space
* shooter game, and learn about how to use classes and functions. In the end you will be able to
* tweak values and rewrite the program to redesign the game so it suits your taste.
*
* (c) 2013-2016 Arduino LLC.
*/
import processing.serial.*;
// Arraylist where we can put our objects
ArrayList<Star> starArr = new ArrayList<Star>(); // Array of stars
// Serial communication
Serial myPort; // Serial port variable
int newLine = 13; // New line character in ASCII
float angleVal; // Stores the incoming angle value
int buttonState; // Stores the incoming button state
String message; // String that stores all incoming data
String [] valArray = new String [2]; // Array to store all incoming values
// Star variables
float nbrOfStars=40; // Number of stars
float starVal; // Used as star counter
// player variables
float playerWidth=100; // Width of the player
float playerHeight=20; // Height of the player
float playerX; // Player x position
float playerY; // Player y position
float posVar=0; // Variable for angle based position
void setup()
{
size(800, 600);
smooth();
fill(180);
noStroke();
// List all the available serial ports
println(Serial.list());
// Open the port you are using at the rate you want:
myPort = new Serial(this, Serial.list()[2], 9600);
// We write an 's' to receive data from the board
myPort.write("s");
// Create stars
while (starVal<nbrOfStars) {
createNewStar();
starVal++;
}
}
void draw()
{
// We read the incoming serial message
serialEvent();
// We update the visuals
updateVisuals();
// We want the following changes to apply to the same matrix only
pushMatrix();
// Update player position
playerPos();
// Update player rotation
playerRotation();
// Draw the player
drawPlayer();
popMatrix();
}
void serialEvent() {
message = myPort.readStringUntil(newLine); // Read from port until new line (ASCII code 13)
if (message != null) {
valArray = split(message, ","); // Split message by commas and store in String array
angleVal = float(valArray[0]); // Convert to float angeVal
buttonState = int(valArray[1]); // Convert to int buttonState
myPort.write("s"); // Write an "s" to receive more data from the board
}
}
void playerPos() {
// Add position every frame
posVar+=angleVal;
// Position the player avatar
playerX=width/2+posVar; // Set center as the players starting x position
playerY=height-100; // Set the y position of the player
// Limit the X coordinates
if (playerX <= 0) {
playerX = 0;
}
if (playerX > width) {
playerX = width;
}
translate(playerX, playerY);
}
void playerRotation() {
// We add the current rotation
rotate(radians(angleVal));
}
void drawPlayer() {
rectMode(CENTER);
fill(200);
// Draw the player
rect(0, 0, playerWidth, playerHeight);
}
void updateVisuals() {
background(40);
createStars();
}
void createNewStar() {
// We add a new star to the star array
starArr.add(new Star(random(0, width), random(0, height), random(1, 4)));
}
void createStars() {
translate(0, 0);
// We create a for loop that loops through all stars
for (int i=0; i<starArr.size(); i++) {
// We create a local instance of the star object
Star star = starArr.get(i);
star.move(angleVal);
star.display();
}
}
class Star {
float xpos; // Star x position
float ypos; // Star y position
float starSize; // Star size
// The Constructor is defined with arguments
Star(float xPos, float yPos, float size) {
// Assign the incoming values to our class variables
xpos = xPos;
ypos = yPos;
starSize = size;
}
void display() {
pushMatrix();
noStroke();
fill(255); // Make the stars white
ellipseMode(CENTER); // Makes sure the ellipse is center position on pivot
/*Make sure the universe is infinite by checking whenever a
star is outside of the window*/
if (xpos>width) { // If the x position is larger than screen width
xpos=0;
} else if (xpos<0) { // If the x position is less than 0
xpos=width;
}
ellipse(xpos, ypos, starSize, starSize); // Draw the star
popMatrix();
}
void move(float inVal) {
//Divide the inVal to create a parallax effect
xpos -= inVal/10;
}
}
Commands
- ArrayList<Type>(): A flexible array that can be resized dynamically. It has methods like size(), add(), remove()and get().
- random(min, max) returns a random value between the minimum and maximum values we define
How it works
- We create a new class that we name “Star”.
- We declare float variables for our class, to store position and size.
- We make a constructor that needs some variables defined when we create a new object.
- We create a function called display(), to draw the stars. An if(xpos>width) and else if(xpos<0) are used to keep track of when a stars x axis position is out of the window frame. Whenever the player is moving and a star disappears, it will appear on the opposite side of the screen. The star is drawn as an ellipse(xpos, ypos, starSize, starSize).
- move() is used to add to the xpos variable and animate the star according to player movement.
- In our Main program , we use ArrayList<Star> starArr to declare our array of stars.
- We declare a variable to decide the number of stars, and another used to count through the stars.
- In setup(), we use a while() loop to draw all the stars. For every loop we add one new star using createNewStar()and increase the counter by 1 each loop until we have reached the limit set by our nbrOfStars variable.
- In the updateVisuals() method we created before, we add createStars(), which refers to a method further down in our code.
- In the method createNewStar() we add a new star to our array with starArr.add(new Star( width , height , size )). Instead of assigning a specific position, we do it using random(min, max), so that the stars are distributed in an asymmetrical pattern
- In the createStars(), we loop through the array to reach each individual with the get(i) method. We then create a temporary star object to call the methods move() and display(). This way, each individual star is given a unique behaviour.
Step 16 – Bringing some action
Hovering around in the universe is nice, but passing star after star can become boring after a while. Let’s add some action and create some retro style shots!
Find the code in File>Examples>EducationShield>Module5-Space>Projects>AsteroidAttack>processing>AsteroidAttack_part4
/*
* AsteroidAttack
*
* You will create a retro style space
* shooter game, and learn about how to use classes and functions. In the end you will be able to
* tweak values and rewrite the program to redesign the game so it suits your taste.
*
* (c) 2013-2016 Arduino LLC.
*/
import processing.serial.*;
// Arraylists where we can put our objects
ArrayList<Star> starArr = new ArrayList<Star>(); // Array of stars
ArrayList<Shot> shotArr = new ArrayList<Shot>(); // Array of shots
// Serial communication
Serial myPort; // Serial port variable
int newLine = 13; // New line character in ASCII
float angleVal; // Stores the incoming angle value
int buttonState; // Stores the incoming button state
String message; // String that stores all incoming data
String [] valArray = new String [2]; // Array to store all incoming values
// Star variables
float nbrOfStars=40; // Number of stars
float starVal; // Used as star counter
// Timer variables
float timeSinceStart;
float shotTimer; // Shot timer
float lastShotCheck; // Recording last check
float shotInterval=100; // The interval between shots, in milliseconds
//player variables
float playerWidth=100; // Width of the player
float playerHeight=20; // Height of the player
float playerX; // Player x position
float playerY; // Player y position
float posVar=0; // Variable for angle based position
void setup()
{
size(800, 600);
smooth();
fill(180);
noStroke();
// List all the available serial ports
println(Serial.list());
// Open the port you are using at the rate you want:
myPort = new Serial(this, Serial.list()[2], 9600);
// We write an 's' to receive data from Arduino
myPort.write("s");
// Create stars
while (starVal<nbrOfStars) {
createNewStar();
starVal++;
}
}
void draw()
{
// We read the incoming serial message
serialEvent();
// We update the visuals
updateVisuals();
// We want the following changes to apply to the same matrix only
pushMatrix();
// Update player position
playerPos();
// Update player rotation
playerRotation();
// Draw the player
drawPlayer();
popMatrix();
// Game logic
gameController();
}
void serialEvent() {
message = myPort.readStringUntil(newLine); // Read from port until new line (ASCII code 13)
if (message != null) {
valArray = split(message, ","); // Split message by commas and store in String array
angleVal = float(valArray[0]); // Convert to float angeVal
buttonState = int(valArray[1]); // Convert to int buttonState
myPort.write("s"); // Write an "s" to receive more data from the board
}
}
void playerPos() {
//Add position every frame
posVar+=angleVal;
//Position the player avatar
playerX=width/2+posVar; // Set center as the players starting x position
playerY=height-100; // Set the y position of the player
// Limit the X coordinates
if (playerX <= 0) {
playerX = 0;
}
if (playerX > width) {
playerX = width;
}
translate(playerX, playerY);
}
void playerRotation() {
//We add the current
rotate(radians(angleVal));
}
void drawPlayer() {
rectMode(CENTER);
fill(200);
//Draw the player
rect(0, 0, playerWidth, playerHeight);
}
void updateVisuals() {
background(40);
createStars();
createShots();
}
void createNewStar() {
//We add a new star to the star array
starArr.add(new Star(random(0, width), random(0, height), random(1, 4)));
}
void createStars() {
translate(0, 0);
//We create a for loop that loops through all stars
for (int i=0; i<starArr.size(); i++) {
//We create a local instance of the star object
Star star = starArr.get(i);
star.move(angleVal);
star.display();
}
}
void newShot() {
//We add a new star to the shot array
shotArr.add(new Shot(playerX, playerY));
}
void createShots() {
translate(0, 0);
// A for loop that loops through all shots
for (int j=0; j<shotArr.size(); j++) {
// We create a local instance of the shot object
Shot shot = shotArr.get(j);
shot.show();
}
}
void gameController() {
// If the button is pressed and the shotTimer variable has reached the interval
if (buttonState==1&&shotTimer>shotInterval) {
newShot(); // We add a new shot
lastShotCheck=timeSinceStart; // We save the current time since start
} else {
shotTimer=0; // The timer is reset
}
timeSinceStart=millis(); // Assign current time
shotTimer=timeSinceStart-lastShotCheck; // Assign time since last shot
}
Commands
- ArrayList<Type>(): A flexible array that can be resized dynamically. It has methods like size(), add(), remove()and get().
- random(min, max) returns a random value between the minimum and maximum values we define.
How it works
- We create a new class that we name “Star”.
- We declare float variables for our class, to store position and size.
- We make a constructor that needs some variables defined when we create a new object.
- We create a function called display(), to draw the stars. An if(xpos>width) and else if(xpos<0) are used to keep track of when a stars x axis position is out of the window frame. Whenever the player is moving and a star disappears, it will appear on the opposite side of the screen. The star is drawn as an ellipse(xpos, ypos, starSize, starSize).
- move() is used to add to the xpos variable and animate the star according to player movement.
- In our Main program , we use ArrayList<Star> starArr to declare our array of stars.
- We declare a variable to decide the number of stars, and another used to count through the stars.
- In setup(), we use a while() loop to draw all the stars. For every loop we add one new star using createNewStar()and increase the counter by 1 each loop until we have reached the limit set by our nbrOfStars variable.
- In the updateVisuals() method we created before, we add createStars(), which refers to a method further down in our code.
- In the method createNewStar() we add a new star to our array with starArr.add(new Star( width , height , size )). Instead of assigning a specific position, we do it using random(min, max), so that the stars are distributed in an asymmetrical pattern
- In the createStars(), we loop through the array to reach each individual with the get(i) method. We then create a temporary star object to call the methods move() and display(). This way, each individual star is given a unique behaviour.
Step 17 – Adding some tension
Throwing random shots into endless space can be fun, but let’s provide a threat to add some meaning to the shots and tension to the gameplay!
Find the code in File>Examples>EducationShield>Module5-Space>Projects>AsteroidAttack>processing>AsteroidAttack_part5
/*
* AsteroidAttack
*
* You will create a retro style space
* shooter game, and learn about how to use classes and functions. In the end you will be able to
* tweak values and rewrite the program to redesign the game so it suits your taste.
*
* (c) 2013-2016 Arduino LLC.
*/
import processing.serial.*;
//Arraylists where we can put our objects
ArrayList<Star> starArr = new ArrayList<Star>(); // Array of stars
ArrayList<Shot> shotArr = new ArrayList<Shot>(); // Array of shots
ArrayList<Asteroid> asteroidArr = new ArrayList<Asteroid>(); // Array of asteroids
// Serial communication
Serial myPort; // Serial port variable
int newLine = 13; // New line character in ASCII
float angleVal; // Stores the incoming angle value
int buttonState; // Stores the incoming button state
String message; // String that stores all incoming data
String [] valArray = new String [2]; // Array to store all incoming values
// Star variables
float nbrOfStars=40; // Number of stars
float starVal; // Used as star counter
//Shot variables
float shotX; // Shot x position
float shotY; // Shot y position
float shotSize; // Shot size
//Asteroid variables
float asteroidX; // Asteroid position x
float asteroidY; // Asteroid position y
float asteroidSize; // Asteroid size
float asteroidSpeed=2; // Asteroid speed
float asteroidMinSize=30; // Min size for random method
float asteroidMaxSize=80; // Max size for random method
// Timer variables
float timeSinceStart; // Time since start
float shotTimer; // Shot timer
float lastShotCheck; // Recording last check
float shotInterval=100; // The interval between shots, in milliseconds
float asteroidTimer; // Asteroid timer
float lastAsteroidCheck; // Recording last check
float asteroidInterval=1000; // The interval between asteroids, in milliseconds
//player variables
float playerWidth=100; // Width of the player
float playerHeight=20; // Height of the player
float playerX; // Player x position
float playerY; // Player y position
float posVar=0;
void setup()
{
size(800, 600);
smooth();
fill(180);
noStroke();
// List all the available serial ports
println(Serial.list());
// Open the port you are using at the rate you want:
myPort = new Serial(this, Serial.list()[2], 9600);
// We write an 's' to receive data from Arduino
myPort.write("s");
// Create stars
while (starVal<nbrOfStars) {
createNewStar();
starVal++;
}
}
void draw()
{
// We read the incoming serial message
serialEvent();
// We update the visuals
updateVisuals();
// We want the following changes to apply to the same matrix only
pushMatrix();
// Update player position
playerPos();
// Update player rotation
playerRotation();
// Draw the player
drawPlayer();
popMatrix();
// We make sure to check if any shot hit any asteroid every frame
checkHits();
gameController();
}
void serialEvent() {
message = myPort.readStringUntil(newLine); // Read from port until new line (ASCII code 13)
if (message != null) {
valArray = split(message, ","); // Split message by commas and store in String array
angleVal = float(valArray[0]); // Convert to float angeVal
buttonState = int(valArray[1]); // Convert to int buttonState
myPort.write("s"); // Write an "s" to receive more data from the board
}
}
void playerPos() {
// Add position every frame
posVar+=angleVal;
// Center player position in window
playerX=width/2+posVar;
playerY=height-100;
// Limit the X coordinates
if (playerX <= 0) {
playerX = 0;
}
if (playerX > width) {
playerX = width;
}
translate(playerX, playerY);
}
void playerRotation() {
//We add the current
rotate(radians(angleVal));
}
void drawPlayer() {
rectMode(CENTER);
fill(200);
// Draw the player
rect(0, 0, playerWidth, playerHeight);
}
void updateVisuals() {
background(40);
createStars();
createShots();
createAsteroids();
}
void createNewStar() {
// We add a new star to the star array
starArr.add(new Star(random(0, width), random(0, height), random(1, 4)));
}
void createStars() {
translate(0, 0);
// A for loop that loops through all stars
for (int i=0; i<starArr.size(); i++) {
//We create a local instance of the star object
Star star = starArr.get(i);
star.move(angleVal);
star.display();
}
}
void newShot() {
// We add a new shot to the shot array
shotArr.add(new Shot(playerX, playerY));
}
void createShots() {
translate(0, 0);
// A for loop that loops through all shots
for (int j=0; j<shotArr.size(); j++) {
Shot shot = shotArr.get(j);
shot.show();
}
}
void newAsteroid() {
// We assign the Asteroid a random x position based on zero and full window width
float asteroidXPos= random(0, width);
// We assign the Asteroid a random size based on our min and max values
float asteroidSize= random(asteroidMinSize, asteroidMaxSize);
// We add a new asteroid to the asteroid array
asteroidArr.add(new Asteroid(asteroidXPos, asteroidSpeed, asteroidSize));
}
void createAsteroids() {
translate(0, 0);
// A for loop that loops through all asteroids
for (int k=0; k<asteroidArr.size(); k++) {
Asteroid asteroid = asteroidArr.get(k);
asteroid.visualize();
}
}
void checkHits() {
// We loop through all shots we have created
for (int l=0; l<shotArr.size(); l++) {
// We want to compare each shot with all existing asteroids
for (int m=0; m<asteroidArr.size(); m++) {
// We declare a variable to access one shot object of the Class Shot at a time
Shot shot = shotArr.get(l);
// We call the functions in the Shot class to return variables for position and size
shotX=shot.getXPos();
shotY=shot.getYPos();
shotSize=shot.getSize();
// We declare a variable to access one asteroid object of the Class Asteroid at a time
Asteroid asteroid = asteroidArr.get(m);
// We call the functions in the Asteroid class to return variables for position and size
asteroidX=asteroid.getXPos();
asteroidY=asteroid.getYPos();
asteroidSize=asteroid.getSize();
// We check the boundaries using nestled if statements, just like in "Catch the Apple"
if (asteroidY+asteroidSize>shotY&&asteroidY<shotY+shotSize) {
if (asteroidX+asteroidSize>shotX&&asteroidX<shotX+shotSize) {
// Once we know an asteroid has been hit, we set the function asteroidHit() to "true"
asteroid.asteroidHit(true);
// We remove the asteroid from the array
asteroidArr.remove(m);
} else {
asteroid.asteroidHit(false);
}
}
}
}
}
void gameController() {
// If the button is pressed and the shotTimer variable has reached the interval
if (buttonState==1&&shotTimer>shotInterval) {
newShot(); // We add a new shot
lastShotCheck=timeSinceStart; // We save the current time since start
} else {
shotTimer=0; // The timer is reset
}
// If the asteroidTimer variable has reached the interval
if (asteroidTimer>asteroidInterval) {
newAsteroid(); // We add a new asteroid
lastAsteroidCheck=timeSinceStart; // We save the current time since start
} else {
asteroidTimer=0; // The timer is reset
}
timeSinceStart=millis(); // Assign current time
shotTimer=timeSinceStart-lastShotCheck; // Assign time since last shot
asteroidTimer=timeSinceStart-lastAsteroidCheck; // Assign time since last asteroid
}
Commands
- Nested for loops are basically loops within loops. A nested for loop has an inner loop within an outer one. As the outer loop is on it’s first step, it triggers the inner loop that goes through all content in that loop. As the inner loop has finished, the outer loop is triggered again and takes it’s second step. This repeats until the outer loop is finished.
How it works
- We create a new class that we name “Asteroid”.
- We declare float and boolean variables for our class, to store position, size, speed, and keep track of hits.
- We make a constructor that needs some variables defined when we create a new object.
- In visualize(), we animate and draw the asteroids. We use translate(x,y) to animate the shots position each frame, and draw the asteroid using ellipse(0, 0, size, size). We use the boolean isShot in our if statement whether the asteroid has been shot or not.
- The ypos variable is updated every frame to move the asteroid.
- asteroidHit(hit), getXPos(), getYPos() and getSize() are methods we use to make specific class functions and variables in our class easily accessible from the main program .
- In the main program, we start by adding our ArrayList<Asteroid> asteroidArr to declare our array of asteroids.
- We declare position, size and speed variables for our asteroids
- Time variables are declared to keep track of the interval at which new asteroids are created.
- In updateVisuals(), we add createAsteroids(), which refers to a method further down in our code.
- In the method newAsteroid() we add a new asteroid to our array with asteroidArr.add(new Asteroid(x, y, size)). We use random(0, width) to distribute the asteroids in an asymmetrical pattern. To assign a size, we randomize between our asteroidMinSize and asteroidMaxSize variables.
- In createAsteroids() we loop through the array to reach each individual asteroid with the get(k) method. We then create a temporary asteroid object to call the method visualize().
- In checkHits(), we perform a basic collision detection to check if any asteroid has been hit by a shot. We do this by comparing each individual shot with each individual asteroid, every frame. We loop through all shots with our shotArr, and within that loop we loop through all asteroids using our asteroidArr. Using the position and size to determine the complete area of the shot, we can compare it with the complete area of the asteroid. We check one shot against all asteroids, step by step. We declare a variable to access one shot object of the class Shot at a time, and then we store the position and size of the shot. The same is done with the asteroid variable to access the position and size to store for comparison. Once an asteroid has been hit, we call asteroid.asteroidHit(true) and remove it from the arraylist. If the else statement is true, it executes asteroid.asteroidHit(false).
- In gameController(), we use the asteroidInterval variable to wait some time inbetween launching new asteroids. We check if the counter has reached the interval threshold. When it reaches the threshold, we create a new asteroid, and then update the lastAsteroidCheck value so that we can use it again. Otherwise we reset the asteroidTimervariable.
- After the if statement, we assign the current time to our timeSinceStart variable and assign the time since last shot to our asteroidTimer.
Step 18 – Creating gameplay
In order to make this fun, we need to add some gameplay to our interactive application. Let’s add points, life, and a “game over” screen.
Find the code in File>Examples>EducationShield>Module5-Space>Projects>AsteroidAttack>processing>AsteroidAttack_part6
/*
* AsteroidAttack
*
* You will create a retro style space
* shooter game, and learn about how to use classes and functions. In the end you will be able to
* tweak values and rewrite the program to redesign the game so it suits your taste.
*
* (c) 2013-2016 Arduino LLC.
*/
import processing.serial.*;
//Arraylists where we can put our objects
ArrayList<Star> starArr = new ArrayList<Star>(); // Array of stars
ArrayList<Shot> shotArr = new ArrayList<Shot>(); // Array of shots
ArrayList<Asteroid> asteroidArr = new ArrayList<Asteroid>(); // Array of asteroids
// Serial communication
Serial myPort; // Serial port variable
int newLine = 13; // New line character in ASCII
float angleVal; // Stores the incoming angle value
int buttonState; // Stores the incoming button state
String message; // String that stores all incoming data
String [] valArray = new String [2]; // Array to store all incoming values
// Star variables
float nbrOfStars=40; // Number of stars
float starVal; // Used as star counter
//Shot variables
float shotX; // Shot x position
float shotY; // Shot y position
float shotSize; // Shot size
//Asteroid variables
float asteroidX; // Asteroid position x
float asteroidY; // Asteroid position y
float asteroidSize; // Asteroid size
float asteroidSpeed=2; // Asteroid speed
float asteroidMinSize=30; // Min size for random method
float asteroidMaxSize=80; // Max size for random method
//Game state variables
int points=0; // Point variable
PFont pointText; // Text for displaying points
boolean gameOver=false; // Boolean keeps track of game state
// Timer variables
float timeSinceStart; // Time since start
float shotTimer; // Shot timer
float lastShotCheck; // Recording last check
float shotInterval=100; // The interval between shots, in milliseconds
float asteroidTimer; // Asteroid timer
float lastAsteroidCheck; // Recording last check
float asteroidInterval=1000; // The interval between asteroids, in milliseconds
//player variables
float playerWidth=100; // Width of the player
float playerHeight=20; // Height of the player
float playerX; // Player x position
float playerY; // Player y position
float posVar=0;
boolean playerHit=false; // Use to check if player is hit
float life=200; // The players life points
void setup()
{
size(800, 600);
smooth();
fill(180);
noStroke();
// List all the available serial ports
println(Serial.list());
// Open the port you are using at the rate you want:
myPort = new Serial(this, Serial.list()[2], 9600);
// We write an 's' to receive data from Arduino
myPort.write("s");
// Create stars
while (starVal<nbrOfStars) {
createNewStar();
starVal++;
}
// Create a font
pointText = createFont("CourierNewPSMT-48.vlw", 48); // Define font
textFont(pointText); // Set font
}
void draw()
{
// If statement to check whether the player still has life
if (life>0) {
// We read the incoming serial message
serialEvent();
// We update the visuals
updateVisuals();
// We want the following changes to apply to the same matrix only
pushMatrix();
// Update player position
playerPos();
// Update player rotation
playerRotation();
// Draw the player
drawPlayer();
popMatrix();
// We make sure to check if any shot hit any asteroid every frame
checkHits();
// Check whether player collided with an asteroid
checkDamage();
// Show points on screen
drawPoints();
gameController();
} else {
// Game over method is run when life is out
gameOver();
}
}
void serialEvent() {
message = myPort.readStringUntil(newLine); // Read from port until new line (ASCII code 13)
if (message != null) {
valArray = split(message, ","); // Split message by commas and store in String array
angleVal = float(valArray[0]); // Convert to float angeVal
buttonState = int(valArray[1]); // Convert to int buttonState
myPort.write("s"); // Write an "s" to receive more data from the board
}
}
void playerPos() {
// Add position every frame
posVar+=angleVal;
// Center player position in window
playerX=width/2+posVar;
playerY=height-100;
// Limit the X coordinates
if (playerX <= 0) {
playerX = 0;
}
if (playerX > width) {
playerX = width;
}
translate(playerX, playerY);
}
void playerRotation() {
// We add the current
rotate(radians(angleVal));
}
void drawPlayer() {
rectMode(CENTER);
fill(200);
// Draw the player
rect(0, 0, playerWidth, playerHeight);
}
void updateVisuals() {
if (playerHit==true) {
background(random(100, 250), 100, 100); // If the player is hit we make the background flash in red
} else {
background(40);
}
createStars();
createShots();
createAsteroids();
createLifeBar();
}
void createNewStar() {
// We add a new star to the star array
starArr.add(new Star(random(0, width), random(0, height), random(1, 4)));
}
void createStars() {
translate(0, 0);
// We create a for loop that loops through all stars
for (int i=0; i<starArr.size(); i++) {
//We create a local instance of the star object
Star star = starArr.get(i);
star.move(angleVal);
star.display();
}
}
void newShot() {
// We add a new shot to the shot array
shotArr.add(new Shot(playerX, playerY));
}
void createShots() {
translate(0, 0);
// A for loop that loops through all shots
for (int j=0; j<shotArr.size(); j++) {
Shot shot = shotArr.get(j);
shot.show();
}
}
void newAsteroid() {
//We assign the Asteroid a random x position based on zero and full window width
float asteroidXPos= random(0, width);
//We assign the Asteroid a random size based on our min and max values
float asteroidSize= random(asteroidMinSize, asteroidMaxSize);
// We add a new asteroid to the asteroid array
asteroidArr.add(new Asteroid(asteroidXPos, asteroidSpeed, asteroidSize));
}
void createAsteroids() {
translate(0, 0);
// A for loop that loops through all asteroids
for (int k=0; k<asteroidArr.size(); k++) {
Asteroid asteroid = asteroidArr.get(k);
asteroid.visualize();
}
}
void checkHits() {
// We loop through all shots we have created
for (int l=0; l<shotArr.size(); l++) {
// We want to compare each shot with all existing asteroids
for (int m=0; m<asteroidArr.size(); m++) {
// We declare a variable to access one shot object of the Class Shot at a time
Shot shot = shotArr.get(l);
// We call the functions in the Shot class to return variables for position and size
shotX=shot.getXPos();
shotY=shot.getYPos();
shotSize=shot.getSize();
// We declare a variable to access one asteroid object of the Class Asteroid at a time
Asteroid asteroid = asteroidArr.get(m);
// We call the functions in the Asteroid class to return variables for position and size
asteroidX=asteroid.getXPos();
asteroidY=asteroid.getYPos();
asteroidSize=asteroid.getSize();
// We check the boundaries using nestled if statements, just like in "Catch the Apple"
if (asteroidY+asteroidSize>shotY&&asteroidY<shotY+shotSize) {
if (asteroidX+asteroidSize>shotX&&asteroidX<shotX+shotSize) {
// Once we know an asteroid has been hit, we set the function asteroidHit() to "true"
asteroid.asteroidHit(true);
// We remove the asteroid from the array
asteroidArr.remove(m);
points++; //We add points each time an asteroid is destroyed
} else {
asteroid.asteroidHit(false);
}
}
}
}
}
void gameController() {
// If the button is pressed and the shotTimer variable has reached the interval
if (buttonState==1&&shotTimer>shotInterval) {
newShot(); // We add a new shot
lastShotCheck=timeSinceStart; // We save the current time since start
} else {
shotTimer=0; // The timer is reset
}
// If the asteroidTimer variable has reached the interval
if (asteroidTimer>asteroidInterval) {
newAsteroid(); // We add a new asteroid
lastAsteroidCheck=timeSinceStart; // We save the current time since start
} else {
asteroidTimer=0; // The timer is reset
}
timeSinceStart=millis(); // Assign current time
shotTimer=timeSinceStart-lastShotCheck; // Assign time since last shot
asteroidTimer=timeSinceStart-lastAsteroidCheck; // Assign time since last asteroid
}
void checkDamage() {
//We loop through all asteroids
for (int n=0; n<asteroidArr.size(); n++) {
//We declare a variable to access one asteroid object of the Class Asteroid at a time
Asteroid asteroid = asteroidArr.get(n);
//We call the functions in the Asteroid class to return variables for position and size
asteroidX=asteroid.getXPos();
asteroidY=asteroid.getYPos();
asteroidSize=asteroid.getSize();
// We check all asteroids against our player for collision
if (asteroidY+asteroidSize>playerY&&asteroidY<playerY+playerHeight) {
if (asteroidX+asteroidSize>playerX-playerWidth/2&&asteroidX<playerX+playerWidth/2) {
//Decrease life on hit
life-=1;
playerHit=true;
} else {
playerHit=false;
}
}
}
}
void createLifeBar() {
rectMode(CORNER);
//Green lifebar
fill(24, 148, 154);
rect(20, 40, life, 40);
}
void drawPoints() {
fill(200);
textAlign(CENTER, TOP); // Align text
text(points, width/2, 30); // Assign text content
}
void gameOver() {
background(100);
fill(40);
textAlign(CENTER, BOTTOM); // Align text
text("GAME OVER", width/2, 200); // Assign text content
textAlign(CENTER, TOP); // Align text
text("your score was:", width/2, 320); // Assign text content
text(points, width/2, 400); // Assign text content
}
How it works
- We create a couple of variables: a float for points, a String for pointText and a boolean to determine the game state with our gameOver variable
- The float variable life and the boolean called playerHit are used to keep track of the players points and check if it is hit.
- The font is created using createFont(), containing the approperiate font name and size. The font is then set using textFont(pointText).
- In draw(), we use an if statement to check whether the player still has life. If the player has life, we execute the game methods to run the game.
- In draw(), we call checkDamage() and drawPoints() to handle the players life points and draw the collected points from asteroid hits.
- It the players life points are less than 0, we call a method named gameOver().
- In checkDamage() we perform a collision detection to check if the player has been hit by an asteroid. We declare a variable to access one asteroid object of the class Asteroid at a time, and then we check it against the position and size of the player. If the player has been hit, we set playerHit to true, and decrease the players life point. If the else statement is true, it sets playerHit to false.
- In createLifeBar() we use a rect(x,y,size,size) to display the health points.
- The drawPoints() method is used to show the points. textAlign(CENTER, TOP) is used to position the text, and in text(text, x, y) the points are specified to be displayed relative to the previous positioning.
- gameOver() is used to show the game over screen. textAlign(CENTER, TOP) and textAlign(text, x, y) is used to control the text content and layout.
Step 19 – Making it look great!
The game could be finished now, and it is quite elegant in a minimalistic kind of way. But to be honest, the spaceship doesn’t really look like a spaceship, and the asteroids..well. Let’s add some flavour! Just like in the “Catch the apple project”, we will add some images to the sketch!
Find the code in File>Examples>EducationShield>Module5-Space>Projects>AsteroidAttack>processing>AsteroidAttack_part7
/*
* AsteroidAttack
*
* You will create a retro style space
* shooter game, and learn about how to use classes and functions. In the end you will be able to
* tweak values and rewrite the program to redesign the game so it suits your taste.
*
* (c) 2013-2016 Arduino LLC.
*/
import processing.serial.*;
//Arraylists where we can put our objects
ArrayList<Star> starArr = new ArrayList<Star>(); // Array of stars
ArrayList<Shot> shotArr = new ArrayList<Shot>(); // Array of shots
ArrayList<Asteroid> asteroidArr = new ArrayList<Asteroid>(); // Array of asteroids
// Serial communication
Serial myPort; // Serial port variable
int newLine = 13; // New line character in ASCII
float angleVal; // Stores the incoming angle value
int buttonState; // Stores the incoming button state
String message; // String that stores all incoming data
String [] valArray = new String [2]; // Array to store all incoming values
// Star variables
float nbrOfStars=40; // Number of stars
float starVal; // Used as star counter
//Shot variables
float shotX; // Shot x position
float shotY; // Shot y position
float shotSize; // Shot size
//Asteroid variables
float asteroidX; // Asteroid position x
float asteroidY; // Asteroid position y
float asteroidSize; // Asteroid size
float asteroidSpeed=2; // Asteroid speed
float asteroidMinSize=30; // Min size for random method
float asteroidMaxSize=80; // Max size for random method
//Game state variables
int points=0; // Point variable
PFont pointText; // Text for displaying points
boolean gameOver=false; // Boolean keeps track of game state
// Timer variables
float timeSinceStart; // Time since start
float shotTimer; // Shot timer
float lastShotCheck; // Recording last check
float shotInterval=100; // The interval between shots, in milliseconds
float asteroidTimer; // Asteroid timer
float lastAsteroidCheck; // Recording last check
float asteroidInterval=1000; // The interval between asteroids, in milliseconds
//player variables
float playerWidth=200; // Width of the player
float playerHeight=100; // Height of the player
float playerX; // Player x position
float playerY; // Player y position
float posVar=0;
boolean playerHit=false; // Use to check if player is hit
float life=200; // The players life points
//Image array
String[] imgFiles={"spaceShip.png", "meteor_small.png", "meteor_big.png"};
PImage[] img = new PImage[3];
void setup()
{
size(800, 600);
smooth();
fill(180);
noStroke();
// List all the available serial ports
println(Serial.list());
// Open the port you are using at the rate you want:
myPort = new Serial(this, Serial.list()[2], 9600);
// We write an 's' to receive data from Arduino
myPort.write("s");
// Create stars
while (starVal<nbrOfStars) {
createNewStar();
starVal++;
}
// Create a font
pointText = createFont("CourierNewPSMT-48.vlw", 48);
textFont(pointText);
// Load the images
for(int i = 0; i < 3; i = i + 1) {
img[i] = loadImage(imgFiles[i]);
}
}
void draw()
{
// If statement to check whether the player still has life
if (life>0) {
//We read the incoming serial message
serialEvent();
//We update the visuals
updateVisuals();
//We want the following changes to apply to the same matrix only
pushMatrix();
//Update player position
playerPos();
//Update player rotation
playerRotation();
//Draw the player
drawPlayer();
popMatrix();
//We make sure to check if any shot hit any asteroid every frame
checkHits();
//Check whether player collided with an asteroid
checkDamage();
// Show points on screen
drawPoints();
gameController();
} else {
// Game over method is run when life is out
gameOver();
}
}
void serialEvent() {
message = myPort.readStringUntil(newLine); // Read from port until new line (ASCII code 13)
if (message != null) {
valArray = split(message, ","); // Split message by commas and store in String array
angleVal = float(valArray[0]); // Convert to float angeVal
buttonState = int(valArray[1]); // Convert to int buttonState
myPort.write("s"); // Write an "s" to receive more data from the board
}
}
void playerPos() {
// Add position every frame
posVar+=angleVal;
// Center player position in window
playerX=width/2+posVar;
playerY=height-100;
// Limit the X coordinates
if (playerX <= 0) {
playerX = 0;
}
if (playerX > width) {
playerX = width;
}
translate(playerX, playerY);
}
void playerRotation() {
//We add the current
rotate(radians(angleVal));
}
void drawPlayer() {
rectMode(CENTER);
fill(200);
//Represent the player with an image
image(img[0], 0-playerWidth/2, 0-playerHeight/2, playerWidth, playerHeight);
}
void updateVisuals() {
if (playerHit==true) {
background(random(100, 250), 100, 100); // If the player is hit we make the background flash in red
} else {
background(40);
}
createStars();
createShots();
createAsteroids();
createLifeBar();
}
void createNewStar() {
//We add a new star to the star array
starArr.add(new Star(random(0, width), random(0, height), random(1, 4)));
}
void createStars() {
translate(0, 0);
// We create a for loop that loops through all stars
for (int i=0; i<starArr.size(); i++) {
//We create a local instance of the star object
Star star = starArr.get(i);
star.move(angleVal);
star.display();
}
}
void newShot() {
// We add a new shot to the shot array
shotArr.add(new Shot(playerX, playerY));
}
void createShots() {
translate(0, 0);
// A for loop that loops through all shots
for (int j=0; j<shotArr.size(); j++) {
Shot shot = shotArr.get(j);
shot.show();
}
}
void newAsteroid() {
//We assign the Asteroid a random x position based on zero and full window width
float asteroidXPos= random(0, width);
//We assign the Asteroid a random size based on our min and max values
float asteroidSize= random(asteroidMinSize, asteroidMaxSize);
// We add a new asteroid to the asteroid array
asteroidArr.add(new Asteroid(asteroidXPos, asteroidSpeed, asteroidSize));
}
void createAsteroids() {
translate(0, 0);
// A for loop that loops through all asteroids
for (int k=0; k<asteroidArr.size(); k++) {
Asteroid asteroid = asteroidArr.get(k);
asteroid.visualize();
}
}
void checkHits() {
//We loop through all shots we have created
for (int l=0; l<shotArr.size(); l++) {
//We want to compare each shot with all existing asteroids
for (int m=0; m<asteroidArr.size(); m++) {
//We declare a variable to access one shot object of the Class Shot at a time
Shot shot = shotArr.get(l);
//We call the functions in the Shot class to return variables for position and size
shotX=shot.getXPos();
shotY=shot.getYPos();
shotSize=shot.getSize();
//We declare a variable to access one asteroid object of the Class Asteroid at a time
Asteroid asteroid = asteroidArr.get(m);
//We call the functions in the Asteroid class to return variables for position and size
asteroidX=asteroid.getXPos();
asteroidY=asteroid.getYPos();
asteroidSize=asteroid.getSize();
//We check the boundaries using nestled if statements, just like in "Catch the Apple"
if (asteroidY+asteroidSize>shotY&&asteroidY<shotY+shotSize) {
if (asteroidX+asteroidSize>shotX&&asteroidX<shotX+shotSize) {
//Once we know an asteroid has been hit, we set the function asteroidHit() to "true"
asteroid.asteroidHit(true);
//We remove the asteroid from the array
asteroidArr.remove(m);
points++; //We add points each time an asteroid is destroyed
} else {
asteroid.asteroidHit(false);
}
}
}
}
}
void gameController() {
// If the button is pressed and the shotTimer variable has reached the interval
if (buttonState==1&&shotTimer>shotInterval) {
newShot(); // We add a new shot
lastShotCheck=timeSinceStart; // We save the current time since start
} else {
shotTimer=0; // The timer is reset
}
// If the asteroidTimer variable has reached the interval
if (asteroidTimer>asteroidInterval) {
newAsteroid(); // We add a new asteroid
lastAsteroidCheck=timeSinceStart; // We save the current time since start
} else {
asteroidTimer=0; // The timer is reset
}
timeSinceStart=millis(); // Assign current time
shotTimer=timeSinceStart-lastShotCheck; // Assign time since last shot
asteroidTimer=timeSinceStart-lastAsteroidCheck; // Assign time since last asteroid
}
void checkDamage() {
//We loop through all asteroids
for (int n=0; n<asteroidArr.size(); n++) {
//We declare a variable to access one asteroid object of the Class Asteroid at a time
Asteroid asteroid = asteroidArr.get(n);
//We call the functions in the Asteroid class to return variables for position and size
asteroidX=asteroid.getXPos();
asteroidY=asteroid.getYPos();
asteroidSize=asteroid.getSize();
// We check all asteroids against our player for collision
if (asteroidY+asteroidSize>playerY&&asteroidY<playerY+playerHeight) {
if (asteroidX+asteroidSize>playerX-playerWidth/2&&asteroidX<playerX+playerWidth/2) {
//Decrease life on hit
life-=1;
playerHit=true;
} else {
playerHit=false;
}
}
}
}
void createLifeBar() {
rectMode(CORNER);
//Green lifebar
fill(24, 148, 154);
rect(20, 40, life, 40);
}
void drawPoints() {
fill(200);
textAlign(CENTER, TOP); // Align text
text(points, width/2, 30); // Assign text content
}
void gameOver() {
background(100);
fill(40);
textAlign(CENTER, BOTTOM); // Align text
text("GAME OVER", width/2, 200); // Assign text content
textAlign(CENTER, TOP); // Align text
text("your score was:", width/2, 320); // Assign text content
text(points, width/2, 400); // Assign text content
}
How it works
- We create a couple of variables: a float for points, a String for pointText and a boolean to determine the game state with our gameOver variable
- The float variable life and the boolean called playerHit are used to keep track of the players points and check if it is hit.
- The font is created using createFont(), containing the approperiate font name and size. The font is then set using textFont(pointText).
- In draw(), we use an if statement to check whether the player still has life. If the player has life, we execute the game methods to run the game.
- In draw(), we call checkDamage() and drawPoints() to handle the players life points and draw the collected points from asteroid hits.
- It the players life points are less than 0, we call a method named gameOver().
- In checkDamage() we perform a collision detection to check if the player has been hit by an asteroid. We declare a variable to access one asteroid object of the class Asteroid at a time, and then we check it against the position and size of the player. If the player has been hit, we set playerHit to true, and decrease the players life point. If the else statement is true, it sets playerHit to false.
- In createLifeBar() we use a rect(x,y,size,size) to display the health points.
- The drawPoints() method is used to show the points. textAlign(CENTER, TOP) is used to position the text, and in text(text, x, y) the points are specified to be displayed relative to the previous positioning.
- gameOver() is used to show the game over screen. textAlign(CENTER, TOP) and textAlign(text, x, y) is used to control the text content and layout.
Troubleshooting
- Make sure that you have the correct Serial port connected. Check the list that println(Serial.list()) prints to find out which one to use.
- The code is long and complex. To make debugging effective, it is a good idea to use comments and Serial.println() to determine what is wrong and where. Comment out code you are unsure of, and print out values you use to make sure they do what you intend.
Learn by Doing
- How would you make this game harder? What variables would you tweak?
- How would you make the game end within a certain time, instead of when the player’s life runs out?
- The game has the same difficulty from beginning to end right now. How would you make the game easy in the beginning, and increasingly harder over time?
- Re-design and change the theme of the game. You can draw your own images and program the graphics to suit your taste. What if the game is really about watering flowers, and have them survive? Use your imagination and have fun!
“The day Microsoft makes something that doesn’t suck is probably the day they start making vacuum cleaners.”
— Ernst Jan Plugge