For the midterm assignment, we were asked to make a video game using processing. I made one in which the person has to try crossing the road without getting hit by the car.
Process
I used basic shapes to create a scenario of a road on the screen. The roads are made out of the rectangles. The cars are made using the rectangle with curved edges and ellipses for wheels. Along with a sun in the corner.
So my game is quite simple. Using sprite sheet, I made a person move across the screen for the game. The cars come from the side in random speeds to try and hit the person. The speed increases with increase in every level.
There are three levels in total and are shown in the top-left corner. When the bar goes all the way up to green, it means that the player has all three lives. On getting hit by the car, a life is reduced and the player as to start the game all the way from the start of the road.
I have added sound to the game( which somehow does not come in the video below). The sounds add a a special affect to the game I believe. The start of the game shows instructions on how to play the game. Just follow the simple instructions and win the game.
The player to cross all three rounds, wins the game. Here’s the video for the game:
CODE:
PFont intro;
PImage spritesheet;
PImage background_image;
PImage[][] sprites;
int direction = 1; // 0 up
int step = 0;
int x;
int y;
int speed = 3;
int level = 1;
float car1x = 750;
float hood1x = 720;
float wheel1a = 825;
float wheel1b = 775;
float car2x = -150;
float hood2x = -50;
float wheel2a = -130;
float wheel2b = -75;
float speed1 = 50;
float speed2= 50;
int lives = 3;
import processing.sound.*;
SoundFile sound_cars;
SoundFile sound_levelup;
SoundFile sound_lost;
SoundFile sound_win;
SoundFile sound_crash;
boolean instruct = true;
void setup() {
size(600,600);
background_image = loadImage("im_game.jpg"); //the background image
spritesheet = loadImage("man_walking.png"); //the man
sprites = new PImage[4][9]; //putting spritesheet in an array
int w = spritesheet.width/9;
int h = spritesheet.height/4;
for (int y=0; y < 4; y++) {
for (int x=0; x< 9; x++) {
sprites[y][x] = spritesheet.get(x*w, y*h, w, h);
}
}
x = 300; // setting the original values
y = 370;
imageMode(CENTER);
//introducing sound.
sound_cars = new SoundFile(this, "sound_cars.wav");
sound_levelup = new SoundFile(this,"sound_levelup.wav");
sound_lost= new SoundFile(this,"game_lost.mp3");
sound_win = new SoundFile(this,"sound_win.wav");
sound_crash = new SoundFile(this,"sound_crash.mp3");
//playing the sound of cars
if (level < 4 && lives > 0 ){
sound_cars.loop();
}
}
void draw() {
//laying a base
background(255,70);
//displaying the image
image(background_image,400,280);
//showing the instructions
instructions();
if (instruct == false){
lifeline(); ///showing the lifelines
scene();//displaying the scene
car1_dimension();//calling out the car1 dimensions
car2_dimensions();//calling out the car2 dimensions
//executing the game
if (level == 1 && lives >0) {
movecar1();
if (car1x < 0){
speed1 = random(5,10);
car1x = 750;
hood1x = 720;
wheel1a = 825;
wheel1b = 775;
}
movecar2();
if (car2x > 600){
speed2 = random(5,10);
car2x = -150;
hood2x = -50;
wheel2a = -130;
wheel2b = -75;
}
//Determining the direction of the man
moveman();
image(sprites[direction][step], x, y); //printing the image
crash();//calling out the crash function
}
if (level == 2 && lives >0 ){
car1_dimension();
movecar1();
if (car1x < 0){
speed1 = random(10,13);//speed upgrades with every level
car1x = 750;
hood1x = 720;
wheel1a = 825;
wheel1b = 775;
}
car2_dimensions();
movecar2();
if (car2x > 600){
speed2 = random(10,13);
car2x = -150;
hood2x = -50;
wheel2a = -130;
wheel2b = -75;
}
//Determining the direction of the man
moveman();
image(sprites[direction][step], x, y); //printing the image
crash();
}
if (level == 3 && lives >0){
car1_dimension();
movecar1();
if (car1x < 0){
speed1 = random(13,17);
car1x = 750;
hood1x = 720;
wheel1a = 825;
wheel1b = 775;
}
car2_dimensions();
movecar2();
if (car2x > 600){
speed2 = random(13,17);
car2x = -150;
hood2x = -50;
wheel2a = -130;
wheel2b = -75;
}
//Determining the direction of the man
moveman();
image(sprites[direction][step], x, y); //printing the image
crash();
}
//level up settings
if (y>600){
level+=1;
sound_levelup.play(); //level up sound
car1x = 750;
hood1x = 720;
wheel1a = 825;
wheel1b = 775;
car2x = -150;
hood2x = -50;
wheel2a = -130;
wheel2b = -75;
speed1 = 20;
speed2= 20;
x= 300;
y= 370;
}
if (y<370){
y=370;
}
if (x<0){
x=0;
}
if (x>600){
x=600;
}
if (y>600){
sound_levelup.play();
}
if (instruct == false){
leveltext(); // to display level only on starting the game
}
}
}
//function to indicate lives left
void lifeline(){
if (lives == 3){
//shows all three boxes
fill(#F21D1D);
rect(10,10,50,10);
fill(#FACC12);
rect(55,10,95,10);
fill(#62E32C);
rect(100,10,140,10);
}
if (lives ==2){
//deleted the green box showing two lives left
fill(#F21D1D);
rect(10,10,50,10);
fill(#FACC12);
rect(55,10,95,10);
}
if (lives == 1){
//deletes the orange box showing one life left
fill(#F21D1D);
rect(10,10,50,10);
}
}
void scene(){
//background
stroke(255,69,0);
strokeWeight (3);
stroke (1);
strokeWeight (1);
//road
fill(100);
rect(0,400,600,200);
//road lines
fill(255,255,0);
rect(0,475,600,8);
rect(0,517,600,8);
//sun
arc(600,0,200,200,radians(90),radians(180),PIE);
}
void car1_dimension(){
fill(0,191,255);
rect(car1x,405,100,50,80,30,0,0);
rect(hood1x,430,30,25,40,0,0,0);
fill(1);
ellipse(wheel1a,450,25,25);
ellipse(wheel1b,450,25,25);
}
void car2_dimensions(){
fill(255,20,20);
rect(car2x,535,100,50,30,80,0,0);
rect(hood2x,560,30,25,0,40,0,0);
fill(1);
ellipse(wheel2a,580,25,25);
ellipse(wheel2b,580,25,25);
}
void movecar1(){
//subtracting speed to make the car move left
car1x -= speed1;
hood1x -= speed1;
wheel1a -= speed1;
wheel1b -= speed1;
}
void movecar2(){
//adding speed to make the car move right
car2x += speed2;
hood2x += speed2;
wheel2a += speed2;
wheel2b += speed2;
}
//function to make the man move
void moveman(){
if (keyPressed) {
if (keyCode == DOWN) { //to move down
direction = 2;
y +=speed*0.6;
}
if (keyCode == LEFT) { //to move left
direction = 1;
x -=speed*0.6;
}
if (keyCode == RIGHT) { //to move right
direction = 3;
x +=speed*0.6;
}
if (keyCode == UP) { //to move up
direction = 0;
y -=speed*0.6;
}
if (frameCount%speed==0) { //repeating the spritesheet
step = (step+1) % 9;
}
}
}
//function to detect if the man crashes with the car
void crash(){
//calculating distance between man and the car
float distancehood1 = dist(hood1x,430,x,y);
float distancehood2 = dist(hood2x,560,x,y);
float distancecar1 = dist(car1x,405,x,y);
float distancecar2 = dist(car2x,535,x,y);
float distancebody1 = dist(car1x + 30,405,x,y);
float distancebody2 = dist(car2x + 30,405,x,y);
//detecting a crash
if (distancehood1 < 15){
y = 370;// restarting man's position to the start of the road
lives -=1;// reducing lives
sound_crash.play(); //playing sound
}
if (distancehood2 < 15){
y = 370;// restarting man's position to the start of the road
lives-=1; // reducing lives
sound_crash.play(); //playing sound
}
if (distancecar1 < 15){
y = 370; // restarting man's position to the start of the road
lives -=1; // reducing lives
sound_crash.play(); //playing sound
}
if (distancecar2 < 15){
y = 370; // restarting man's position to the start of the road
lives -=1; // reducing lives
sound_crash.play(); //playing sound
}
if (distancebody1 < 15){
y = 370; // restarting man's position to the start of the road
lives -=1; // reducing lives
sound_crash.play(); //playing sound
}
if (distancebody2 < 15){
y = 370; // restarting man's position to the start of the road
lives-=1; // reducing lives
sound_crash.play(); //playing sound
}
}
//displaying level of the game
void leveltext(){
if (level == 4 && lives >0){
background(0);
textSize(50);
fill(255);
text("Congratulations! \n You Win!!",150,200);
sound_cars.stop();
}
if (level == 1 && lives >0 || level == 2 && lives >0 || level == 3 && lives >0){
textSize(100);
fill(#1AD628);
textAlign(CENTER);
text(level,278,100);
}
if (lives == 0){
background(0);
textSize(60);
fill(255,0,0);
text("You Lost! \n Game Over:(", 300,200);
textSize(20);
text("Press SHIFT to play again",300,500);
sound_cars.stop();
//restarting the game
if (keyPressed){
if (keyCode== SHIFT){
sound_lost.stop();
car1x = 750;
hood1x = 720;
wheel1a = 825;
wheel1b = 775;
car2x = -150;
hood2x = -50;
wheel2a = -130;
wheel2b = -75;
x= 300;
y= 370;
lives = 3;
level = 1;
image(background_image,400,280);
scene();
sound_cars.loop();
}
}
}
}
//displaying instructions of the game
void instructions(){
if (instruct == true){
background(0);
fill(#22F0E0);
textSize(20);
text("Make the man cross the road without getting hit by a car",100,100);
text(" ↑ : to move up", 100,125);
text("↓: to move down", 100,150);
text(" ←: to move left",100,175);
text(" → : to move right",100,200);
text("Press Shift to make the instructions dissappear", 100,225);
textSize(50);
intro = createFont("STFangsong",32);
textFont(intro);
fill(0,0,random(150,255));
text("DON'T CRASH!",150,500);
}
//to remove instructions and start the game.
if (keyPressed){
if (keyCode == SHIFT){
instruct = false;
}
}
}
As I mentioned in my proposal, my game was inspired by Fruit Ninja. Apart from slicing the fruits, you had to catch the covid objects, like pills, syringes, sanitizers, and masks. You also had to avoid catching the virus. There is also a powerup in the game, that looks like a clock, which slows the frameRate for about 5 seconds.
Process:
The game starts by showing a start screen, which gives the instructions for the game. Then, when you click any key on the keyboard, the game starts.
I used the keypressed function in order to switch from the start screen to the main game.
After that, you can see a glove and objects falling from the top. The glove is made on mouse X and mouse Y. The objects use a random function on the width, to fall randomly from different sides and I have used gravity along with velocity to randomise the speed of the objects falling from the top.
Everytime, you catch the object, score increases by 1 and everytime you miss an object, the X on top gets red. If you miss 10 objects, the game ends. If you catch the virus game ends. I have used null to remove the objects.
Once the game ends, you see the following screen and I have again used keypressed function to restart the game, once the game ends.
This is what the game looks like:-
The code for the above game is below:-
//setting up the variables, importing libraries, importing images and fonts.
import processing.sound.*;
SoundFile file;
SoundFile objects;
SoundFile bomb;
SoundFile gameover;
PImage back;
PImage knife;
PImage start;
PFont font;
int height = 720;
int width = 1040;
int frame = 0;
//initialising variables
Game game = new Game();
boolean gameOver = false;
boolean startScreen = true;
void setup(){
//setting up the screen and the background
size(1040, 720);
back = loadImage("back.jpg");
background(0);
font = createFont("Optima-Bold", 20);
//accessing sound path
file = new SoundFile(this, "main.wav");
objects = new SoundFile(this, "objects.mp3");
bomb = new SoundFile(this, "bomb.mp3");
gameover = new SoundFile(this, "gameover.mp3");
//loading the images
knife = loadImage("knife.png");
knife.resize(100,150);
start = loadImage("start.jpeg");
start.resize(width,height);
//looping the background music
file.amp(0.05);
file.loop();
}
void draw(){
// setting font to optima bold
textFont(font);
//changing cursor settings
if(startScreen || gameOver){
cursor(CROSS);
} else {
cursor(knife);
}
// start screen for instructions
if(startScreen){
background(start);
strokeWeight(0);
fill(200,0,0,100);
rect((width/2)-400,(height/2)-150,800,400);
fill(200,0,0,200);
stroke(255);
textSize(80);
textAlign(CENTER);
text("COVID NINJA", (width/2)-20,(height/2)-180);
fill(255);
textSize(40);
textAlign(CENTER);
text("INSTRUCTIONS", (width/2)-20,(height/2)-100);
textSize(30);
textAlign(CENTER);
text("Catch every object on the screen apart from the virus. \n If you catch the virus, you die. If you catch the clock, \n the time slows and the objects move slower. Use the cursor \n to catch the objects", (width/2),(height/2)-50);
text("PRESS ANY KEY TO START",(width/2), (height/2)+150);
textAlign(BASELINE);
}
//condition to remove the start screen after keypressed
if(keyPressed){
startScreen = false;
}
//starting the game
if(!startScreen){
background(back);
//score change
fill(255); textSize(30); text("SCORE: ", 40, 50);
text(game.score, 150, 50);
//x's turing red after every miss
for(int i = 0; i < 10; i++){
if(i < game.missed){
fill(255,0,0);
}
else {
fill(220, 220, 220);
}
textSize(40);
text("x", width - 40 - (30*i), 50);
}
game.display();
//restarting the game after game is over
if(gameOver == true && keyPressed){
game = new Game();
frameRate(60);
gameOver = false;
}
}
}
//setting up class covid with variables
class Covid {
int x, y;
float vy, g;
boolean virus = false;
boolean clock = false;
PImage img;
int img_w, img_h;
Covid() {
x = (int) random(150, width-150);
y = 60;
g = random(0.1,0.3);
int selection = (int) random(1,60);
if(selection < 8){
selection = 6;
} else if(selection < 10) {
selection = 5;
} else {
selection = (selection%4) + 1;
}
img = loadImage(str(selection) + ".png");
if(selection == 6){
virus = true;
}
if(selection == 5){
clock = true;
}
img_w = 100; img_h = 100;
}
void gravity() {
vy = vy + g;
}
void update() {
gravity();
y += vy;
}
void display() {
update();
image(img, x, y, img_w, img_h);
}
}
class Game {
int difficulty;
int score;
int missed;
int numCovidItems;
boolean roundOver;
Covid[] covid;
//using the constructor
Game() {
difficulty = 1;
score = 0;
missed = 0;
roundOver = true;
}
//defining method
void updateCovid(){
//increasing the number of objects
difficulty++;
numCovidItems = difficulty;
covid = new Covid[numCovidItems];
for (int i=0; i<numCovidItems; i++){
covid[i] = new Covid();
}
}
//method display
void display(){
//condition for game over
if(missed >= 10){
// GAME OVER SCREEN
if (gameOver == false){
gameover.play();
}
gameOver = true;
textSize(30);
fill(255);
text("PRESS ANY KEY TO RESTART.", (width/2)-200, height/2);
} else {
if(frame+150 < frameCount){
frameRate(60);
}
if(roundOver == true){
updateCovid();
roundOver = false;
}
numCovidItems = difficulty;
//making the objects disappear
int killedobjs = 0;
for (int i=0; i<numCovidItems; i++){
if(covid[i] == null){
killedobjs++;
}
}
if(killedobjs == numCovidItems){
roundOver = true;
}
for (int i=0; i<numCovidItems; i++){
// checks if object is missed
if(covid[i] != null){
if(covid[i].y > height) {
if(!covid[i].virus) { missed++; }
covid[i] = null;
}
}
// checks if object is cut else displays
if(covid[i] != null){
if((covid[i].x <= (mouseX+50) && (mouseX-50) <= (covid[i].x + covid[i].img_w)) && (covid[i].y <= (mouseY+40) && (mouseY-50) <= (covid[i].y + covid[i].img_h))){
if(covid[i].virus) {
missed = 11;
bomb.play();
} else if (covid[i].clock) {
covid[i] = null;
frameRate(20);
frame = frameCount;
} else {
covid[i] = null;
score++;
objects.amp(0.1);
objects.play();
}
} else {
covid[i].display();
}
}
}
}
}
}
The purpose of this game is for the student, skeleton in the picture, to collect all the questions and then collect the final paper to get the full grade for their exam.
IDEA(details):
Background:
Originally, I intended to have clouds as the background with the stars, but even when I used images instead of pixels, the game’s frameRate dropped and the game was barely functioning so I settled for a clear starry night look.
I choose this because:
firstly, I have really sensitive eyes, and I find it easier on my eye to look at a dark screen.
Secondly, I am a night person, night always looks better ;).
For the stars, I used overlapping circles to give a faded look, which resembles actual stars more than normal circles would.
Code:
class Bg {
float[] x = new float[50];
float[] y = new float[50];
Bg() {
for (int i=0; i<50; i++) {
x[i] = random(0, widthh);
y[i] = random(0, heightt);
}
}
void drawBG() {
if (frameCount%50==0) {
for (int i=0; i<50; i++) {
x[i] = random(0, width);
y[i] = random(0, height);
}
}
background(50, 50, 80);
for (int i=0; i<50; i++) {
for (int j=0; j<10; j++) {
noStroke();
fill(255, 25);
ellipse(x[i], y[i], 1+j, 1+j);
}
}
}
}
Creature:
Instead of making repetitive classes, I used class inheritance, where I made the Superclass, Creatures, for all the live items in the game.
For the player, I choose the skeleton, because obviously, we are all dead during midterm and final seasons. That also gave me the idea of the name “dead student“.
Sprite:
With the player, I struggled a little with the jumps. I wanted to only allow double jumps, which I thought was the case until my roommate tried it and I realized it didn’t. I tried to fix it, but that only made it worse, so I left it as it was when my roommate tried it. (I mean what works, works right?)
Code:
class Player extends Creature {
boolean left;
boolean up;
boolean right;
SoundFile jump;
int count;
Player(float x, float y, float r, float g, PImage _img, float w, float h, int num_frames, PApplet sketch) {
super(x, y, r, g, _img, w, h, num_frames);
left = false;
up = false;
right = false;
jump = new SoundFile(sketch, "jump.wav");
count = frameCount;
}
void update() {
gravity();
//====================
if (left==true) {
speed.x=-3;
dir=false;
} else if (right==true) {
speed.x=3;
dir=true;
} else {
speed.x=0;
}
//====================
if (up==true && pos.y>=ground-size.y &&secondJump==false) {
jump.play();
speed.y = -6;
} else if (up==true && speed.y<0 && secondJump==true && pos.y<ground-size.y) {
jump.play();
speed.y = -8;
secondJump=false;
}
pos.x +=speed.x;
pos.y +=speed.y;
if (frameCount%5 == 0 && speed.x !=0 && speed.y ==0) {
frame= (frame+1)%numFrames;
} else if (speed.x==0) {
frame=8;
}
//====================
if (pos.x<=0) {
pos.x = 0;
}
//====================
if (pos.x+size.x>= widthh) {
pos.x = widthh- size.x;
} else if (pos.x<size.x) {
pos.x = size.x;
}
//====================
for (int i =0; i<game.myMonsters.length; i++) {
float numME = pow((pow(size.x, 2)+(pow(size.y, 2))), 0.5);
float numthem = pow((pow(game.myMonsters[i].size.x, 2)+(pow(game.myMonsters[i].size.y, 2))), 0.5);
if (game.myMonsters[i].distance(this)<=numME/2.5+numthem/2.5 && game.myMonsters[i].alive) {
if (speed.y>0) {
game.myMonsters[i].alive=false;
game.myMonsters[i].sound2.play();
} else {
game.myMonsters[i].sound1.play();
alive = false;
}
}
}
for (int i =0; i<game.myQuestions.length; i++) {
if (game.myQuestions[i].distance(this)<=size.x/2+game.myQuestions[i].size.x/2 && game.myQuestions[i].alive) {
game.score++;
game.myQuestions[i].sound.play();
game.myQuestions[i].alive = false;
}
}
if (game.myfinal.distance(this)<=size.x/2+game.myfinal.size.x/2 && game.myfinal.alive) {
game.score+=5;
game.myfinal.sound.play();
game.myfinal.alive = false;
game.won= true;
}
}
}
Monsters:
Finding Sprites for the monsters was not easy. I was trying to find monsters that represent the distractions we have while studying. So instead of finding different monsters, I made my own using emojis and clip art images.
I used the facial features from the emojis and the body of what I think represents distractions. I made them alternate between an Innocent look and an evil look because distractions usually don’t look so bad.
For this one, I thought it was better to have a bouncy still image, as it was the only one that was completely dead. I used the same class for both as they had the same charactaristics.
For the full game, I made a game class that allows me to create a game object whenever I want to start the game. This helps with restarting and waiting for the user to press a key to start; as I just create a game object if the player is dead and the user presses enter at the welcom screen.
My game class only had the creation of all the game objects and the display call for them.
Code:
class Game {
//
int ground = 100;
public int score = 0;
boolean won;
Player myplayer;
Platforms[] plats = new Platforms[5];
Platforms groundd;
int platLocH[] = new int[5];
int platLocW[] = new int[5];
Monster[] myMonsters = new Monster[6];
Question[] myQuestions = new Question[5];
Question myfinal;
Game(PApplet sketch, PImage playerImg, PImage[] monsters, PImage questionIMG, PImage finalIMG) {
// creating ground
groundd = new Platforms(0, heightt-ground, widthh, ground, 0);
// creating platforms
for (int i=0; i<plats.length; i++) {
platLocH[i] = (heightt-255)-i*155;
platLocW[i] = int(random(0, widthh-500));
plats[i]= new Platforms(platLocW[i], platLocH[i], 600, heightt/25, 15);
}
// creating Monsters(1@ground & 4 at plats 0-3)
int n;
float m;
for (int i=4; i>-1; i--) {
n = int(random(0, 3));
myMonsters[i]= new Monster(platLocW[i]+60, platLocH[i]-60*(monsters[n].width/monsters[n].height)-10, 60, ground, monsters[n], monsters[n].width, monsters[n].height, 2, platLocW[i]+random(40, 50), platLocW[i]+random(160, 220), sketch);
}
n = int(random(0, 3));
m = random(widthh/2, widthh-450);
myMonsters[5] = new Monster(m+60, heightt-ground, 60, ground, monsters[n], monsters[n].width, monsters[n].height, 2, m+random(40, 50), m+random(160, 220), sketch);
// creating Questions
for (int i=3; i>-1; i--) {
myQuestions[i]=new Question(platLocW[i]+400, platLocH[i]-60*(questionIMG.width/questionIMG.height)-50, 60, ground, questionIMG, questionIMG.width, questionIMG.height, 1, sketch);
}
myQuestions[4]=new Question(m+400, heightt-ground, 60, ground, questionIMG, questionIMG.width, questionIMG.height, 1, sketch);
// creating final
myfinal = new Question(platLocW[4]+400, platLocH[4]-80*(finalIMG.width/finalIMG.height)-50, 80, ground, finalIMG, finalIMG.width, finalIMG.height, 1, sketch);
myfinal.alive=false;
// creating Player
myplayer = new Player(20, height-ground-180, 70, ground, playerImg, playerImg.width, playerImg.height, 9, sketch);
won = false;
}
void main() {
for (int i = 0; i<5; i++) {
plats[i].display();
if (myQuestions[i].alive) {
myQuestions[i].display();
}
}
groundd.display();
myplayer.display();
for (int i=0; i<6; i++) {
if (myMonsters[i].alive) {
myMonsters[i].display();
}
}
if (score == myQuestions.length) {
myfinal.alive = true;
myfinal.display();
}
}
}
Start and end screens and Key presses:
I made functions to display text at the start and end of each game. This allowed me to easily call either whenever needed.
I reset my game variables in the end screen functions, but that is just to avoid unexpected errors.
void startSc() {
textFont(f, 80);
fill(255);
textAlign(CENTER);
text("Dead Student", widthh/2, heightt/2-100);
textFont(f, 40);
text("press enter to start", widthh/2, heightt/2-10);
textFont(f, 20);
text("Collect the questions to get to final paper", widthh/2, heightt/2+100);
text("You can kill monsters by jumping on them", widthh/2, heightt/2+130);
text("Please use left, right, and up arrows to play", widthh/2, heightt/2+190);
}
void endSc() {
if (game.won) {
textFont(f, 40);
fill(255);
textAlign(CENTER);
text("YOU WON!!", widthh/2, heightt/2);
text("YOUR GRADE IS:", widthh/2, heightt/2+50);
text(String.valueOf(game.score)+" /10", widthh/2, heightt/2+100);
text("CONGRATS!!", widthh/2, heightt/2+150);
textFont(f, 20);
text("press r to reset", widthh/2, heightt/2+200);
} else {
for (int i=0; i<game.myMonsters.length; i++) {
game.myMonsters[i].alive=true;
}
for (int i=0; i<game.myQuestions.length; i++) {
game.myQuestions[i].alive=true;
}
textFont(f, 40);
fill(255);
textAlign(CENTER);
text("YOU DIED", widthh/2, heightt/2);
text("YOUR GRADE IS:", widthh/2, heightt/2+50);
text(String.valueOf(game.score)+" /10", widthh/2, heightt/2+100);
textFont(f, 20);
text("press r to reset", widthh/2, heightt/2+150);
}
}
Key Pressed/Released Code:
I created the game when the user presses enter;
void keyPressed() {
if (keyCode == 10 && started==false) {//enter
started = true;
game = new Game(this, playerimg, monsters, question, finall);
} else if (keyCode==82 && started==true) {//r-> reset
started=false;
startSc();
} else if (started==true && game.won==false) {
if (keyCode == LEFT) {
game.myplayer.left = true;
}
if (keyCode==RIGHT) {
game.myplayer.right = true;
}
if (keyCode==UP) {
game.myplayer.up = true;
}
}
}
void keyReleased() {
if (started==true) {
if (keyCode == LEFT) {
game.myplayer.left = false;
}
if (keyCode==RIGHT) {
game.myplayer.right = false;
}
if (keyCode==UP) {
game.myplayer.up = false;
game.myplayer.secondJump=true;
}
}
}
As mentioned before, for my midterm project, I was inspired by the mechanics of the Chrome Dino Runner game and tried to create a newer version with more features that I called “Knight Runner”. To avoid obstacles, the avatar can either jump over the fire, or jump/crouch when it’s a bird.
Process & Features:
Parallax:
To give the game a realistic aspect, I added a Parallax effect in which the far-away clouds and mountains seem to move more slowly than the closer ones, by changing each layer’s (6 layers) position by a different amount (between 1 and 5).
Instead of making the character move inside the display window, I used an infinite side-scrolling in which the character is static whereas the background moves from the right to the left. To achieve that, I used two images placed next to each other that reappear on the right side once they get out of the display window.
// Infinite scrolling
if (x6<=-width){x6=width;} if (x6_2<=-width){x6_2=width;}
if (x5<=-width){x5=width;} if (x5_2<=-width){x5_2=width;}
if (x4<=-width){x4=width;} if (x4_2<=-width){x4_2=width;}
if (x3<=-width){x3=width;} if (x3_2<=-width){x3_2=width;}
if (x2<=-width){x2=width;} if (x2_2<=-width){x2_2=width;}
if (x1<=-width){x1=width;} if (x1_2<=-width){x1_2=width;}
Spritesheet:
To animate the character, I am using 3 sprite sheets stored in a 2D array (running, jumping, sliding, dying), each row has 10 images.
To animate the obstacles, I am using 2 other sprite sheets, one for fire (64 images), and the other for birds (9 images).
I am using frameCount to loop over the sprites
// Upload all the sprites
void loadsprites(){
// Running
for (int i=0; i<sprites.length;i++){
sprites[i][0]=loadImage("assets/run/run"+i+".png");
sprites[i][0].resize(53,74);
}
// Jumping
for (int i=0; i<sprites.length;i++){
sprites[i][1]=loadImage("assets/jump/jump"+i+".png");
sprites[i][1].resize(53,74);
}
// Sliding
for (int i=0; i<sprites.length;i++){
sprites[i][2]=loadImage("assets/slide/slide"+i+".png");
sprites[i][2].resize(53,57);
}
// Dying
for (int i=0; i<sprites.length;i++){
sprites[i][3]=loadImage("assets/dying/Dead__00"+i+".png");
sprites[i][3].resize(73,77);
}
// Fire
for (int i=0; i<firesprites.length;i++){
firesprites[i]=loadImage("assets/fire/tile0"+i+".png");
firesprites[i].resize(60,60);
}
// Bird
for (int i=0; i<birdsprites.length;i++){
birdsprites[i]=loadImage("assets/bird/tile00"+i+".png");
birdsprites[i].resize(80,80);
}
}
Gravity:
I am using a gravity effect for both jumping and sliding, to give the animation a realistic aspect. When jumping, the speed is continuously decreased by the amount of gravity, however, when crouching, it gets increased.
void move(){
ycoord -= speed;
// gravity if jumps
if (ycoord<425){
speed -= gravity;
}
// gravity if crouches
else if (ycoord>425){
ycoord += speed;
speed += gravity;
}
// remain same when running
else{
state=0;
speed=0;
ycoord=425;
}
}
Jump and Crouch:
The user cannot jump and crouch at the same time. Both the jump and crouch are done using ycoord, speed and the gravity.
To generate obstacles I am both using frameCount and two random functions, the first one is used to choose when to generate the obstacle, whereas the second one is used to choose what to generate (fire or bird). The obstacles are automatically stored in an array to keep track of their position and to display them continuously. If the obstacles disappear from the screen (go over the edge) they get immediately remove from the array.
The bird obstacles have a higher speed than fire because technically fire is static so it should have the same speed as the scrolling, whereas the bird is flying.
// add a new obstacle
void addObstacle(){
if (frameCount % 60 == 0 && random(1)<0.5){
// choose randomly if its fire or a bird
if (random(2)<1){
// add it to the list
firelist.add(new Fire());
}
else {
birdlist.add(new Bird());
}
}
}
Collisions:
To flag when the avatar touches one of the obstacles, I first used a rectangle to limit the areas of both the obstacle and avatar, then checked if those areas overlapped. Meaning that the x and y coordinates of the avatar would be inside the obstacle area. The avatar should jump when there is fire, but can either jump or crouch when there is a bird, to avoid collisions with obstacles.
// Bird
boolean checkfail(float ycoord){
// if crouching, avatar is safe
if (avatar.state==2){
return false;
}
// check if avatar touches the obstacle
return xcoord+25>=100 && xcoord+25<=150 && ycoord>=400 && ycoord<=400+30 || xcoord+40+25>=100 && xcoord+40+25<=150 && ycoord+70>=400 && ycoord<=400+30;
}
// fire
boolean checkfail(float ycoord){
// check if avatar touches the obstacle
return xcoord>=100 && xcoord<=150 && ycoord+70>=540-60-30 || xcoord+40>=100 && xcoord+40<=150 && ycoord+70>=540-60-30;
}
Menu:
Similarly, to check which button the user clicked, I used mouseClicked(), mouseX, and mouseY and checked whether the x and y coordinates are inside the area of that specific button. The menu (lobby) is displayed first, then the user has a choice to either start the game, or read the instructions.
To switch between lobby, game, and instructions pages, I am overlapping backgrounds over each other.
draw():
Inside my draw() function, I mainly check the status of the game using boolean variables (avatar died, menu, reset, game ongoing…), then proceed with displaying the right images, and the right text.
Boolean variables:
Start: flags when the user clicks on the start button, if start is false, then the menu is displayed
Help: flags when the user clicks on the help button, the instructions are then displayed
Dead: flags when the avatar touches an obstacle, the game then ends, and the user is given a choice to replay
Reset: flags when the user chooses to replay, all the games settings are reset, and the arrays get cleared
void draw(){
// if instructions icon clicked
if (help){
// mute the music
back.amp(0);
image(main,0,0);
imageMode(CENTER);
image(instructmenu,width/2,height/2);
image(backmenu,width/2, height/2-200);
imageMode(CORNER);
textSize(20);
text("↑ : Jump",width/2,height/2-60);
text("↓ : Crouch",width/2,height/2-20);
text("Try to avoid all obstacles",width/2,height/2+20);
text("(Fire, birds)",width/2,height/2+60);
noFill();
rect(width/2-30,height/2-230,60,60);
}
else if (start){
// unmute the music if alive
if (dead==false) {back.amp(1);}
// mute the music if dead
else {back.amp(0);}
display(); // display the background images
update(); // parallax effect and infinite scrolling
rect(100,425,50,70);
avatar.show(); // display the avatar
addObstacle(); // add an obstacle
imageMode(CENTER);
// Display the score
image(scoremenu, width/2, 50);
imageMode(CORNER);
textSize(20);
textAlign(CENTER);
text("score: " + round(score),width/2,55);
// Display the obstacles
for (int i=0; i<firelist.size(); i++){
firelist.get(i).show();
firelist.get(i).move();
// check if avatar touches an obstacle
if (firelist.get(i).checkfail(avatar.ycoord)){
dead=true;
}
// remove the obstacles that are not displayed
if (firelist.get(i).xcoord <-70){
firelist.remove(i);
}
}
// Display the obstacles
for (int i=0; i<birdlist.size(); i++){
birdlist.get(i).show();
birdlist.get(i).move();
// check if avatar touches an obstacle
if (birdlist.get(i).checkfail(avatar.ycoord)){
dead=true;
}
// remove the obstacles that are not displayed
if (birdlist.get(i).xcoord <-70){
birdlist.remove(i);
}
}
// If replay button is clicked, reset the game
if (reset==true){
back.amp(1); // unmute the music
dead=false;
start=true;
reset=false;
score=0; // reset the score
// reset the obstacles list
firelist = new ArrayList<Fire>();
birdlist = new ArrayList<Bird>();
}
if (dead==true){
// stop the parallax
x6++; x6_2++;
x5+=2; x5_2+=2;
x4+=3; x4_2+=3;
x3+=3; x3_2+=3;
x2+=4; x2_2+=4;
x1+=5; x1_2+=5;
// stop the obstacles animation
for (int i=0; i<firelist.size(); i++){
firelist.get(i).xcoord +=5;
}
for (int i=0; i<birdlist.size(); i++){
birdlist.get(i).xcoord +=10;
}
// enable the dying animation
avatar.state=3;
// display the replay button
imageMode(CENTER);
image(startmenu,width/2, height/2-20);
text("REPLAY",width/2,height/2+7-20);
imageMode(CORNER);
}
}
// display the lobby menu
else if (start==false){
// mute the music
back.amp(0);
menu();
image(main,0,0);
imageMode(CENTER);
textAlign(CENTER);
textSize(30);
// display the ui
image(startmenu,width/2, height/2-20);
text("PLAY",width/2,height/2+7-20);
image(helpmenu,width/2, height/2+100-20);
text("HELP",width/2,height/2+100+7-20);
image(title,width/2, 100);
noFill();
rect(width/2-75,height/2-55,150,70);
rect(width/2-90,height/2+40,180,80);
imageMode(CORNER);
}
}
Shapes:
I have used 4 blinking rectangles to add some aesthetics to the title.
To detect when the user clicks on a key, I used both keyPressed() and keyReleased() functions for keyboard, and mouseClicked.
void mouseClicked(){
// if player clicks on start
if ((mouseX>width/2-75) && (mouseX<width/2+75) && (mouseY>height/2-55) && (mouseY<height/2+15)){
start=true;
menu.play();
}
// if player clicks on help
if ((start==false) && (mouseX>width/2-90) && (mouseX<width/2+90) && (mouseY>height/2+40) && (mouseY<height/2+120)){
help=true;
menu.play();
}
// if player clicks on back
else if ((help==true) && (mouseX>width/2-30) && (mouseX<width/2+30) && (mouseY>height/2-230) && (mouseY<height-170)){
help=false;
menu.play();
}
// if player clicks on replay
else if ((dead==true) && (mouseX>width/2-75) && (mouseX<width/2+75) && (mouseY>height/2-55) && (mouseY<height/2+15)){
reset=true;
menu.play();
}
}
void keyPressed(){
// Jump
if (keyCode==UP && dead==false){
avatar.jump();
}
// Crouch
if (keyCode==DOWN && dead==false){
avatar.crouching=true;
avatar.crouch();
}
}
void keyReleased(){
// stop crouching
if (keyCode==DOWN && dead==false){
avatar.crouching=false;
}
}
SFX:
Concerning the sound effects, I have only used two main ones (the background music, and the click sound effect). Instead of stopping the music, I mute it, then unmute it when the game starts.
SoundFile menu; // Click sound effect
SoundFile back; // background music
// Load the sound effects
menu = new SoundFile(this, "assets/menu.wav");
back = new SoundFile(this, "assets/back.mp3");
// Play the background music
back.play();
// Loop the background music
back.loop();
// unmute the music if alive
if (dead==false) {back.amp(1);}
// mute the music if dead
else {back.amp(0);}
Score:
The score gets incremented when the avatar is moving by O.O5 per frame and is displayed on the top-center of the display window. It gets reset when the game restarts.
// increment the score
if (!dead) {score+=0.05;}
End of the game:
When the avatar touches an obstacle, the dying animation is enabled and freezes at the last sprite, and all the other animations/motions are stopped. The score is displayed on top, and the replay button appears.
Finally, after all the ranting and hours of crying, the midterm game project is complete and it looks and works great, as intended, who am I kidding even. To summarize the game, the theme is around stress struggles as a student. To make it more personalized, the student is an NYUAD falcon. The game design includes free-falling stressful and stress-relieving activities that the player has to avoid and collect respectively while moving across the screen horizontally. It is a race between sanity and stress. The first total to reach 320 points wins. There are four types of free-falling activities that are worth a different number of points and allows the player certain boosted or dulled abilities. The game starts with the main page that allows the player to either start the game directly or navigate to the instructions screen. The game has three different difficulty levels that the player has to choose from. As the game concludes, a game over screen is displayed with results and the option to restart the game with a mouse press.
Production
This production process has been by far the most stressful, ironic right. With the code breaking down so many times, I started the initial game design from scratch with circles as the objects and incorporated the movement. See the initial sketch below.
Once the intended movement was achieved, building upon previous progress, I selected my images to use and incorporated them into the previous sketch. Finally, as shown below, the basic design of the game was complete.
The following steps included working more on the game outlook. Putting my artsy skills to use was real fun while coloring the free-falling objects png files. To have a better outlook, I used two images for the player, flipped left and right, which were chosen based on the movement of the player on the screen.
After completing this, the main landing screen, instructions screen, main gameplay screen, and game over screen were redesigned. For this, I used different background images, fonts, and text placements. Like most sketch screens, I wanted to have a faded image background during the main gameplay screen. However, with png moving down the screen, the background would distort, and also the increased loading time of the screen made the movement of objects weird and much less smooth. Therefore, after trying several different patterns and backgrounds, I just stick to a solid background. This came out looking pretty good design and allowing the moving objects’ color to pop on screen. Below are screenshots of the final designs.
Once all of the design was finalized, the last step was to include sound. There were challenges with different sounds playing at a time, ending up in weird noise. So, I finalized a single background sound looped until the game over screen and whenever a selection is made using mouse press or keypress, a notification sound is played. With this, I finalized my game design, and below is a demo
Code for the game is below (+500 lines) and the entire sketch can be downloaded from here
import processing.sound.*;
//Global variables responsible for various states in game
//That includes: difficulty select, playing game, game over
Game start;
Player player;
String state = "MAIN";
String difficulty = "EASY";
//array to store the different points
Activity[] activityarray;
//all the images and fonts for design
PImage food;
PImage leisure;
PImage deadlines;
PImage grades;
PImage user;
PImage userr;
PImage bdrop;
PImage miss;
PImage bg;
PImage diff;
PImage inst;
PImage keys;
PImage back;
PFont f;
PFont g;
//global variables for location check and size
float speed = 1;
float previouspos = 0;
//soundfile
SoundFile audio;
SoundFile sel;
//setup the game
void setup(){
fullScreen();
//initializing game and player
start = new Game(state);
player = new Player();
//loading images
food =loadImage("data/food.png");
leisure = loadImage("data/leisure.png");
deadlines = loadImage("data/deadline.png");
grades = loadImage("data/test.png");
user = loadImage("data/user.png");
userr = loadImage("data/userr.png");
bg = loadImage("data/bg.jpg");
diff = loadImage("data/diff.jpg");
bdrop = loadImage("data/backdrop.jpg");
inst = loadImage("data/inst.jpg");
keys = loadImage("data/key.png");
miss = loadImage("data/stress.png");
back = loadImage("data/backdrop.jpg");
back.resize(width,height);
//loading sound
audio = new SoundFile(this, "data/game.mp3");
audio.loop();
sel = new SoundFile(this, "data/select.mp3");
//font
f = loadFont("FootlightMTLight-55.vlw");
g = loadFont("JavaneseText-55.vlw");
//diffculty level
if(difficulty == "EASY"){
speed = 2;
}
if(difficulty == "MEDIUM"){
speed = 4;
}
if(difficulty == "HARD"){
speed = 8;
}
activityarray = new Activity[250];
//initializing the array of activity object
fillarray();
}
void draw(){
//image(back,0,0,width,height);
start.display();
}
//fill activity array
void fillarray(){
//choose random x values and drop activities
for(int i=0; i<activityarray.length; i++){
float x_pos = random(0,width-30);
while(x_pos == previouspos){
x_pos = random(0,width-30);
}
previouspos = x_pos;
float type = random(1,50);
String activity_ability = "FOOD";
//randomly choosing ability based on random number
if(type < 5){
activity_ability = "LEISURE";
}
else if(type > 45){
activity_ability = "GOODGRADES";
}
if(type > 10 && type <=25){
activity_ability = "DEADLINE";
}
//create new activity object
activityarray[i] = new Activity(x_pos,random(-10000,0),speed,activity_ability);
}
}
//game class
class Game{
String mode;
int score = 0;
int lostpoints = 0;
float speed = 0;
Game(String state){
mode = state;
}
void display(){
textAlign(CENTER,CENTER);
//check for game over
if(lostpoints >= 320 || score >= 320){
mode = "OVER";
audio.stop();
}
//load the main landing page
if(mode=="MAIN"){
//background gradient
background(255);
loadPixels();
for(int y=0;y<height;y++){
for(int x=0;x<width; x++){
int index = x+y*width;
pixels[index] = color(map(y,0,height,155,0),0,map(y,0,height,255,0),50);
}
}
updatePixels();
//displaying text
textFont(f,random(90,97));
fill(150,120);
text("STRESS FREE NYUAD?",width/2,height/3);
fill(255);
textFont(f,90);
text("STRESS FREE NYUAD?",width/2,height/3);
textFont(g,30);
fill(255);
text("START GAME - G",width/2,2*height/3 - height/14);
text("INSTRUCTIONS - I",width/2,2*height/3);
textFont(f,20);
text("Press corresponding keys to initiate the game",width/2,height-height/14);
}
//loading the instructions page
if(mode=="INSTRUCTIONS"){
fill(0);
image(inst,0,0,width,height);
textFont(f,60);
text("INSTRUCTIONS",width/2,height/10);
//display images and text
image(keys,width/4-width/7,height/2.5 - height/8,130,60);
image(food,width/2-130/2,height/2.5 - height/8,130,60);
image(grades,3*width/4+width/16,height/2.5 - height/7,130,80);
image(leisure,width/4-width/7,1.75*height/2.5 - height/8,130,60);
image(deadlines,width/2-130/2,1.75*height/2.5 - height/8,130,60);
image(miss,3*width/4+width/16,1.75*height/2.5 - height/8,130,60);
textFont(g,22);
text("Use arrow L-R keys to\nmove across the screen",width/4-width/10,height/2.5);
text("Eat healthy to gain +05 pts",width/2,height/2.5);
text("Perform well on assignment\n to get +10 pts ",3*width/4+width/10,height/2.5);
text("Do refresing leisure activities\n to get boosted speed",width/4-width/10,1.75*height/2.5);
text("Avoid deadline pressure\nto skip decreased speed",width/2,1.75*height/2.5);
text("Missed pts added to stress level\nFirst to reach 320 pts win",3*width/4+width/10,1.75*height/2.5);
textFont(g,random(32,34));
fill(random(255),0,0);
text("START GAME - G",width/2,9*height/10);
}
//displaying the difficulty selection screen
if(mode=="DIFFICULTY"){
image(diff,0,0,width,height);
//turn the text red and create shadow effect when mouse is hovered
//over the level selection part
if(mouseX>0 && mouseX<width/3){
fill(255,0,0);
textFont(g,54);
text("EASY - E",width/4-width/10,height/2);
}
else if(mouseX>width/3 && mouseX<2*width/3){
fill(255,0,0);
textFont(g,54);
text("MEDIUM - M",width/2,height/2);
}
else if(mouseX>2*width/3 && mouseX<width){
fill(255,0,0);
textFont(g,54);
text("HARD - H",3*width/4+width/10,height/2);
}
fill(255);
textFont(g,50);
text("EASY - E",width/4-width/10,height/2);
text("MEDIUM - M",width/2,height/2);
text("HARD - H",3*width/4+width/10,height/2);
textFont(f,20);
text("Press corresponding keys to initiate the game",width/2,height-height/14);
}
//game over screen
if(mode=="OVER"){
//display the background image
fill(255);
image(bg,0,0,width,height);
//display the text
textFont(f,60);
text("GAME OVER",width/2, height/3);
textFont(f,35);
text("Your Score:",width/2 - width/6, height/3 +height/6);
text("Stress Level:",width/2 - width/6, height/3 + height/4);
textFont(g,45);
text(score,width/2, height/3 +height/6);
text(lostpoints,width/2, height/3 + height/4);
textFont(f,35);
text("points",width/2 + width/6, height/3 +height/6);
text("points",width/2 +width/6, height/3 + height/4);
//display result string based on scores
textFont(g,45);
if(score>=lostpoints){
if(score==lostpoints){
text("IT'S A TIE", width/2, 2.25*height/3);
}
else{
text("YOU WON", width/2, 2.25*height/3);
}
}
else{
text("YOU LOST", width/2, 2.25*height/3);
}
textFont(f,20);
text("Please click on screen to restart game",width/2,height-height/14);
}
//main game screen
if(mode=="PLAY"){
//background color
background(back);
fill(0);
//score board
rect(width-width/6,height/14.5,width/8,height/13.5);
fill(255);
textFont(g,18);
text("Your:", width-width/7,height/12);
text("Stress:", width-width/7,height/8);
textFont(f,random(24,26));
fill(random(200,255));
text(score,width-width/10,height/12);
text(lostpoints,width-width/10,height/8);
fill(255);
textFont(g,18);
text("points", width-width/16.5,height/12);
text("points", width-width/16.5,height/8);
//main display player
player.display();
//display activities
for(int i =0; i< activityarray.length;i++)
{
activityarray[i].display();
if(activityarray[i].yloc > height){
lostpoints += activityarray[i].point;
activityarray[i].point = 0;
}
//resize the image upon collision to have effect
//of collecting the activity
if(activityarray[i].collisions() == true){
score += activityarray[i].point;
activityarray[i].awidth = 0;
activityarray[i].aheight = 0;
activityarray[i].point =0;
}
}
}
}
}
//player class
class Player{
float pwidth;
float pheight;
float xPos;
float yPos;
boolean left;
boolean right;
float speed;
float fast_time;
float slow_time;
Player(){
pwidth= 100;
pheight = 100;
xPos = width/2 - pwidth;
yPos = height - pheight;
left = false;
right = false;
speed = 7;
fast_time = 0;
slow_time = 0;
}
void display(){
//tracking the time when boosted speed
if(speed == 12){
fast_time += 1;
//last 100 frames
if(fast_time == 100){
fast_time = 0;
speed = 7;
}
}
//tracking the time when slowed speed
if(speed == 1){
slow_time += 1;
//last 100 frames
if(slow_time == 100){
slow_time = 0;
speed = 7;
}
}
//update the position on screen
update();
//draw the player
if(left==true){
image(user,xPos,yPos,pwidth,pheight);
}
else if(right==true){
image(userr,xPos,yPos,pwidth,pheight);
}
else{
image(userr,xPos,yPos,pwidth,pheight);
}
}
//update the position of the player
void update(){
if(left==true && xPos >=0){
xPos -= speed;
}
if(right==true && xPos <= width-pwidth){
xPos += speed;
}
}
}
//Class of falling activities/ points
class Activity{
float awidth = 60;
float aheight = 60;
//coordinates
float yloc;
float xloc;
float speed;
String ability;
//standard point
int point = 5;
//image
PImage activityimg;
Activity(float xpos, float y,float s, String a){
xloc = xpos;
speed= s;
yloc = y;
ability = a;
//updating point values and image based on type
if(ability == "GOODGRADES"){
activityimg = grades;
point = 10;
}
else if(ability == "LEISURE"){
activityimg = leisure;
}
else if(ability == "DEADLINE"){
point = 0;
activityimg = deadlines;
}
else{
point =5;
activityimg = food;
}
}
//display the activity object
void display(){
update();
image(activityimg,xloc,yloc,awidth,aheight);
}
//update the locations
void update(){
//move down
yloc += speed;
}
//check for collisions
boolean collisions(){
if((player.xPos + player.pwidth >= xloc) && (player.xPos <= xloc + awidth)){
if((yloc + aheight >= player.yPos) && (yloc <= player.pheight + player.yPos)){
//check if it collides with special activity and update speed accordingly
if(ability == "LEISURE"){
player.speed = 12;
}
if(ability == "DEADLINE"){
player.speed = 1;
}
return true;
}
}
return false;
}
}
//keep track of key presses on screen
void keyPressed(){
if(start.mode == "MAIN"){
if(keyCode == 73){ //73 = 'i'
sel.play();
start.mode = "INSTRUCTIONS";
}
if(keyCode == 71){ //71 = 'g'
sel.play();
start.mode = "DIFFICULTY";
}
}
if(start.mode == "INSTRUCTIONS"){
if(keyCode == 71){ //71 = 'g'
sel.play();
start.mode = "DIFFICULTY";
}
}
if(start.mode == "DIFFICULTY"){
if(keyCode == 69){ //71 = 'e'
sel.play();
difficulty = "EASY";
start.mode = "PLAY";
}
if(keyCode == 77){ //71 = 'm'
sel.play();
difficulty = "MEDIUM";
start.mode = "PLAY";
}
if(keyCode == 72){ //71 = 'h'
sel.play();
difficulty = "HARD";
start.mode = "PLAY";
}
}
//move until key pressed
if(start.mode=="PLAY"){
if(keyCode == RIGHT){
player.right = true;
}
if(keyCode == LEFT){
player.left = true;
}
}
}
//stop motion when key is released
void keyReleased(){
if(start.mode == "PLAY"){
if(keyCode == RIGHT){
player.right = false;
}
if(keyCode == LEFT){
player.left = false;
}
}
}
//replay the game
void mouseClicked(){
if(start.mode=="OVER"){
sel.play();
player.left = false;
player.right = false;
fillarray();
background(0);
start = new Game("MAIN");
audio.play();
}
}
The game that I made for the midterm project was definitely a product of a lot of ideas and concepts that were discussed in the class – not just pertaining to the environment of processing itself. As a CS major, I had done a bit of Processing for my Intro to CS class, but this time around, I approached making a game on Processing differently – and the biggest difference was I was actively thinking about whether the game was designed to make sense to a player – whereas back then I was concentrating on achieving functionality.
My game, in a nutshell, can be thought of as snake and ladders but instead of the snakes and ladders, there are cards that describe the number of steps you move back and forth. The theme I chose was my state in India – Kerala and it can be seen in the tokens – Theyyam and Kathakali (both of which are art forms in Kerala) and the cards. The cards are things that are related to Kerala. Here are the cards:
Implementation
I took a step-by-step approach and broke down the game into smaller units and then tied them together. In the last week, I worked on the rolling die, the grid, and the tokens. This week, I started on improving the design of the grid. The one I had before was a grid with 121 squares starting on the top left and ending on the bottom right. I wasn’t satisfied with these specifications – the idea of reaching a 100 had more of a victorious effect than 121. Also, traditionally, most of these games begin at the bottom left and end on the top left. I tried playing through a game with the original specifications, but knowledge of previous games made it hard to play, so I decided to match the specifications. Moreover, the numbers were displayed in n order, so when the next line comes, it starts from the left again, but with the movement of the tokens, this path is very abrupt and cut off. Making these changes was very confusing, especially alternating between going from left to right and then vice-versa, but finally, I used the (%2==0) condition and alternated to get the left to right and right to left display. Also, I changed the colors of the squares from just black and alternated between shades of black, to provide more definition to the individual squares.
Then, I worked on picking random squares on each row to have a colored square that would represent a card. This was very easy to implement, and I made an array of colors and randomized which color to pick for each game from this array, and the position of the squares is random for each game as well.
After this, I worked on moving the tokens and recording what the die returned on rolling. Further, I had to add the functionality of moving when arriving on a colored square and then showing the card when the token arrives at the colored square. This was definitely the hardest part to figure out – implementation-wise and design-wise. I went through a lot of ways to implement this and figuring out how the token should move, when should it move, should there be triggers, etc. I picked the version that seemed the simplest of the bunch. The player rolls the die and the token moves according to the die number, and this is automatic. If they happen to arrive at a colored square, the extra steps are also accounted for in the movement of the token, and the card is displayed on the side to make sense of the movement. In order to understand whose turn it is, the token is displayed on the side of whose turn it is.
Rolling of the die and coordinating that with the turns of the user was confusing. I had a global variable to keep track of the turns. Figuring out the logic behind it took some time though. Also, I found out that the number of steps the tokens take the first time around is always -1 the number of steps they are supposed to take, so I made an if condition to check if it is the first time they are moving and take that into account as well. The explanation I have for it is that the number displayed starts at 1 and the internal number starts at 0, and I created the program handling both, so I changed that. Finally, the player gets to move, only when a 1 or 6 comes up, so I had a global variable to indicate whether they are on the board yet.
Then, I designed the beginning and end slides of the game which was fairly easy to implement.
The functionality of the restart button took some time to figure out, the core of the issue was that many of my global variables needed to be reinitialized too, not just those in the setup(). But once I figured that out, all was done. The final addition was the sound! I added an evil laugh effect every time the player enters a colored square that makes them move back and a “woohoo” effect for when they progressed. I added some instructions on the game board as well, just in case the player chooses to skip the instructions on the welcome slide, they’ll not be completely confused during the game. A lot of the things I incorporated in the game design-wise were from feedback from family and friends playing the game. They noticed things that seemed very obvious to me (for example, you have to double click on the dice to stop it). So, yeah, it was a cool learning experience.
Here’s a demo of the game
//sound
import processing.sound.*;
SoundFile[] soundFiles = new SoundFile[2];
//images
PImage kathakali, kToken, kathakaliWon;
PImage theyyam, tToken, theyyamWon;
PImage power, filter, daagini, mosquito, dosa, coconut, houseboat, mahabali, traffic, sadya;
PImage welcome;
Rect rect[];
int size = 82;
int diceX = (100*width/8)+25;
int diceY =(60*height/8)+10;
int diceSize = 90;
boolean toggleRun=false;
boolean gameMode = false;
int yOffset = 35;
int xOffset = 40;
//color palette
color c1=#ffc097;
color c2=#ffee93;
color c3=#fcf5c7;
color c4=#aeced9;
color c5=#adf7b6;
int die = 0;
color[] palette = {c1, c2, c3, c4, c5};
//game logisitics
int turn=-1;
int token=0;
Token token1, token2;
boolean card=false;
int cardButton=0;
int start1;
int start2;
boolean celebrate = false;
boolean start=false;
void setup() {
fullScreen();
soundFiles[0] = new SoundFile(this, "evil.wav");
soundFiles[1] = new SoundFile(this, "woohoo.wav");
theyyam = loadImage("theyyam.png");
tToken = loadImage("theyyam.png");
theyyam.resize(220, 260);
tToken.resize(88, 104);
theyyamWon = loadImage("theyyamWon.jpg");
kathakali = loadImage("kathakali.png");
kToken = loadImage("kathakali.png");
kathakali.resize(180, 224);
kToken.resize(72, 90);
kathakaliWon = loadImage("kathakaliWon.jpg");
power = loadImage("power.jpg");
filter = loadImage("filter.jpg");
daagini = loadImage("daagini.jpg");
mosquito = loadImage("mosquito.jpg");
dosa = loadImage("dosa.jpg");
coconut = loadImage("coconut.jpg");
houseboat = loadImage("houseboat.jpg");
mahabali = loadImage("mahabali.jpg");
traffic = loadImage("traffic.jpg");
sadya = loadImage("sadya.jpg");
welcome = loadImage("WELCOME.jpg");
background(0);
image(welcome, 0, 40);
}
void draw() {
if (start)
{
//when restart, show image of dice, so that player can click on it to begin game
if (celebrate)
{
fill(#FFF3D6);
rectMode(CENTER);
rect(diceX, diceY, diceSize, diceSize, diceSize/5);
//dots
fill(50);
ellipse(diceX, diceY, diceSize/5, diceSize/5);
rectMode(CORNER);
celebrate=false;
}
if (toggleRun)
{
die=dice();
}
for (int j=0; j<rect.length; j++) {
rect[j].display();
}
if (token1.mode)
{
token1.display();
}
if (token2.mode)
{
token2.display();
}
if (((die==1) || (die ==6)) && ((!toggleRun)))
{
if (token==0 && !token1.mode)
{
token1.start();
}
if (token==1 && !token2.mode)
{
token2.start();
}
}
fill(0);
noStroke();
rect(0, 0, 285, 800);
fill(0);
noStroke();
rect(diceX-diceSize, diceY-diceSize, 200, 30);
fill(255);
text("Double click on the dice to roll it", diceX-diceSize, diceY-diceSize+20);
text("Goal: Reach Square 100", 60, 600);
text("Roll die and move accordingly, movement", 20, 620);
text(" changes if there's a card involved on the square ", 0, 640);
text("you land.", 100, 660);
text("Each player needs to roll a one or a six", 30, 300);
text("to begin their game ", 80, 320);
if (turn==-1)
image(kathakali, 35, height/2-100);
else
{
if (token==0)
image(theyyam, 25, height/2-100);
else
image(kathakali, 35, height/2-100);
}
if (token1.num>=99)
{
token1.celebrate();
celebrate = true;
}
if (token2.num>=99)
{
token2.celebrate();
celebrate = true;
}
}
}
//grid print
void grid() {
int xlen = (width)/size ;
int ylen = (height)/size;
rect = new Rect[(xlen-7)*(ylen)];
int i=99;
color clr;
int card;
int cardNum=0;
for (int y=0; y < ylen; y++) {
cardNum++;
int rand = int(random(4, xlen-5));
if (y%2==0)
{
for (int x =3; x < xlen-4; x++) {
card=0;
PVector p = new PVector(x*size+xOffset, y*size+yOffset);
int j = y%2;
if ((i+j)%2==0)
clr=color(40);
else
clr=color(0);
if (x==rand)
{
clr=palette[int(random(5))];
//clr=color(247,221,123);
card=1;
}
rect[i] = new Rect(p, size, i, clr, card, cardNum);
i--;
}
} else
{
for (int x =xlen-4; x >3; x--) {
card=0;
PVector p = new PVector(x*size+xOffset-size, y*size+yOffset);
int j = y%2;
if ((i+j)%2!=0)
clr=color(40);
else
clr=color(0);
if (x==rand)
{
clr=palette[int(random(5))];
//clr=color(247,221,123);
card=1;
}
rect[i] = new Rect(p, size, i, clr, card, cardNum);
i--;
}
}
}
}
int dice() {
fill(255);
fill(#FFF3D6);
rectMode(CENTER);
rect(diceX, diceY, diceSize, diceSize, diceSize/5);
//dots
fill(50);
int side = int(random(1, 7));
if (side == 1 || side == 3 || side == 5)
ellipse(diceX, diceY, diceSize/5, diceSize/5);
if (side == 2 || side == 3 || side == 4 || side == 5 || side == 6) {
ellipse(diceX - diceSize/4, diceY - diceSize/4, diceSize/5, diceSize/5);
ellipse(diceX+ diceSize/4, diceY + diceSize/4, diceSize/5, diceSize/5);
}
if (side == 4 || side == 5 || side == 6) {
ellipse(diceX - diceSize/4, diceY + diceSize/4, diceSize/5, diceSize/5);
ellipse(diceX + diceSize/4, diceY- diceSize/4, diceSize/5, diceSize/5);
}
if (side == 6) {
ellipse(diceX, diceY- diceSize/4, diceSize/5, diceSize/5);
ellipse(diceX, diceY + diceSize/4, diceSize/5, diceSize/5);
}
rectMode(CORNER);
return side;
}
void mousePressed() {
color red = color(255, 0, 0);
color yellow = color(255, 255, 0);
if (start) {
if (start1!=-1||start2!=-1)
{
fill(0);
rect(1160, 450, 250, 70, 50 );
}
PVector p = new PVector(0, 0);
if (mouseX>(diceX)-(diceSize/2) && mouseX<(diceX)+(diceSize/2) && mouseY>(diceY)-(diceSize/2) && mouseY<(diceY)+(diceSize/2 ))
{
toggleRun=!toggleRun;
turn+=1;
fill(0);
rect(1160, 100, 281, 450);
if ((turn%2==0))
{
token=1-token;
} else
{
if (token==1 && token2.mode)
{
if (token2.num>99) {
token2.num=99;
}
if (token2.num<0) {
token2.num=0;
}
token2.num +=die;
for (int i=0; i<rect.length; i++)
{
if (rect[i].num==token2.num)
{
if (start1<0)
{
p = rect[i-1].position;
token2.move(p);
token2.num=rect[i-1].num;
if (rect[i-1].card==1)
{
showCards(rect[i-1].cardNum, yellow, token2);
cardButton=2;
}
break;
}
p = rect[i].position;
token2.move(p);
if (rect[i].card==1)
{
showCards(rect[i].cardNum, yellow, token2);
cardButton=2;
}
break;
}
}
start1++;
}
if (token==0 && token1.mode)
{
token1.num +=die;
if (token1.num>99) {
token1.num=99;
}
if (token1.num<0) {
token1.num=0;
}
for (int i=0; i<rect.length; i++)
{
if (rect[i].num==token1.num)
{
if (start2<0)
{
p = rect[i-1].position;
token1.move(p);
token1.num=rect[i-1].num;
if (rect[i-1].card==1)
{
showCards(rect[i-1].cardNum, red, token1);
cardButton=1;
}
break;
}
p = rect[i].position;
token1.move(p);
if (rect[i].card==1)
{
showCards(rect[i-1].cardNum, red, token1);
cardButton = 1;
}
break;
}
}
start2++;
}
}
}
if ((token2.num>=99||token1.num>=99) && celebrate)
{
//reset
if (mouseX>360 && mouseX<660 && mouseY>459 && mouseY<542)
{
restart();
}
//exit
if (mouseX>719 && mouseX<982 && mouseY>459 && mouseY<542)
{
exit();
}
}
}
if (!start)
{
if (mouseX>121 && mouseX<1319 && mouseY>742 && mouseY<782)
{
start=true;
restart();
}
}
}
void showCards(int cardNum, color c, Token token)
{
PVector p = new PVector(0, 0);
switch(cardNum)
{
case 1:
fill(c);
token.num -=5;
soundFiles[0].play();
image(power, 1160, 100);
break;
case 2:
fill(c);
token.num +=8;
soundFiles[1].play();
image(filter, 1160, 100);
break;
case 3:
fill(c);
token.num -=8;
image(daagini, 1160, 100);
soundFiles[0].play();
break;
case 4:
fill(c);
token.num -=3;
image(mosquito, 1160, 100);
soundFiles[0].play();
break;
case 5:
fill(c);
token.num +=10;
soundFiles[1].play();
image(dosa, 1160, 100);
break;
case 6:
fill(c);
token.num -=10;
image(coconut, 1160, 100);
soundFiles[0].play();
break;
case 7:
fill(c);
token.num +=12;
soundFiles[1].play();
image(houseboat, 1160, 100);
break;
case 8:
fill(c);
token.num +=3;
soundFiles[1].play();
image(mahabali, 1160, 100);
break;
case 9:
fill(c);
token.num -=3;
image(traffic, 1160, 100);
soundFiles[0].play();
break;
case 10:
token.num +=5;
soundFiles[1].play();
image(sadya, 1160, 100);
break;
}
if (token.num>99) {
token.num=99;
}
if (token.num<0) {
token.num=0;
}
for (int i=0; i<rect.length; i++)
{
if (rect[i].num==token.num)
{
token.position = rect[i].position;
break;
}
}
}
void restart()
{
background(0);
toggleRun=false;
gameMode = false;
die = 0;
turn=-1;
token=0;
card=false;
cardButton=0;
start1=-1;
start2=-1;
token1 = new Token(tToken, theyyam, theyyamWon);
token2 = new Token(kToken, kathakali, kathakaliWon);
grid();
dice();
}
Disclaimer: The components of the art used in this game are done by other artists and some of them by me, I worked on them on illustrator for the game.
There is a Taiwanese myth about catching fallen stars to make a wish come true. I intend to create a game that consists of a traditional Taiwanese bamboo woven basket and white stars falling from the night sky.
RULES:
The way to win is to collect 66 stars (white). The players would lose one life if hit by fallen satellite remains (red). They have three lives in total and loses the game when all three lives are wasted. However, players can catch Taiwanese street food (green) to restore lost lives.
OBJECTS:
Background: import an image of the Taiwanese famous night sky of lanterns
Sound: Background music – summer night time in Taiwanese mountains
The hardest part was the collision detection. It was hard to wrap my head around the logic. For the satellite remains, player lives should have decrement one when one satellite hits, but for some reason, perhaps the boundary setting or the collision content, player lives decrease ridiculously fast. However, sometimes it works perfectly. I am still in the process of figuring it out.
Slight mistake: not clarifying how to play the game (what key to press)
CODES:
1. Main Function
PImage sky;
PImage stinkyTofu;
PImage sausage;
PImage oysterOmlette;
//another variable name to create another object
Fall [] fallStars;
Fall [] fallGood;
Fall [] fallBad;
import processing.sound.*;
SoundFile bgMusic;
//SoundFile winMusic;
//SoundFile loseMusic;
//setup the basket
int stage;
float basketWidth = 100;
float basketHeight = 50;
float X = 384;
float Y = 723;
color basketColor = color(188, 123, 25);
int lives = 3;
int stars = 0;
boolean goLeft = false;
boolean goRight = false;
void setup() {
size (1024, 768);
bgMusic = new SoundFile (this, "/Users/chi-tingtsai/Downloads/summer night.mp3");
bgMusic.loop();
//winMusic = new SoundFile (this, "/Users/chi-tingtsai/Documents/Processing/Intro_to_IM_Midterm_ver2/data/win.mp3");
//loseMusic = new SoundFile (this, "/Users/chi-tingtsai/Documents/Processing/Intro_to_IM_Midterm_ver2/data/lose.mp3");
stage = 0;
//setup the falling objects
fallStars = new Fall[10];
for (int k=0; k<fallStars.length; k++) {
fallStars[k] = new Fall(random(width), 0, random(2, 7), color(255));
}
fallGood = new Fall[5];
for (int i=0; i<fallGood.length; i++) {
fallGood[i] = new Fall(random(width), 0, random(1, 5), color(94, 240, 134));
}
fallBad = new Fall[15];
for (int l=0; l<fallBad.length; l++) {
fallBad[l] = new Fall(random(width), 0, random(6, 10), color(234, 83, 131));
}
}
//EXECUTION
void draw () {
if (stage == 0) {
menu();
}
if (stage == 3) {
playGame();
}
}
void keyReleased() {
goLeft = false;
goRight = false;
if (key == ' ' && stage == 0) {
stage = 1;
story();
} else if (key == ' ' && stage == 1) {
stage = 2;
rules();
} else if (key == ' ' && stage == 2) {
stage = 3;
} else if (key == ' ' && stage == 3) {
for (int k=0; k<fallStars.length; k++) {
fallStars[k].restart();
}
for (int i=0; i<fallGood.length; i++) {
fallGood[i].restart();
}
for (int l=0; l<fallBad.length; l++) {
fallBad[l].restart();
}
}
}
2. MENU FUNCTION:
color frame = color(237, 168, 17);
void menu() {
background(color(99, 144, 130));
fill(255);
textAlign(CENTER);
PFont F = createFont("PatuaOne-Regular", 1);
//explain how to start
textFont(F, 30);
text("Press Space Bar to Begin Game", width/2, height*3/4);
//explain this game
textFont(F, 50);
text("CATCHING FALLEN STARS\n(TAIWANESE VERSION)", width/2, height/2-50);
//frame
noStroke();
rectMode(CENTER);
fill(frame);
rect(width/2, 10, width, 20);
rect(width/2, height-10, width, 20);
rect(10, height/2, 20, height);
rect(width-10, height/2, 20, height);
}
3. STORY:
int rectWidth = width/10;
int rectHeight = height/10;
float bg, up, mid, down;
void story() {
background(color(99, 144, 130));
fill(255);
textAlign(CENTER);
PFont F = createFont("PatuaOne-Regular", 1);
//explain how to start
textFont(F, 50);
text("STORY", width/2, height/4);
textFont(F, 30);
text("A long time ago,\nDeep in the mountains of Yu and Ali,\nThere was a lake called Sun Moon Lake.\nRumor has it that if you collected enough fallen stars\nDuring Mid Autumn Festival,\nYour fears will disipate,\nInsecurities will calm,\nAnd peace will shine on you.", width/2, height/2-120);
//frame
noStroke();
rectMode(CENTER);
fill(frame);
rect(width/2, 10, width, 20);
rect(width/2, height-10, width, 20);
rect(10, height/2, 20, height);
rect(width-10, height/2, 20, height);
//Next to Rules
textFont(F, 40);
text("Press Space Bar to Continue", width/2, height*3/4+75);
}
4. RULES:
int size = 200;
void rules() {
stinkyTofu = loadImage("stinkytofu.jpeg");
stinkyTofu.resize(size, size);
sausage = loadImage("sausage.jpeg");
sausage.resize(size, size);
oysterOmlette = loadImage("oysteromlette.jpeg");
oysterOmlette.resize(size-30, size-30);
background(color(99, 144, 130));
fill(255);
textAlign(CENTER);
PFont F = createFont("PatuaOne-Regular", 1);
//display images of food
image(stinkyTofu, 30, 30);
image(sausage, 784, 30);
image(oysterOmlette, width-200, height - 200);
//explain how to play
textFont(F, 50);
text("RULES", width/2, height/4);
textFont(F, 30);
text("The way to win is to catch 66 stars(white)\nwith you bamboo woven basket\nYou have three lives in total.\nYou lose one life if hit by a satellite (red).\nYou lost the game when all lives are wasted.\nHowever, you can catch Taiwanese street food(green)\nto restore lost lives.", width/2, height/2-60);
//frame
noStroke();
rectMode(CENTER);
fill(frame);
rect(width/2, 10, width, 20);
rect(width/2, height-10, width, 20);
rect(10, height/2, 20, height);
rect(width-10, height/2, 20, height);
}
5. PLAY GAME:
void playGame() {
sky = loadImage("pinxi sky lantern.jpeg");
sky.resize(width, height);
PFont F = createFont("PatuaOne-Regular", 1);
noStroke();
//background
background (255);
image (sky, 0, 0);
//objects start falling
for (int k=0; k<fallStars.length; k++) {
fallStars[k].runFall();
}
for (int i=0; i<fallGood.length; i++) {
fallGood[i].runFall();
}
for (int l=0; l<fallBad.length; l++) {
fallBad[l].runFall();
}
//different kinds of collision
for (int i=0; i<fallStars.length; i++) {
fallStars[i].starcollision();
}
for (int k=0; k<fallGood.length; k++) {
fallGood[k].goodcollision();
}
for (int l=0; l<fallBad.length; l++) {
fallBad[l].badcollision();
}
//player basket
rectMode(CENTER);
fill(basketColor);
rect(X, Y, basketWidth, basketHeight);
if (goLeft == true) {
X -= 15;
}
if (goRight == true) {
X += 15;
}
if (stars == 66 && stage == 3) {
background(color(99, 144, 130));
textFont(F, 80);
fill(255);
text("Congratulations! You have done it!", width/2, height/2);
textFont(F, 50);
text("Have you found your peace?\nOr even more chaotic?\nhehe)", width/2, height/2-50);
textFont(F, 30);
text("Press Space Bar to Restart", width/2, height/2+50);
}
if (lives == 0 && stage == 3) {
background(color(99, 144, 130));
textFont(F, 80);
fill(255);
text("Game Over...", width/2, height/2);
textFont(F, 50);
text("Awwww... Press Space Bar to Restart?", width/2, height/2+50);
}
//show lives to player
textSize(24);
fill(color(90, 167, 247));
text("LIVES:", width -120, height - 50);
text(lives, width - 60, height - 50);
//show lives to player
fill(color(255));
text("STARS:", 90, 60);
text(stars, 160, 60);
//frame
noStroke();
rectMode(CENTER);
fill(frame);
rect(width/2, 10, width, 20);
rect(width/2, height-10, width, 20);
rect(10, height/2, 20, height);
rect(width-10, height/2, 20, height);
}
void keyPressed() {
if (keyCode == LEFT) {
goLeft = true;
}
else if (keyCode == RIGHT) {
goRight = true;
}
if (X <= basketWidth/2 + 10) {
goLeft = false;
X = basketWidth/2;
}
if (X >= width - basketWidth/2 - 10) {
goRight = false;
X = width - basketWidth/2;
}
}
Uni Rush is an innovative idea for gaming in processing which have sketched the idea of a student escaping the reality of university course grades. The basic logic behind it is that the student character is moving in all four directions and the grade letters are coming from the right side of the screen. When a grade passes behind the character, the score increases. However, if the character moves out of the screen or touches any grade, whether bad or good, then it is game over. For the character movements, conventional gaming keyboard keys were used: w for up, a for left, s for down, and d for right.
INSPIRATION BEHIND UNI RUSH:
The inspiration behind the game I wanted to create comes from how overprotective I tend to be with my GPA. Since I was a kid, I always would work to achieve the highest grades and even the slightest deduction of grades would make me cry. However, after I came to university, that reality changed, and it’s been like a roller-coaster with all its ups and downs. The game resembles a university student who is running all over to escape the sad reality of university grades. Based on that, the more grades the character is able to escape , the higher the score is. I have always been keen to understand how gpa calculation works, so I searched it up and decided to base my game on that. Yet, this did not work and the fun of it made me come up with a new game logic, closer to reality.
CHALLENGES & PROBLEMS:
The very first and foremost challenge I have faced was rendering the grades continuously on the screen, in addition to its movement within the specified speed. This has put me in chaos, as I felt that my code is all over the place. A second difficulty was during the movement of the character. Third, the management of frames and the glitches found during frame rendering. Last, but not least, playing the sound based on the movement of the character and rendering the grade letters as a series of image was an obstacle.
OBJECTS USED :
1- Grade letters
The grade letter objects are moving images of all grade letters
2- Student Character
This is an NYU Mascot cartoon character given a bigger size as compared to the grade letters and is able to move left- right & up-down based on the key pressing
3- Game Background
This object is a university campus image
4- GPA Score Prompt
This object is a simple score text prompt which gets added as the character escapes the grade letters.
PROCEDURE:
I have started my game by implementing the player character class. This class was responsible for the actions taken by the character. Later, I have implemented attributes and methods in that class that helps with the movement of the character in all four directions. The rendering of the character is also implemented in that class. I performed the default constructor for the character class which takes x and y positions of the character.
The die function implemented to check if the player character moves out of the screen.
//function to check if the player character is out screen which means the character died
//stopping the game background sound and playing main menu sound
void die()
{
this.xPos = -500;
this.yPos = -500;
this.CharacterDropped = true;
BackSoundEffect.stop();
SkipGradeLetterSound.play();
MainMenuSoundEffect.play();
}
The player character is moving based on the key pressed events which work on the specified keys for the movement of the player character in upward, downward, left, or right direction.
void keyPressed()
{
//If the key is 'w' then moving the character upward
if(key == 'w') Character.move(0, -1);
//if the key is 'a' then moving the character left
if(key == 'a') Character.move(-1, 0);
//if the key is s then moving the character down
if(key == 's') Character.move(0, 1);
//if the key is 'd' then moving the character right
if(key == 'd') Character.move(1, 0);
//if the key is 'r' then setting the character to its default state and postion
if(key == 'r' || Character.CharacterDropped)
{
//stop playing background sounds
BackSoundEffect.stop();
MainMenuSoundEffect.stop();
BackSoundEffect.loop();
//restarting game to its default configuration
restart();
gameFrame = 0;
}
}
For rendering the player character on screen I implemented show() method inside the character class which is loading the player character image on the current frame x and y position.
//for rendering the player character in rectangular box structure on screen
void show()
{
rectMode(CENTER);
fill(255);
noStroke();
//showing player character image on screen
image(PlayerCharacter[currentFrame], this.xPos + xRail * screenHeight/4, this.yPos + this.yRail * screenHeight/4);
}
Then I did the implementation letter class which I used later to render grade letters on the screen. The letter class contains attributes for the position of letters and the movement speed of letter images on the screen. Afterwards, I implemented a function to update letters on-screen through frames and another function to check if the letter moves out of the screen. In this case, the letter further drops out from the object array.
The default constructor of the letter class takes the position of the letter as a parameter and sets the default speed and movement direction of the letter on screen.
//default constructor for letter class which takes position of letter as parameter
Letter(PVector pos)
{
//setting radom speed and velocity of the letters for the movement of letter
acceleration = new PVector(random(-0.05, 0.05), random(-0.05, 0.05));
velocity = new PVector(random(-2, 2), random(-2, 2));
//default position of each letter
position = pos.copy();
//random boundry for each letter movement
MovementSpan = random(100.0, 255.0);
}
I implemented the function IsLetterDrops() to check if the letter moves out the screen or not. If the letter moves out of the screen then the letter will be removed further from the letters array.
//for checking if the letters moves out of the screen or not
boolean IsLetterDrops()
{
//if the letter movement boundry value is less than 0 then letter is out of screen thus returning true else false
if(MovementSpan < 0.0)
{
return true;
}
else
{
return false;
}
}
For loading background image and sound effects, I made a separate function and called that function in the draw and setup functions to render the graphical assets on screen. There are different sound effects used to represent different behaviours. When the character moves, then the sound effect is different. Thus, for the main menu screen and game over screen, a different sound effect is allocated.
I implemented the loadGraphics() function which is loading the images saving in the global image arrays for the player character and grade letters. Furthermore, the background image is also loading into the global background image variable.
In setup() function I am setting the frame rate by using framerate() builtin function, loading the graphical assets, and playing the main menu sound effect in the loop.
//Main setup function for the configuration of game
void setup()
{
//setting the size of screen by width and height
size(1080, 720);
//setting framerate which means how many times screen should be repeated
frameRate(30);
//loading sound and background images assets on screen
loadAssets();
//Put the game in default configuration and game renders from starting position
restart();
//checking true which shows that character is at its default state
Character.CharacterDropped = true;
//for playing main menu sound
MainMenuSoundEffect.loop();
}
In draw() function I am rendering the background image by using image() function and also updating the game assets, for example, repositioning the player character based on the key pressed by the user.
//Main draw function which iterates infinite time and render game screen
void draw()
{
image(background,0,0);
//Updating GPA score point and game configuration
updateGame();
//showing graphichs and other assets on screen
show();
}
FINAL WORK:
CONCLUSION:
For my midterm project, I learned how to implement game logic, render multiple graphics all at the same time, move images from one position to another, and use my knowledge of data structures (for example arrays lists in processing). The game making journey has enhanced my knowledge in regards to different object initialization and creation, and in audio file manipulations from the loading of sound effects that has been done.
//importing library for sound
import processing.sound.*;
//Object Creation for Background Sound Effect while game playing
SoundFile BackSoundEffect;
//Object Creation For Main Menu Background Sound Effect
SoundFile MainMenuSoundEffect;
//Object Creation For Sound Effect When Character Fails to Eat Any Grade Letter
SoundFile SkipGradeLetterSound;
//Object Creation For Sound Effect during the movement of character
SoundFile CharacterMovementSoundEffect;
//Image object array creation for lion character
PImage PlayerCharacter[] = {null, null, null, null};
//Object Creation for grade letters
PImage gradeLetters[] = {null, null, null, null, null, null, null, null};
//Object Creation for the background image
PImage background;
//object creation for key controls for game playing
PImage KeyControlsImage;
//Object creation for main screen text prompt
PImage MainPromptText;
//Object creation for GPA Score Prompt
PImage GPAScore;
//Object creation for GPA score Points
PImage GPAScoreValues[] = {null, null, null, null, null, null, null, null, null, null};
//Game screen width and height
int screenWidth = 1080;
int screenHeight = 720;
//for how many times background should be repeated
int backgroundRepeat[] = {((int)screenWidth/128) + 2, ((int)screenHeight/128) + 2};
//the starting point of the background image
int backgroundOffset = 0;
ArrayList<IntersectionWithLetter> fframes;
float framespeed;
float margin;
float marginCounter;
int score;
int gameFrame = 0;
GradeLetters GradeLetters = new GradeLetters();
Character Character;
//Main setup function for the configuration of game
void setup()
{
//setting the size of screen by width and height
size(1080, 720);
//setting framerate which means how many times screen should be repeated
frameRate(30);
//loading sound and background images assets on screen
loadAssets();
//Put the game in default configuration and game renders from starting position
restart();
//checking true which shows that character is at its default state
Character.CharacterDropped = true;
//for playing main menu sound
MainMenuSoundEffect.loop();
}
//Main draw function which iterates infinite time and render game screen
void draw()
{
image(background,0,0);
//Updating game background which include background image,character images and grade letters
// updateBackground();
//Updating GPA score point and game configuration
updateGame();
//showing graphichs and other assets on screen
show();
}
//Key pressed function event which occurs when any keyboard key is pressed
void keyPressed()
{
//If the key is 'w' then moving the character upward
if(key == 'w') Character.move(0, -1);
//if the key is 'a' then moving the character left
if(key == 'a') Character.move(-1, 0);
//if the key is s then moving the character down
if(key == 's') Character.move(0, 1);
//if the key is 'd' then moving the character right
if(key == 'd') Character.move(1, 0);
//if the key is 'r' then setting the character to its default state and postion
if(key == 'r' || Character.CharacterDropped)
{
//stop playing background sounds
BackSoundEffect.stop();
MainMenuSoundEffect.stop();
BackSoundEffect.loop();
//restarting game to its default configuration
restart();
gameFrame = 0;
}
}
//Main class for Grade letters which inherits the letters class object array
class GradeLetters
{
//grade letters object array
ArrayList<Letter> letters;
//default constructor for grade letters
GradeLetters()
{
//initializing letters object array in defualt constructor
letters = new ArrayList<Letter>();
}
//populating grade letters with different screen positions
void AddLetter(PVector position, int nletters)
{
//for loop which iterates through number of letters and adding letter with its position
for(int i = 0; i < nletters; i++)
{
//adding letter with its position
this.AddLetter(position);
}
}
//single paramter add letter function which is for initializing new letter with its position
void AddLetter(PVector position)
{
//calling letters array add function and adding new letter with it position into the array
letters.add(new Letter(position));
}
//function for updating letters on screen
void UpdateLettersOnScreen()
{
//for loop which runs till letters array size in reverse order
for (int i = letters.size()-1; i >= 0; i--)
{
//getting the letter from the array from ith index
Letter l = letters.get(i);
//rendering the letter on screen
l.UpdateLettersOnScreen();
//checking if the letter goes out of screen then removing the letter from the array
if (l.IsLetterDrops())
{
letters.remove(i);
}
}
}
}
//letter class which represents single letter object
class Letter
{
//Position of Letter on screen
PVector position;
//Velocity of movement of letter on screen
PVector velocity;
//Accelration of movement of letter on screen
PVector acceleration;
//size of the letter which is randomly generated from values 1 to 4
int size = (int)random(1, 4);
//varibale for limiting the area for the movement of each letter on screen. the letter moves in its setting boundry
float MovementSpan;
//default constructor for letter class which takes position of letter as parameter
Letter(PVector pos)
{
//setting radom speed and velocity of the letters for the movement of letter
acceleration = new PVector(random(-0.05, 0.05), random(-0.05, 0.05));
velocity = new PVector(random(-2, 2), random(-2, 2));
//default position of each letter
position = pos.copy();
//random boundry for each letter movement
MovementSpan = random(100.0, 255.0);
}
//to update the rendering of letter on screen
void UpdateLettersOnScreen()
{
//updating speed and velocity of letter movement and decresing the movement boundry of letter by value 2.
velocity.add(acceleration);
position.add(velocity);
MovementSpan -= 2.0;
//displaying the letter on screen
display();
}
//to display letter on screen
void display()
{
//setting transparent color
fill(255, MovementSpan);
//drawing rectangle which represents the boundry of letters
rect(position.x, position.y, size, size);
}
//for checking if the letters moves out of the screen or not
boolean IsLetterDrops()
{
//if the letter movement boundry value is less than 0 then letter is out of screen thus returning true else false
if(MovementSpan < 0.0)
{
return true;
}
else
{
return false;
}
}
}
//Player Character Class
class Character
{
//The default x and y position value variables for player character.
int xPos, yPos;
//The default x and y direction of the player character
int xRail = 0, yRail = 0;
//variable for default image frame value for player character
int currentFrame = 0;
//varibale for checking of the player character is inside screen or not
boolean CharacterDropped = false;
//default constructor for player character which sets the default position of the player character
Character(int xPos, int yPos)
{
this.xPos = xPos;
this.yPos = yPos;
}
//function for the movement of player character accoriding to the x and y direction of the character
void move(int xDirection, int yDirection)
{
//checking if the movement of the character is in horizontal direction
if(this.xRail + xDirection <= 4 && this.xRail + xDirection >= -1)
{
//if the y direction value is zero then adding new letter in the followed direction and playing player character movement sound
if(yDirection == 0)
{
GradeLetters.AddLetter(new PVector(this.xPos + xRail * screenHeight/4, this.yPos + this.yRail * screenHeight/4 + 30), 20);
keyViewerActive(key, 10, screenHeight - 10);
CharacterMovementSoundEffect.play();
}
//moving one step forward the direction of player character in x direction
this.xRail += xDirection;
}
//checking if the movement of the character is in verticle direction
if(this.yRail + yDirection <= 1 && this.yRail + yDirection >= -1)
{
//if the x direction value is zero then adding new letter in the followed direction and playing player character movement sound
if(xDirection == 0)
{
GradeLetters.AddLetter(new PVector(this.xPos + xRail * screenHeight/4 + 20, this.yPos + this.yRail * screenHeight/4 + 30), 10);
keyViewerActive(key, 10, screenHeight - 10);
CharacterMovementSoundEffect.play();
}
//moving one step forward the direction of player character in y direction
this.yRail += yDirection;
}
}
//function to check if the player character is out screen which means the character died
//stopping the game background sound and playing main menu sound
void die()
{
this.xPos = -500;
this.yPos = -500;
this.CharacterDropped = true;
BackSoundEffect.stop();
SkipGradeLetterSound.play();
MainMenuSoundEffect.play();
}
//for rendering the player character in rectangular box structure on screen
void show()
{
rectMode(CENTER);
fill(255);
noStroke();
//showing player character image on screen
image(PlayerCharacter[currentFrame], this.xPos + xRail * screenHeight/4, this.yPos + this.yRail * screenHeight/4);
}
//for updating grade letters on screen
void UpdateLettersOnScreen()
{
//if the player character is in screen boundry
if(!CharacterDropped)
{
//shwoing the player character
this.show();
//updating the current frame value to render assests on screen
if(frameCount % 5 == 0)
{
currentFrame++;
if(currentFrame == 4) currentFrame = 0;
}
}
}
}
// class for checking if the player character eat or absorb any grade or not
class IntersectionWithLetter
{
// variable for the x postion
int xPos;
// varibales for the screen frames and its speed initialized by random values
int currentFrame = (int)random(0, 7), frameSpeed = (int)random(4, 6), empty = (int)random(-2, 2);
float speed, slowSpeed = 0;
//check if there is need to add gpa point or not
boolean canAddToScore = true;
//default constructor which takes x position of intersecting the grade letter and player character + the speed with which intersection occurs
IntersectionWithLetter(int xPos, float speed)
{
this.xPos = xPos;
this.speed = speed;
}
//function to check if the player character eat any grade letter or not
void Eat()
{
if(!Character.CharacterDropped && Character.yRail != this.empty && Character.xPos + Character.xRail * screenHeight/4 >= this.xPos -40 && Character.xPos + Character.xRail * screenHeight/4 <= this.xPos + 70)
{
Character.die();
}
}
//for showing grade letter on the current rendered frame
void show()
{
rectMode(CENTER);
noStroke();
//for loop which iterates one time and if the ith index is not empty then showing image of grade letter on current frame
for(int i = -1; i <= 1; i++)
{
if(i != empty) image(gradeLetters[currentFrame], this.xPos, screenHeight/2 + i * screenHeight/4);
}
}
//for updating letters on current rendered frame
void UpdateLettersOnScreen()
{
//updating the speed of grade letters
if(this.slowSpeed == 0) this.xPos -= this.speed;
else this.xPos -= this.slowSpeed;
this.Eat();
this.show();
// if the rendering is done with all frames then resetting the current frame value to zero
if(frameCount % frameSpeed == 0)
{
currentFrame++;
if(currentFrame == 8)
{
currentFrame = 0;
}
}
}
}
//for updating frames on screen after intersection
void UpdateFrames()
{
//looping to update letters on screen frame after intersection
for(IntersectionWithLetter IntersectionWithLetter : fframes)
{
IntersectionWithLetter.UpdateLettersOnScreen();
}
//checking if the top frame is first frame
if(fframes.size() > 0 && fframes.get(0).xPos < -128)
{
//removing the first frame from the screen
removeFirstFrame();
}
//checking if there is space on rendering more frame on screen
if(marginCounter <= 0 && !(gameFrame >= 2400 && gameFrame <= 3030))
{
//adding new frame on screen
addFrame();
}
SlowFrameSpeed();
}
//for removing the first frame from the screen
void removeFirstFrame()
{
fframes.remove(0);
}
//for adding new frame on screen
void addFrame()
{
fframes.add(new IntersectionWithLetter(screenWidth + 128, framespeed));
if(margin > screenWidth/4) margin *= 0.95;
marginCounter = margin;
if(framespeed < 20)
{
framespeed *= 1.015;
}
}
//for slowing the frame rendering speed
void SlowFrameSpeed()
{
//if there is space to render more frames on screen then setting the frame speed to 1
if(gameFrame >= 2400 && gameFrame <= 3030)
{
for(IntersectionWithLetter IntersectionWithLetter : fframes)
{
IntersectionWithLetter.slowSpeed = 1;
}
}
//if there is no space to render more frames on screen then setting the frame speed to 0
else
{
for(IntersectionWithLetter IntersectionWithLetter : fframes)
{
IntersectionWithLetter.slowSpeed = 0;
}
}
}
//for updating background of the game
void updateBackground()
{
// if the frame vlue is even then updating the background offset position
if(frameCount%2 == 0)
{
backgroundOffset++;
}
//if the background offset value is maximum then resetting the offset value to 0 and showing the background image
if(backgroundOffset == 128)
{
backgroundOffset = 0;
showBackground();
}
}
//for displaying background on screen
void showBackground()
{
//nested for loop to render the background image on each pixel on screen in x and y direction
for(int x = 0; x < backgroundRepeat[0]; x++)
{
for(int y = 0; y < backgroundRepeat[1]; y++)
{
image(background, x*background.width - backgroundOffset, y*background.height);
}
}
}
// for converting number to images
ArrayList<PImage> numberToArrayOfImages(int number)
{
ArrayList<PImage> images = new ArrayList<PImage>();
String numberS = str(number);
int len = numberS.length(), buffer;
//iterating till the number length
for(int index = 0; index < len; index++)
{
//switch for checking for the desired gpa point and if the case match then updating buffer
switch(numberS.charAt(index))
{
case '0':
buffer = 0;
break;
case '1':
buffer = 1;
break;
case '2':
buffer = 2;
break;
case '3':
buffer = 3;
break;
case '4':
buffer = 4;
break;
case '5':
buffer = 5;
break;
case '6':
buffer = 6;
break;
case '7':
buffer = 7;
break;
case '8':
buffer = 8;
break;
case '9':
buffer = 9;
break;
default:
buffer = 0;
break;
}
images.add(GPAScoreValues[buffer]);
}
return images;
}
//showing gpa score
void showScore()
{
fill(255);
textSize(16);
//rendering gpa score image
image(GPAScore, 0, 0);
ArrayList<PImage> images = numberToArrayOfImages(score);
int index = 0;
//iterating all gpa score images
for(PImage image : images)
{
int yOffset = 0;
//if the gpa score value matches with the image
if(image == GPAScoreValues[0]) yOffset = 4;
//then rendering the image on screen
image(image, 60 + index * 11, 10 - yOffset);
index++;
}
}
// for updating gpa score
void updateScore()
{
//checking if player is in screen boundry
if(!Character.CharacterDropped)
{
//checking if the
for(IntersectionWithLetter layer : fframes)
{
if(layer.canAddToScore == true && layer.xPos <= Character.xPos + (Character.xRail * screenHeight/4))
{
score++;
layer.canAddToScore = false;
}
}
}
}
//for displaying key viewer on screen
void keyViewer()
{
image(KeyControlsImage, 10, screenHeight - 10 - KeyControlsImage.height);
}
//for updating the position based on the active key press
void keyViewerActive(char keyName, int xPos, int yPos)
{
fill(50, 255, 50);
//if the key is w then drawing square to new required position
if(keyName == 'w')
{
square(xPos + 3 * 3 + 44 + 25, yPos + 3 * 3 - 44 * 2, 46);
}
//if the key is a then drawing square to new required position
else if(keyName == 'a')
{
square(xPos + 3 + 22, yPos - 3 - 22, 46);
}
//if the key is s then drawing square to new required position
else if(keyName == 's')
{
square(xPos + 3 * 3 + 44 + 25, yPos - 3 - 22, 46);
}
//if the key is d then drawing square to new required position
else if(keyName == 'd')
{
square(xPos + 3 * 5 + 44 * 2 + 28, yPos - 3 - 22, 46);
}
keyViewer();
}
// function for main menu screen
void mainScreen()
{
// filling up a rectangle with light color
fill(50, 150);
rect(screenWidth/2, screenHeight/2, screenWidth, screenHeight);
textSize(36);
fill(255);
imageMode(CENTER);
//prompting text on screen
image(MainPromptText, screenWidth/2, screenHeight/3);
imageMode(CORNER);
}
// for loading images and sound effect assests on screen
void loadAssets()
{
loadGraphics();
loadSound();
}
// for loading graphics on screen
void loadGraphics()
{
PlayerCharacter[0] = loadImage("C1.png");
PlayerCharacter[1] = loadImage("C2.png");
PlayerCharacter[2] = loadImage("C3.png");
PlayerCharacter[3] = loadImage("C4.png");
gradeLetters[0] = loadImage("A.png");
gradeLetters[1] = loadImage("B.png");
gradeLetters[2] = loadImage("C.png");
gradeLetters[3] = loadImage("D.png");
gradeLetters[4] = loadImage("F.png");
gradeLetters[5] = loadImage("B+.png");
gradeLetters[6] = loadImage("C+.png");
gradeLetters[7] = loadImage("D+.png");
GPAScoreValues[0] = loadImage("0.png");
GPAScoreValues[1] = loadImage("1.png");
GPAScoreValues[2] = loadImage("2.png");
GPAScoreValues[3] = loadImage("3.png");
GPAScoreValues[4] = loadImage("4.png");
background = loadImage("back.png");
KeyControlsImage = loadImage("Controls.png");
MainPromptText = loadImage("textprompt.png");
GPAScore = loadImage("gpascore.png");
}
//for loading sound effects
void loadSound()
{
//background sound effect
BackSoundEffect = new SoundFile(this, "backgroundsound.mp3");
//main menu sound effect
MainMenuSoundEffect = new SoundFile(this, "mainmenusound.mp3");
//skipping grade letter sound
SkipGradeLetterSound = new SoundFile(this, "dropsound.mp3");
//player character movement sound effect
CharacterMovementSoundEffect = new SoundFile(this, "cmovesound.mp3");
}
//for updating game
void updateGame()
{
//upodating frames count on screen
gameFrame++;
//updating grade letters on screen
Character.UpdateLettersOnScreen();
//updating frames on screen
UpdateFrames();
//updating gpa score
updateScore();
//for updating letters on screen
GradeLetters.UpdateLettersOnScreen();
//upodating frame speed
marginCounter -= framespeed;
}
//for showing gpa score and key viewer on game screen
void show()
{
//showing gpa score
showScore();
//if player character is out of screen then returning to main menu screen
if(Character.CharacterDropped)
{
mainScreen();
}
//for rendering key viewer on screen
keyViewer();
}
// for restarting the game.
void restart()
{
// the gpa score will be zero
score = 0;
//frame speed reset to 5 which means 5 frames will be rendered in one second
framespeed = 2;
//margin between player character and grade letters
margin = screenWidth/2;
marginCounter = margin;
//initializing player character to its default position
Character = new Character(230, height/2);
// resetting frames
fframes = new ArrayList<IntersectionWithLetter>();
fframes.add(new IntersectionWithLetter(screenWidth + 128, framespeed));
}
Building up on the progress I posted a while ago, I am happy the process went smoothly. I did face a few minor issues but the help from professor Aaron and Jack, aided me in getting through them.
Issues and solutions:
First of all an initial issue that I had regarding the game was that I wasn’t able to fix the issue that the thief was above the darkness. Initially I was using load pixels/ image processing with pixels which did make the flashlight effect, but after professor Aaron’s help, the conclusion that in order to make the thief behind the darkness as well I had to switch to using a PGraphic object was reached. I drew the nyu background and the thief image into the pgraphic object and so I can access the pixels of that pgraphic object in the for loops, instead of just the background image.
Another issue I faced was that using keyPressed after game over to return to the play mode did not work as I had to keep pressing the key, hence in order to fix this I created a new function “reset game”, which helped fix this issue as well as replay the sounds and the reset the time back to countdown from 5.
void resetGame(){
if (keyPressed==true){
gameState="PLAY";
file.loop();
laugh.stop();
sound.stop();
timeLeft=6;
}
Further development:
Developments I mad since the progress post are extensive. First of all I added the start page with instructions and the “win” or “lose” pages as well, with the transitions from each one to the other.
Intro page:
Win page:
Game Over page:
I also made the thief image appear at random locations every time a new game is played, and included the mouse pressed event for the player to be able to press on the thief and win.
Finalizing minor details:
In my opinion the finer details brought the whole project together. The sound effects of the thief laughing when you lose the game as well as the alarm sirens makes the game more interesting. The timer makes the suspenseful mood , making the game much more fun to play. Adding the timer was bit confusing but with the help of this youtube video I was able to do it (https://www.youtube.com/watch?v=JXqukW44Dhs&t=813s) .
The sounds I used were gotten from (https://freesound.org/)
Final Result:
This is the zip code if anyone wants to try out the game.
import processing.sound.*;
SoundFile file;
SoundFile sound;
SoundFile laugh;
PImage nyu;
PImage thief;
PImage introPic;
PImage winningPic;
PImage gameOver;
PImage image;
float r1 ;
float r2 ;
int buttonX, buttonY;
int buttonWidth, buttonHeight;
color buttonColor, baseColor; //colors of play button
PFont f;
PGraphics pg;
PGraphics black, mask;
String gameState;
float x;
float y;
Timer countdownTimer;
int timeLeft;
void setup() {
size(1200, 800);
gameState=("START");
PFont.list();
f = createFont("Times New Roman",75);
x = random(-20, 1100);
y = random(-20, 600);
nyu=loadImage("nyu.png");
thief=loadImage("thief.png");
buttonColor = color(0);
baseColor = color(102);
buttonWidth = 200;
buttonHeight = 80;
buttonX = width/2+300;
buttonY = 3*height/4 + 100;
countdownTimer=new Timer(1000);
timeLeft=6;
file= new SoundFile(this,"theme.mp3");
sound= new SoundFile(this,"alarm.wav");
laugh= new SoundFile(this,"evil laugh.wav");
}
void draw() {
background(0);
if (gameState=="START"){
introScreen();
if (keyPressed==true){
file.loop();
gameState="PLAY"; //transition to play state if key pressed
countdownTimer.start();
}
}
else if (gameState=="PLAY"){
theGame();
}
else if (gameState=="WIN"){
congratsScreen();
if (keyPressed==true){
gameState="PLAY";
resetGame();
x = random(-20, 1100);
y = random(-20, 600);
}
}
else if (gameState=="LOSE"){
gameOver();
resetGame();
x = random(-20, 1100);
y = random(-20, 600);
}
}
//code for the actual game
void theGame(){
//pgraphics
black = createGraphics(1200, 800);
black.beginDraw();
black.background(0);
black.endDraw();
mask = createGraphics(1200, 800);
mask.beginDraw();
mask.background(0);
mask.endDraw();
pg=createGraphics(1200,800);
pg.beginDraw();
//code for allowing the players to click on the thief image
pg.image(nyu,0,0);
pg.image(thief,x,y);
if(dist(x,y,mouseX,mouseY)<100){
if(mousePressed==true){
gameState="WIN"; //if the player presses on the image the player wins
file.stop();
if(!sound.isPlaying());
sound.play();
}
}
pg.endDraw();
mask.beginDraw();
mask.background(0);
mask.noStroke();
mask.fill(255);
mask.ellipse(mouseX, mouseY, 200, 200);
mask.endDraw();
pg.mask(mask);
image(black,0,0);
image(pg, 0, 0);
//countdown timer logic
if (countdownTimer.complete()==true){
if(timeLeft>1){
timeLeft--;
countdownTimer.start();
}else{
gameState="LOSE"; //code for if the timer finishes the player loses
file.stop();
sound.stop();
if(!laugh.isPlaying());
laugh.play();
}
}
//showing the timer
String s="Time Left:"+timeLeft;
textAlign(LEFT);
textSize(20);
fill(255,0,0);
text(s,20,100);
}
//code for the start/intro screen
void introScreen(){
background(157,157,165);
fill(buttonColor);
imageMode(CORNERS);
introPic=loadImage("introPic.png");
image(introPic, 0,0,1200, 650);
stroke(255);
rect(buttonX, buttonY, buttonWidth, buttonHeight, 20); //key pressed play button
textFont(f,25);
textAlign(CENTER, CENTER);
fill(255);
textSize(20);
String play = "Press any key to play"; //text in play button to clarify it
text(play, buttonX+100 , buttonY+30);
textSize(30);
String instructions="Find the thief using the flashlight before the time finishes";
text(instructions, buttonX-500 , buttonY+15);
String instructionsCont="Click on the thief to capture him,You have 5 seconds!";
text(instructionsCont,buttonX-500 , buttonY+50);
}
//code for the congrats screen when the player wins
void congratsScreen(){
winningPic=loadImage("busted2.png");
image(winningPic, 0,height/5,1200,800);
textFont(f,40);
textAlign(CENTER, CENTER);
fill(255);
textSize(40);
String congrats = "Congrats You Busted Him!";
text(congrats, width/2,80);
String replay = "Click any key to play again";
text(replay, width/2,130);
}
//code for the gameover screen when a player loses
void gameOver(){
background(0);
textSize(80);
String gameover = "GAME OVER!";
String gameovercont="HE GOT AWAY WITH IT";
text(gameover, width/2-250,height/4);
text(gameovercont, width/2-400,height/2);
textSize(40);
String replay = "Click on any key to play again";
text(replay, width/2-200,height/2+200);
}
//code to reset the game, along with the sound and the timer
void resetGame(){
if (keyPressed==true){
gameState="PLAY";
file.loop();
laugh.stop();
sound.stop();
timeLeft=6;
}
}
For my midterm project, I made a mutant version of Match Three game, including elements of Tetris. The patterns of the cubes come from Mahjong, a traditional Chinese tile-based chess game. The player needs to match 3 or more cubes with the same patterns and make combos to eliminate the cubes, and the goal is to eliminate the highlighted cube. I designed a help window, so when you press “H”, you can see some of the combos allowed in this game. There are some more kinds of combos, and I intend to leave the part for the player to figure out.
In the game, the player uses “s”, “a”, “d” to move the cube, and can press “enter” to start, “p” to pause the game, “h” for help and “esc” to exit the game. The players can press “enter” to restart the game once they clear or they lose the game.
I inserted some sound effects, like when the cube falls into the place or the player successfully makes a combo, also when the player wins or loses the game.
There is also one more mechanism that as the score accumulates, the cubes will fall down at a higher speed.
Mahjong
Challenges:
It is challenging to design the mechanism for the game and also to figure out the logic part of the codes. During this week, I focused on the UI of the game. And using images instead of colors is harder than I thought. I had to modify pretty many codes to replace all the colors with images.
And for the sound part, I had to pay attention to where to put the sound.play code so that it won’t loop over and over. It also takes me a lot of time designing the UI and locating the texts and images.
But the whole process of making the game from scratch is interesting. I learned a lot from this midterm assignment!