11 PDE files 829 lines
// Assignment_04.pde
/*
[ -------------------- [ INFO ] -------------------- ]
Assignment 04: Old School Games
Vincent Nguyen
03/29/2017
[ -------------------- [ BLURB ] -------------------- ]
Hello!
For this assignment, I decided to recreate breakout using a chalkboard theme (ie: drawing on the chalkboard).
I took your advice and looked into learning about objects and I can safely say that it was quite challenging
but I feel like it has paid off. I see the benefits of organizing code into objects with their own values
and functions. Some code here might be inefficient but that's just the result of my brain malfunctioning
while it tried to comprehend object code plus maybe a lack of time. Either way, I've done my best to
organize everything and document it all while half-awake at 2 AM just for your viewing pleasure. Enjoy~!
As always, thanks for reading!
P.S. Most of it is tabbed.
[ -------------------- [ TABLE OF CONTENTS ] -------------------- ]
01. SETTING UP GLOBAL VARIABLES
02. VOID SETUP() AND ASSIGNING VALUES
03. VOID DRAW() AND A MENU SWITCH
04. AUDIO USING STATES
05. BALL OBJECT
06. BRICKS AND LIVES
07. BUTTON OBJECT
08. GAME MENU SCREEN
09. GAME OBJECT
10. GAME OVER SCREEN
11. MAIN MENU SCREEN
12. OPTIONS SCREEN
13. PADDLE OBJECT
[ -------------------- [ CREDITS ] -------------------- ]
[ Images ]
01. Chalkboard [http://www.thebridgelive.org/wp-content/uploads/2014/12/Chalkboard.jpg]
[ Music ]
01. Undertale OST - 010 - Ghost Fight [https://www.youtube.com/watch?v=Zz1bfhtKsHM]
02. Undertale OST - 011 - Determination [https://www.youtube.com/watch?v=W1i4mTyidOc]
03. Undertale OST - 034 - Memory [https://www.youtube.com/watch?v=eijdNQMYikY]
[ Fonts ]
01. Eraser by David Rakowski [http://www.dafont.com/eraser.font]
*/
// ============================== [ 01. SETTING UP GLOBAL VARIABLES ] ============================== //
//Import the minim library for audio
import ddf.minim.*;
//Set up variables
PFont title, body; //Fonts
PImage chalkboard; //Background
int state, numBricks, numRows; //Screen state, number of bricks per row and column
float xcenter, ycenter; //Because I'm lazy
color darkblue, pink, darkpink, lightpink, lightblue, pinkred; //So I don't have to refer back to colour codes
boolean musicOn, rainbowOn; //For optionsMenu
//Set up objects
button start, options, exit, main, music, rainbow; //Buttons
brick[] bricks; //Array of brick objects
paddle player; //Paddle object
ball ball; //Ball object
game breakout; //Game object
//Set up audio players
Minim minim;
AudioPlayer musicMenu, musicGame, musicEnd;
// ============================== [ 02. VOID SETUP() AND ASSIGNING VALUES ] ============================== //
void setup() {
size(800, 600); //Set window size
//Quick simple basics out of the way
rectMode(CENTER);
textAlign(CENTER, CENTER);
smooth(8);
noStroke();
//Assign colours
darkblue = #;
darkpink = #C14875;
pink = #FF6FA4;
pinkred = #E8578C;
lightpink = #FF8BB6;
lightblue = #B9C0FF;
//Floats
xcenter = width/2;
ycenter = height/2;
//Booleans
musicOn = true;
rainbowOn = false;
//Images and Fonts
chalkboard = loadImage("chalkboard.jpg");
title = loadFont("Eraser_Regular-84.vlw");
body = loadFont("Eraser_Regular-32.vlw");
//Audio starts here
minim = new Minim(this);
//Load music
musicMenu = minim.loadFile("mainmenu.mp3");
musicGame = minim.loadFile("breakout.mp3");
musicEnd = minim.loadFile("gameover.mp3");
//Set the gain so I can retain my hearing ability
musicMenu.setGain(-14);
musicGame.setGain(-14);
musicEnd.setGain(-14);
//Audio ends here
//Setting up the array of bricks
numBricks = 13; //How many bricks per row?
numRows = 4; //How many rows?
//Create the amount of bricks accordingly
bricks = new brick[numBricks*numRows];
//Setting up the button objects
start = new button("Start", xcenter, height/16*7, width/3, height/7, pinkred, darkpink, lightpink);
options = new button("Options", xcenter, height/16*10, width/3, height/7, pinkred, darkpink, lightpink);
exit = new button("Exit", xcenter, height/16*13, width/3, height/7, pinkred, darkpink, lightpink);
main = new button("Main Menu", xcenter, height/16*13, width/3, height/7, pinkred, darkpink, lightpink);
music = new button("Music: ON", xcenter, height/16*7, width/3, height/7, pinkred, darkpink, lightpink);
rainbow = new button("Rainbow: OFF", xcenter, height/16*10, width/3, height/7, pinkred, darkpink, lightpink);
//Parameters = (text, x position, y position, width, height, idle colour, rollover colour, clicked colour)
//Set up the paddle and ball objects
player = new paddle();
ball = new ball(xcenter, ycenter);
//Create a new game object called breakout and reset it
breakout = new game();
breakout.reset();
}
// ============================== [ 03. VOID DRAW() AND A MENU SWITCH ] ============================== //
void draw() {
audio(); //Plays the audio
//A switch using the integer state to determine which screen we are looking at
switch(state) {
case 0:
mainMenu();
break;
case 1:
optionsMenu();
break;
case 2:
drawGame();
break;
case 3:
gameOver();
break;
default:
mainMenu();
break;
}
}
// audio.pde
// ============================== [ 04. AUDIO USING STATES ] ============================== //
void audio() { //Audio function
if (musicOn) { //If music is on, play the music!
if (state == 0 || state == 1) { //If it's the main menu or options menu...
//Pause other music players just in case.
musicGame.pause();
musicEnd.pause();
//If the music is not playing for some reason, rewind it.
if (musicMenu.isPlaying() != true) {
musicMenu.pause();
musicMenu.rewind();
}
//And play the music
musicMenu.play();
} else if (state == 2) { //If it's the game, play the cool musics
//Pause other music players just in case.
musicMenu.pause();
musicEnd.pause();
//If the music is not playing for some reason, rewind it.
if (musicGame.isPlaying() != true) {
musicGame.pause();
musicGame.rewind();
}
//And play the music
musicGame.play();
} else if (state == 3) { //If it's gameover, play the sad musics
//Pause other music players just in case.
musicMenu.pause();
musicGame.pause();
//If the music is not playing for some reason, rewind it.
if (musicEnd.isPlaying() != true) {
musicEnd.pause();
musicEnd.rewind();
}
//And play the music
musicEnd.play();
}
} else { //If music is not supposed to be on... (oopsies :c)
//Pause everything
musicMenu.pause();
musicGame.pause();
musicEnd.pause();
}
}
// ball.pde
// ============================== [ 05. BALL OBJECT ] ============================== //
//The first object in name but not the first created. That was the button. Because it was easy.
//I hope I didn't mess anything up!
class ball { //Create a new class
//Set up variables
float d, max, rest; //in order of appearance: diameter, max speed, and rest position
PVector p, v, ps; //in order of appearance: position, velocity, and previous speed
ball(float tempX, float tempY) { //Parameters for fun
p = new PVector(tempX, tempY); //Position of the ball will be at the given values
v = new PVector(0, 0); //Starting velocity is 0
ps = new PVector(0, 0); //There's no previous speed yet. There's no speed at all actually.
d = 20; //Diameter 20
max = 16; //Max speed allowed is 16
}
void display() { //Function to fill and draw the ball
noStroke();
fill(lightblue); //A nice cool colour
ellipse(p.x, p.y, d, d); //Draw ball accordingly
}
void move() { //Function to move the ball
if ((p.y-d/2) <= 0) { //If the ball has hit the ceiling...
v.y *= -1; //Reverse the y velocity
}
if ((p.x-d/2) <= 0 || (p.x+d/2) >= width) { //Similarly, If the ball has hit the left or right wall...
v.x *= -1; //Reverse the x velocity
}
//Make the position change based on the velocity
p = p.add(v);
}
void moveStart() { //Function to give the ball it's first kick
v.x = 0;
v.y = -8;
}
void pause() { //Function to pause the game and store the ball's speed
if (v.x == 0 && v.y == 0 && breakout.paused) { //If the ball is not moving (ie: paused)
v.x = ps.x; //Make the ball move again according to its previous velocity
v.y = ps.y;
breakout.paused = false; //Game is no longer paused
} else { //If the ball IS moving when this function is called...
ps.x = v.x; //Store the velocity values
ps.y = v.y;
v.x = 0; //Set the velocity to 0 (stop it from moving)
v.y = 0;
breakout.paused = true; //Game is now paused
}
}
void rest() { //This function is called at the beginning of each round
//Stop the ball from moving
v.x = 0;
v.y = 0;
//Constrain the ball akin to the paddle
rest = constrain(mouseX, (player.getW()/2), width - (player.getW()/2));;
//Place ball atop paddle
p.y = width/8*5;
p.x = rest;
}
void reboundPaddle() { //Funky function to rebound the ball off of the paddle
//Set up a float that determines the distance between the ball and the center of the paddle
float bounce = dist(p.x,player.getY(), player.getX(), player.getY());
//Code to determine the rebounding v.x based on if the ball is to the left or to the right side of the paddle
if (p.x <= player.getX()) {
v.x = (bounce*0.2) *-1;
} else {
v.x = bounce*0.2;
}
//Set the new v.y based on the max speed and absolute value of v.x
v.y = (max - abs(v.x))*-1;
}
void reboundBrick() { //lazy lazy function for rebounding off of bricks
v.y *= -1;
}
boolean startPosition() { //Boolean to check whether the ball is in its starting position
if (p.y == width/8*5 && v.x == 0 && v.y == 0) {
return true;
} else {
return false;
}
}
boolean missed() { //Boolean to check whether the ball has fallen off screen
if (p.y >= height) {
return true;
} else {
return false;
}
}
boolean hitPaddle() { //Boolean to check whether the ball has hit the paddle
if (p.x >= player.getX() - (player.getW()/2) &&
p.x <= player.getX() + (player.getW()/2) &&
p.y >= player.getY() - (player.getH()/2) &&
p.y <= player.getY() + (player.getH()/2)) {
return true;
} else {
return false;
}
}
float getX() { //Quick simple float functions that return the ball's x and y position.
return p.x;
}
float getY() {
return p.y;
}
}
// brick.pde
// ============================== [ 06. BRICKS AND LIVES ] ============================== //
//I really like this one.
//Was it because it was short and easy?
//Yes.
class brick { //Create a new class
//Set up variables
color c; //Colour of brick
float x, y, w, h; //Floats for the rect() because I don't really need a PVector
boolean alive; //IS THE BRICK ALIVE?
brick(float tempX, float tempY) { //Create the brick according to the positions given
x = tempX;
y = tempY;
w = 50;
h = 30;
c = lightpink; //Default manly colour
alive = true; //They should be alive... they should be.
}
void display() { //Draws the bricks
//Quick setup stuff, get out of the way
rectMode(CENTER);
noStroke();
strokeWeight(3);
//Activate rainbow mode onto the blocks if rainbow mode is on and the brick is alive
if (rainbowOn) {
if (alive) {
c = color( int(random(150, 250)), int(random(150, 250)), int(random(150, 250)) );
}
} //I'm sorry if it hurts the eyes.
//Draw the brick
fill(c);
rect(x, y, w, h, 10);
}
boolean getStatus() { //CHECK IF THE BRICK IS ALIVE
return alive;
}
void die() { //Function to kill the brick
alive = false; //They're not alive anymore
c = darkblue; //Dead bricks are dark blue bricks
}
}
// button.pde
// ============================== [ 07. BUTTON OBJECT ] ============================== //
//Oh old friend, it has been too long! How have you been?
class button {//Create a new class
//Set up variables
float x, y, w, h; //Standard issue rect() values
color i, r, c; //Idle, rollover, clicked
boolean mouseOn, clicked; //To check whether the mouse is hovering over or clicking the button
String t; //Text
button(String text, float tempX, float tempY, float tempW, float tempH, color tempI, color tempR, color tempC) {
//To be honest, I don't need the colour parameters but it feels more flexible to me so I'm going to keep it.
//Sets the values
x = tempX;
y = tempY;
w = tempW;
h = tempH;
i = tempI;
r = tempR;
c = tempC;
t = text;
mouseOn = false; //Mouse is not over it
clicked = false; //It is not being clicked
}
void display() { //Function to draw the button
update(); //Call the update function to check whether the mouse is over or if it has been clicked
//Quick setup stuff
rectMode(CENTER);
stroke(pink);
strokeWeight(5);
//If the mouse is over
if (mouseOn) {
fill(r); //rollover colour
if (clicked) { //If it's clicked
fill(c); //clicked colour
}
} else {
fill(i); //otherwise, idle colour
}
//Draw the rect
rect(x, y, w, h, 20);
//Draw the text over it
fill(darkblue);
textFont(body);
textSize(36);
textAlign(CENTER,CENTER);
text(t, x, y);
}
void update() { //Function to check whether the mouse is over/button is being clicked
if (mouseOver(x, y, w, h) == true) {
mouseOn = true;
if (click()) {
clicked = true;
} else {
clicked = false;
}
} else {
mouseOn = false;
}
}
boolean mouseOver(float x, float y, float w, float h) { //Boolean to detect whether the mouse is over the button
if (mouseX >= (x-w/2) && mouseX <= (x+w/2) && mouseY >= (y-h/2) && mouseY <= (y+h/2)) {
return true;
} else {
return false;
}
}
boolean click() { //Boolean to detect whether the mouse is being clicked over the button
if (mouseOver(x, y, w, h) == true) {
if (mousePressed) {
return true;
} else {
return false;
}
} else {
return false;
}
}
}
// drawGame.pde
// ============================== [ 08. GAME MENU SCREEN ] ============================== //
void drawGame() { //Function to draw the game. No, really.
//Background
tint(60, 60, 80);
image(chalkboard, 0, 0, width, height);
//Draw bricks
breakout.build();
//Draw paddle and ball
player.display();
ball.display();
//Draw the pretty text at the bottom
fill(lightblue);
textFont(body);
textSize(20);
textAlign(LEFT, CENTER);
text("lives: " + str(breakout.lives), 20, height/64*63); //Uses the value inside of the game class because I love objects
textFont(body);
textSize(20);
textAlign(RIGHT, CENTER);
text("points: " + str(breakout.points), width-20, height/64*63); //Uses the value inside of the game class because I love objects v.2
if (breakout.active) { //If the game is active...
ball.move(); //Get the ball moving!
if (breakout.paused) { //If the game is paused, give them a reminder that hey, you paused the game, buddy.
textFont(body);
textSize(32);
textAlign(CENTER, CENTER);
text("Paused", xcenter, ycenter);
} else { //If the game is not paused, give them a reminder that hey, you can pause the game, buddy.
textFont(body);
textSize(20);
textAlign(CENTER, CENTER);
text("Click anywhere to pause", xcenter, height/64*63);
}
} else { //If the game is not active (ie: ball went off-screen)
ball.rest(); //Ball go back to the rest position atop the paddle
breakout.reset(); //Reset the game
textFont(body); //Display cute text reminding them that hey, click to start the game, buddy
textSize(32);
textAlign(CENTER, CENTER);
fill(pink);
text("Click anywhere to start", xcenter, ycenter);
}
if (ball.missed()) { //If the ball misses (off screen)
breakout.active = false; //GAME ENDS
breakout.lives -= 1; //You lose a life! :C
}
if (ball.hitPaddle()) { //If the ball hits the paddle
ball.reboundPaddle(); //BOUNCE
}
if (breakout.lives <= 0) { //If you have no more lives
state = 3; //GAME OVER
}
}
// game.pde
// ============================== [ 09. GAME OBJECT ] ============================== //
//Everything should be an object
//You get an object, YOU get an object, EVERYONE gets an object!
class game { //Create a new class
//Set up the variables
int points, lives;
boolean active, paused;
game() { //Simple, set the initial values
points = 0;
lives = 3; //Is 3 lives going to be enough?
active = false; //Not active yet
paused = false; //I'm going to be honest, I don't really understand why this one works
}
void build() { //BUILD THOSE BLOCKS aka WALL aka USA 2017 (also handy dandily checks for collision!)
for (int i=0; i<(numBricks*numRows); i++) {
bricks[i].display(); //draw them
collision((i%13)*60+40, (i/13)*40+30, i); //check for collision
}
}
void reset() { //Just recreates new bricks
for (int i=0; i<(numBricks*numRows); i++) {
bricks[i] = new brick((i%13)*60+40, (i/13)*40+30);
}
}
void points() { //Adds points
points += int(random(18, 24));
}
void collision(float bx, float by, int blockNum) { //Checks if the ball has collided with any brick
//Shoutout to Liam
//Sets up variables
float x, y;
x = ball.getX();
y = ball.getY();
//uses the ball's x and y return functions
if ( (x >= bx - 25) && (x <= bx + 25) && (y >= by - 15) && (y <= by + 15) ) { //If the ball is within a brick's hitbox
if (bricks[blockNum].getStatus()) { //Check if the brick is currently alive
bricks[blockNum].die(); //If it is, it dies.
ball.reboundBrick(); //Ball bounces
breakout.points(); //Give some points
}
}
}
}
void mousePressed() { //Small function using void mousePressed to start and pause the game
if (ball.startPosition()) { //If the ball is at the starting position
ball.moveStart(); //Get the ball moving
breakout.active = true; //Game is ON
} else {
ball.pause(); //Otherwise, it probably means the game is already active, PAUSE THE GAME!
}
}
// gameOver.pde
// ============================== [ 10. GAME OVER SCREEN ] ============================== //
//I'm sure I don't have to explain this...
//But I will.
//Because I'm a sweetheart.
void gameOver() { //Function to draw the game over menu
//draw background
tint(125, 125, 150);
image(chalkboard, 0, 0, width, height);
//Cool text detailing your score
textFont(title);
textSize(72);
textAlign(CENTER, CENTER);
fill(lightpink);
text("game over!", xcenter, ycenter-150);
fill(lightblue);
text("final score: " + str(breakout.points), xcenter, ycenter-50);
//Display the button to return home
main.display();
//If its clicked, go back to the main menu
if (main.click()) {
state = 0;
mousePressed = false;
}
}
// mainMenu.pde
// ============================== [ 11. MAIN MENU SCREEN ] ============================== //
//Three buttons, WHOA
//Yes, it's true. I really am a self-proclaimed object connoisseur!
void mainMenu() { //Function to draw the main menu
//Background
tint(125, 125, 150);
image(chalkboard, 0, 0, width, height);
//Display all three buttons
start.display();
options.display();
exit.display();
//Title. No, seriously it's just the title
textFont(title);
textSize(84);
fill(darkpink);
text("Breakout", width/2*1.01, height/5*1.02);
fill(pink);
text("Breakout", width/2, height/5);
//Button clicking
if (start.click()) { //If the start button is clicked, state = game and make a new game object (reset)
state = 2;
breakout = new game();
mousePressed = false;
} else if (options.click()) { //If the options button is clicked, options screen now
state = 1;
mousePressed = false;
} else if (exit.click()) { //If the exit button is clicked... then ok, bye.
exit();
}
}
// optionsMenu.pde
// ============================== [ 12. OPTIONS SCREEN ] ============================== //
//Same old, same old
void optionsMenu() { //Draws the options menu
//background
tint(125, 125, 150);
image(chalkboard, 0, 0, width, height);
//Display all three buttons
main.display();
music.display();
rainbow.display();
//Title. Ok, not exactly. It says options.
textFont(title);
textSize(84);
textAlign(CENTER,CENTER);
fill(darkpink);
text("Options", width/2*1.01, height/5*1.02);
fill(pink);
text("Options", width/2, height/5);
//Button functionality
if (main.click()) { //If the main menu button is clicked, return to the main menu
state = 0;
mousePressed = false;
} else if (music.click()) { //If the music button is clicked...
//Check whether music is currently on or off
if (musicOn) { //If it's on, turn it off please
music.t = "Music: OFF";
musicOn = false;
} else if (!musicOn) { //If it's off, turn those tunes up!
music.t = "Music: ON";
musicOn = true;
}
mousePressed = false;
} else if (rainbow.click()) { //If the rainbow button is clicked
//Check whether rainbow mode is currently on or off
if (rainbowOn) { //If it's on, sleepy pink dream time
rainbow.t = "Rainbow: OFF";
rainbowOn = false;
} else if (!rainbowOn) { //If it's off, time to go to the club
rainbow.t = "Rainbow: ON";
rainbowOn = true;
}
mousePressed = false;
}
}
// paddle.pde
// ============================== [ 13. PADDLE OBJECT ] ============================== //
//This one is pretty simple.
//Most of the functionality (ie: hit detection) is shoved onto other objects.
class paddle { //Create a class
//Set up variables
PVector p;
float w, h;
color c;
paddle() { //Initialize the variables!
p = new PVector(xcenter, height/8*7);
w = width/6;
h = height/32;
c = lightpink; //It's a soothing colour
}
void display() { //Draws the paddle
p.x = constrain(mouseX, w/2, width - w/2); //Paddles must be constrained
rectMode(CENTER);
noStroke();
fill(c);
rect(p.x, p.y, w, h, 5);
}
//Floats to return values. I don't really need the last two but whatever. It's nice to stay consistent!
float getX() {
return p.x;
}
float getY() {
return p.y;
}
float getW() {
return w;
}
float getH() {
return h;
}
}
//I really enjoy documentation
//But don't tell anybody
// ('-')>P
//Or I'll use my axe
read-only archive source from /2017/Assignments/A04 - Old School Games/03-29-2017/Assignment_04/Assignment_04.pde