Solo Pong Beta!

I was bored the other day and started making a Pong-like game. And it was all fun and nice until I decided to add a second ball to the game. I realized coding a second ball the same way I coded the first one would be incredibly inefficient. So then objects happened. 😀

Using classes and objects, coding games becomes way easier because we can have many instances of the same thing happening independently. Thus, once I got the basics of classes down, adding more cool stuff to the game was a relatively simple matter.

So far, I’ve made 6 different stages. Here’s what I learned and/or used to make each one:

  1. The Wall: This stage is just like Pong except that instead of having an opponent player, there’s just a wall that comes closer as you bance the ball on your paddle. It was easy to make with the stuff we saw in class (remember the bouncing ball of doom?): my first prototype actually had only this stage coded without using objects. Collision detection with the player paddle only required a couple of inequalities to make sure the ball would only bounce on the paddle.Captura de pantalla 2015-11-02 20.49.04
  2. Twin Balls: Now I wanted to add an extra ball. To do so, I took the code I had written for the ball and turned it into a class Ball. I also worked out a way to keep track of time using the frameCount, a couple of variables to store current and past frame counts, and a division (dividing by the frame rate gives me a counter in seconds instead of frames).Captura de pantalla 2015-11-02 20.49.17
  3. Smoke: I wanted to have obscuring smoke to make it harder for the player to bounce the ball back. This meant having some object(s) drawn over the ball to obscure its exact position. My solution was to use an object array to easily create and manipulate 50 smoke objects. After calling the array (ClassName[] arrayName), I used a for() loop to create the individual objects within the array. Using the same technique combined with functions, I made the smokes move to the ball’s position one by one by iterating through the array. I then added a bit of randomness in that movement to create a more chaotic (and confusing) smoke cloud.Captura de pantalla 2015-11-02 20.49.32
  4. Darkness: The fourth stage’s “perk” was that the ball would randomly become invisible when bouncing off walls. Coding that was simple enough. The interesting part was creating Ring objects to aid the player: whenever the ball bounced off a wall, it would call a Ring to its possition and animate it. With a small array to iterate through, this can be done indefinitely without consuming too many resources.Captura de pantalla 2015-11-02 20.49.57
  5. Blocks: Let me get this straight. Collision detection is a pain in the ass. Again, I created an object array for the boxes. The probllem was making the ball bounce when touching them. The right way. And only when there actually was a box to bounce against. Same ideas as before, but much trickier and with a ton of && within a couple of if() statements. Ugh. (UPDATE: version 3 of Solo Pong uses an ArrayList instead of an object array. They are quite similar BUT the array list is like an array of variable length. You can add and delete elements from it (and have that reflected on the list and its length), which makes things like, say, counting the amount of blocks left in the stage, MUCH easier)Captura de pantalla 2015-11-02 20.50.10
  6. Physics?: Here, I added a simple simulation of gravity – with a twist. Fairly simple: I just had to make the program alter the ball’s speed rather than position every frame. Combined with the movement that I had already coded in, this creates a fairly nice 2D simulation of gravity. Yay!Captura de pantalla 2015-11-02 20.51.00

I also imported the Minim library to add some sound to the game. I tried using Processing’s official Sound library BUT IT DOESN’T WORK ON WINDOWS FOR SOME GODFORSAKEN REASON. So yeah.

 

Code here (don’t forget to import Minim or it won’t run!):

import ddf.minim.*;
import ddf.minim.analysis.*;
import ddf.minim.effects.*;
import ddf.minim.signals.*;
import ddf.minim.spi.*;
import ddf.minim.ugens.*;

Minim minim;
AudioOutput out;

float playerX = 30;
float playerY = 300;
float wallX = 790;
float playerSpeed = 10;
int ballStart = 0;
int stage = 0;
int prevStage = 0;
float score = 0;
float score1;
float difficulty = 4;
float time = 0;
float lastTime = 0;
float gravity = 1;
int currentSmoke = 0;
int currentRing = 0;
int initialBoxCount = 5;
int boxCount;
int bounceNote = 0;

Ball b1 = new Ball(playerX + 15, playerY, 10, 10);
Ball b2 = new Ball(playerX + 15, playerY, 5, 10);
Smoke[] smoke;
Ring[] rings;
ArrayList<Box> boxes = new ArrayList<Box>(initialBoxCount);
String[] stages = {"Wall!", "Twins!", "Smoke!", "Darkness!", "Blocks!", "Physics?"};
String[] bounceNotes = {"F3", "C4", "Ab3", "Bb3"}; 

PFont Ocra;

void setup() {
  size(800, 600);
  frameRate(60);
  minim = new Minim(this);
  out = minim.getLineOut();
  out.setTempo(140);
  String[] fonts = PFont.list();
  printArray(fonts);
  Ocra = createFont("OCR A Extended", 24);
  smoke = new Smoke[100];
  for (int q = 0; q < smoke.length; q ++) {
    smoke[q] = new Smoke();
  }
  rings = new Ring[5];
  for (int q = 0; q < rings.length; q ++) {
    rings[q] = new Ring();
  }
  //boxes = new Box[5];
  for (int q = 0; q < initialBoxCount; q ++) {
    boxes.add(new Box(random(width/3, width - 33), random(3, height - 33), 60, 1));
    boxCount = q + 1;
  }
}

void draw() {
  screen();
  stageChange();
  //Player
  rectMode(CENTER);
  rect(playerX, playerY, 10, 100);
  //Ball 1
  b1.update();
  if (stage == 1) {
    b2.update();
  }
  //Boxes
  for(Box box : boxes) {
    box.life();
  }
  //Rings
  for (int q = 0; q < rings.length; q ++) {
    rings[q].shrink();
    rings[q].die();
  }
  //Smoke
  if (stage == 2) {
    for (int q = 0; q < smoke.length; q ++) {
      smoke[q].obscure();
    }
    smoke[currentSmoke].move();
  }
  //  for (Smoke q : smoke){
  //    q.obscure();
  //  }
  //Inputs
  if (keyPressed == true) {
    switch(key) {
    case 'w':
      playerY -= playerSpeed;
      break;
    case 's':
      playerY += playerSpeed;
      break;
    case ' ':
      if (ballStart == 0) {
        ballStart = 1;
        lastTime = frameCount;
        b1.shoot();
        if (stage == 1) {
          b2.shoot();
        }
      }
      break;
    case '1':
      if (prevStage == stage && ballStart == 0) {
        stage = (stage + 1)%stages.length;
      }
      break;
    }
  }
  //Ball movement
  b1.move();
  if (stage == 1) {
    b2.move();
  }
  if (stage == 4) {
    b1.bounceBox();
  }
  //Wall
  if (stage == 0) {
    wallX = 790 - difficulty*score;
  }
  if (stage == 5) {
    b1.ballSpeedY += gravity;
  }
  //Constrains
  playerY = constrain(playerY, 53, height - 53);
  score = constrain(score, 0, 100000);
}

void keyReleased() {
  if (key == '1') {
    prevStage = stage;
    lastTime = frameCount;
    score = 0;
  }
}
class Ball {
  float ballX, ballY, baseSpeed, speed, ballSpeedX, ballSpeedY, ballSize, ballAngle;
  float dark = 1;
  Ball (float a, float b, float c, float d) {
    ballX = a;
    ballY = b;
    baseSpeed = c;
    ballSize = d; 
    speed = baseSpeed;
  }
  void shoot() {   
    ballAngle = random(-QUARTER_PI, QUARTER_PI);
    ballSpeedX = speed*cos(ballAngle);
    ballSpeedY = speed*sin(ballAngle);
    ballStart = 2;
  }
  void update() {
    rectMode(CENTER);
    if (dark >= 1) {
      stroke(255);
      strokeWeight(5);
      fill(255, 255, 255, 32);
    } else {
      stroke(0, 0, 0, 10);
      strokeWeight(5);
      fill(0, 0, 0, 10);
    }
    rect(ballX, ballY, ballSize, ballSize);
    speed = baseSpeed+(score*difficulty/500);
    ballAngle = atan(ballSpeedY/ballSpeedX);
    if (ballStart == 2 && stage == 2) {
      smoke[currentSmoke].smokeX = ballX + random(-60, 60);
      smoke[currentSmoke].smokeY = ballY + random(-60, 60);
      currentSmoke = (currentSmoke + 1) % 100;
    }
  }
  void move() {
    if (ballStart == 0) {
      ballX = playerX + 15;
      ballY = playerY;
      dark = 1;
    } else if (ballStart == 2) {
      ballX += ballSpeedX;
      ballY += ballSpeedY;
      if (ballY <= 3 || ballY >= 597) {
        ballSpeedY = -ballSpeedY;
        rings[currentRing].call(ballX, ballY);
        if (stage == 3) {
          dark = random(0, 3);
        }
      }
      if (ballX >= wallX - 3) {
        ballSpeedX = -ballSpeedX;
        rings[currentRing].call(ballX, ballY);
        if (stage == 3) {
          dark = random(0, 3);
        }
      }
      if (ballX <= playerX + 10 && ballX >= playerX && ballY >= playerY - 51 && ballY <= playerY + 51) {
        score ++;
        rings[currentRing].call(ballX, ballY);
        gravity = -gravity;
        speed = baseSpeed+(score*difficulty/500);
        ballAngle = -(ballAngle) + radians((ballY - playerY)/2.5);
        ballSpeedX = speed*cos(ballAngle);
        ballSpeedY = speed*sin(ballAngle);
        if (stage == 3) {
          dark = random(0, 3);
        }
      }
      if (ballX <= 0) {
        ballStart = 0;
        lastTime = frameCount;
        score -= 10;
        score = constrain(score, 0, 100000);
        textSize(20);
        fill(255, 0, 0, 175);
        text("-10", playerX+5, playerY-20);
      }
    }
  }
  void bounceBox() {
    for (Box q : boxes) {
      if ((ballX >= q.boxX && ballX <= q.boxX + q.boxSize && ballY >= q.boxY 
        && ballY <= q.boxY + 20 && q.boxLife > 0 && ballSpeedY > 0) 
        || (ballX >= q.boxX && ballX <= q.boxX + q.boxSize && ballY >= q.boxY + q.boxSize 
        && ballY <= q.boxY + q.boxSize - 20 && q.boxLife > 0 && ballSpeedY < 0)) {
        ballSpeedY = -ballSpeedY;
        q.boxLife --;
        rings[currentRing].call(ballX, ballY);
        if (stage == 3) {
          dark = random(0, 3);
        }
      }
      if ((ballY >= q.boxY && ballY <= q.boxY + q.boxSize && ballX >= q.boxX 
        && ballX <= q.boxX + 20 && q.boxLife > 0 && ballSpeedX > 0) 
        || (ballY >= q.boxY && ballY <= q.boxY + q.boxSize && ballX <= q.boxX + q.boxSize 
        && ballX >= q.boxX + q.boxSize - 20 && q.boxLife > 0 && ballSpeedX < 0)) {
        ballSpeedX = -ballSpeedX;
        q.boxLife --;
        rings[currentRing].call(ballX, ballY);
        if (stage == 3) {
          dark = random(0, 3);
        }
      }
    }
  }
}
class Box {
  float boxX, boxY, boxSize, boxR, boxG, boxB, boxLife;
  Box(float x, float y, float s, float l) {
    boxX = x;
    boxY = y;
    boxSize = s;
    boxLife = l;
    boxR = random(0, 255);
    boxG = random(0, 255);
    boxB = random(0, 255);
  }
  void life() {
    if (boxLife > 0 && stage == 4) {
      rectMode(CORNER);
      fill(255);
      stroke(boxR, boxG, boxB);
      rect(boxX, boxY, boxSize, boxSize);
    }
  }
}
class Ring {
  float ringX, ringY, ringSize, alpha;
  Ring () {
    ringX = 0;
    ringY = 0;
    ringSize = 0;
    alpha = 255;
  }
  void call(float x, float y) {
    ringX = x;
    ringY = y;
    ringSize = 60;
    currentRing = (currentRing + 1) % rings.length;
    bounceNote();
  }
  void shrink() {
    noFill();
    stroke(255, alpha);
    rectMode(CENTER);
    rect(ringX, ringY, 60 - ringSize, 60 - ringSize);
    if (ringSize > 0) {
      ringSize -= 1.5;
    }
  }
  void die() {
    if (ringSize <= 0) {
      alpha = 0;
    } else {
      alpha = 255;
    }
  }
}
class Smoke {
  float smokeX, smokeY, size;
  Smoke () {
    smokeX = -100;
    smokeY = -100;
    size = 100;
  }
  void move() {
      smokeX = random(b1.ballX - 60, b1.ballX + 60);
      smokeY = random(b1.ballY - 60, b1.ballY + 60);
      size = 100;
  }
  void obscure() {
    rectMode(CENTER);
    stroke(128);
    strokeWeight(4);
    fill(64);
    rect(smokeX, smokeY, size, size);
  }
}
void bounceNote() {
  out.pauseNotes();
  out.playNote(0, 0.25, bounceNotes[bounceNote]);
  out.resumeNotes();
  bounceNote = (bounceNote + 1) % bounceNotes.length;
}
void screen() {
  //bg
  rectMode(CORNER);
  noStroke();
  fill(0, 0, 0, 10);
  rect(0, 0, width, height);
  //Frame (walls)
  fill(255);
  stroke(255);
  strokeWeight(5);
  line(0, 3, 799, 3);
  line(0, 597, 799, 597);
  //Backwall
  fill(255, 255, 255, 32);
  rect(wallX, 3, width - wallX, height);
  //Score
  textSize(150);
  text(int(score), 300, 375);
  if (ballStart == 2) {
    time = (frameCount - lastTime)/60;
  }
  text(time, 100, 500);
  textSize(30);
  text(stages[stage], 20, 50);
}

 

Leave a Reply