Solo Pong – Boss Stage

Solo Pong is now pretty much ready for exhibition at Open Studios! For this week’s assignment I added a boss stage, where I used object arrays and array lists to manage the boss’s attacks and a couple of particle systems. ­čśÇ

So I based the code for the boss enemy directly from what I had written for the block collision detection. I just made the boss a huge block with movement and more life points.

The interesting part (and the one that has to do with the actual assignment) was coding the projectiles and particles.

For the boss’s projectiles, I created a class very similar to my Ball class. Using an array of objects, I created several instances of the class BadBall and “called” them to the screen to be shot using a function and iterating one by one through the array. That way, there was no need to keep creating BadBalls or calling them individually.

For the particle systems, I used array lists. I created a piece of code that would use a for() loop to generate a fixed number of particles. Each particle would have a set time to be alive, and at the end of it the program would call the remove() function to destroy the object. Hence, the particle system could be created (splattering little red blocks everywhere) and then every element would die and be removed one by one, stopping the program from slowing down.

Captura de pantalla 2015-11-08 21.26.10
YEAH

Additionally, as I mentioned in my previous post, I also used an array list to take care of the box count in the block breaker stage: by using the function ArrayList.size() I could monitor the amount of blocks left and advance the game once there were none left.

So yay! Revised code is down here:

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 currentBad = 0;
int badCount = 0;
int initialBoxCount = 10;
int boxCount;
int bounceNote = 0;
int deathCount = 0;
int finalScore = 0;
int totalTime = 0;

Ball b1 = new Ball(playerX + 15, playerY, 10, 10);
Ball b2 = new Ball(playerX + 15, playerY, 5, 10);
Boss boss = new Boss(650, 250, 100, 20, 6);

Smoke[] smoke;
Ring[] rings;
BadBall[] bad;
ArrayList<Box> boxes = new ArrayList<Box>(initialBoxCount);
ArrayList<ParticleBlood> blood = new ArrayList<ParticleBlood>();
ArrayList<Confetti> confetti = new ArrayList<Confetti>();
String[] stages = {"Wall!", "Twins!", "Smoke!", "Darkness!", "Blocks!", "Physics?", "BOSS", "RESULTS!"};
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);

  boss.boxLife = 20;
  bad = new BadBall[20];
  for (int b = 0; b < bad.length; b++) {
    bad[b] = new BadBall(-100, -100, 10, 10);
  }
  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, 4));
    boxCount = q + 1;
  }
}

void draw() {
  screen();
  stageChange();
  //Player
  rectMode(CENTER);
  stroke(255);
  fill(255, 255, 255, 32);
  rect(playerX, playerY, 10, 100);
  //Ball 1
  b1.update();
  if (stage == 1) {
    b2.update();
  }
  //Boxes
  for (int box = boxes.size() - 1; box >= 0; box --) {
    boxes.get(box).life();
    if (boxes.get(box).boxLife <= 0) {
      boxes.remove(box);
    }
  }
  //Boss
  if (stage == 6) {
    boss.life();
    boss.move();
  }
  //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();
  }
  //BadBalls
  if (stage == 6) {
    for (BadBall b : bad) {
      b.update();
      b.move();
    }
    badCount ++;
    if (badCount >= boss.period && boss.boxLife > 0) {
      badCount = 0;
      bad[currentBad].shoot();
      currentBad = (currentBad + 1) % bad.length;
    }
  }
  //Particles
  for (int q = blood.size() -1; q >= 0; q --) {
    blood.get(q).render();
    if (blood.get(q).life <= 0) {
      blood.remove(q);
    }
  }
  for (int q = confetti.size() -1; q >= 0; q --) {
    confetti.get(q).render();
    if (confetti.get(q).life <= 0) {
      confetti.remove(q);
    }
  }
  //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 == 6) {
    b1.bounceBoss();
  }
  if (stage == 4) {
    b1.bounceBox();
  }
  //Wall
  if (stage == 0) {
    wallX = 790 - difficulty*score*3;
  }
  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 BadBall {
  float ballX, ballY, baseSpeed, speed, ballSpeedX, ballSpeedY, ballSize;
  BadBall(float x, float y, float sp, float si) {
    ballX = x;
    ballY = y;
    baseSpeed = sp;
    ballSize = si;
    speed = baseSpeed;
  }
  void shoot() {
    ballX = boss.boxX;
    ballY = boss.boxY + boss.boxSize/2;
    ballSpeedX = -speed*cos(random(-QUARTER_PI, QUARTER_PI));
    ballSpeedY = -speed*sin(random(-QUARTER_PI, QUARTER_PI));
    shootNote();
  }
  void update() {
    rectMode(CENTER);
    stroke(255, 0, 0);
    strokeWeight(5);
    fill(0, 0, 0, 10);
    rect(ballX, ballY, ballSize, ballSize);
  }
  void move() {
    ballX += ballSpeedX;
    ballY += ballSpeedY;
    if (ballY <= 3 || ballY >= 597) {
      ballSpeedY = -ballSpeedY;
    }
    if (ballX >= wallX - 3) {
      ballSpeedX = -ballSpeedX;
    }
    if (ballX <= playerX + 10 && ballX >= playerX && ballY >= playerY - 51 && ballY <= playerY + 51) {
      ballStart = 0;
      deathCount ++;
      boss.boxLife = constrain(boss.boxLife + 1, 0, 20);
    }
  }
}
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);
        if (stage == 5) {
          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;
        totalTime += time;
        deathCount ++;
        lastTime = frameCount;
        score -= 10;
        score = constrain(score, 0, 100000);
        textSize(20);
        fill(255, 0, 0, 175);
        text("-10", playerX+5, playerY-20);
        if (stage == 4){
          for (int q = 0; q < initialBoxCount; q ++) {
            if (boxes.size() < initialBoxCount) {
              boxes.add(new Box(random(width/3, width - 33), random(3, height - 33), 60, 1, 4));
            }
          }
        }
      }
    }
  }
  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 ((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);
      }
    }
  }
  void bounceBoss() {
    if ((ballX >= boss.boxX && ballX <= boss.boxX + boss.boxSize && ballY >= boss.boxY 
      && ballY <= boss.boxY + 30 && boss.boxLife > 0 && ballSpeedY > 0) 
      || (ballX >= boss.boxX && ballX <= boss.boxX + boss.boxSize && ballY >= boss.boxY + boss.boxSize 
      && ballY <= boss.boxY + boss.boxSize - 30 && boss.boxLife > 0 && ballSpeedY < 0)) {
      ballSpeedY = -ballSpeedY;
      boss.boxLife --;
      rings[currentRing].call(ballX, ballY);
      hurtNote();
      for (int q = 0; q < 25; q ++) {
        blood.add(new ParticleBlood());
      }
    }
    if ((ballY >= boss.boxY && ballY <= boss.boxY + boss.boxSize && ballX >= boss.boxX 
      && ballX <= boss.boxX + 30 && boss.boxLife > 0 && ballSpeedX > 0) 
      || (ballY >= boss.boxY && ballY <= boss.boxY + boss.boxSize && ballX <= boss.boxX + boss.boxSize 
      && ballX >= boss.boxX + boss.boxSize - 30 && boss.boxLife > 0 && ballSpeedX < 0)) {
      ballSpeedX = -ballSpeedX;
      boss.boxLife --;
      rings[currentRing].call(ballX, ballY);
      hurtNote();
      for (int q = 0; q < 25; q ++) {
        blood.add(new ParticleBlood());
      }
    }
  }
}
class Boss {
  int timer = 0;
  int period = 120;
  int stageAppear;
  float boxX, boxY, boxSize, boxLife, baseSpeed, bossSpeed;
  Boss(float x, float y, float s, float l, int st) {
    boxX = x;
    boxY = y;
    boxSize = s;
    boxLife = l;
    stageAppear = st;
    bossSpeed = 2;
    baseSpeed = 2;
  }
  void life() {
    if (boxLife > 0 && stage == stageAppear) {
      rectMode(CORNER);
      fill(0);
      stroke(255, 0, 0);
      rect(boxX, boxY, boxSize, boxSize);
      period = int(map(boxLife, 1, 20, 30, 120));
      bossSpeed = bossSpeed/baseSpeed;
      baseSpeed = (140 - period)/10;
      bossSpeed = bossSpeed*baseSpeed;
    }
  }
  void move() {
    
    if ((boxY <= 5 && bossSpeed < 0) || ((boxY + boxSize) >= (height - 5) && bossSpeed > 0) ) {
      bossSpeed = -bossSpeed;
    }   
    boxY += bossSpeed;
  }
}
class Box {
  float boxX, boxY, boxSize, boxR, boxG, boxB, boxLife;
  int stageAppear;
  Box(float x, float y, float s, float l, int st) {
    boxX = x;
    boxY = y;
    boxSize = s;
    boxLife = l;
    boxR = random(0, 255);
    boxG = random(0, 255);
    boxB = random(0, 255);
    stageAppear = st;
  }
  void life() {
    if (boxLife > 0 && stage == stageAppear) {
      rectMode(CORNER);
      fill(255);
      stroke(boxR, boxG, boxB);
      rect(boxX, boxY, boxSize, boxSize);
    }
  }
}
class Confetti {
  float xPos, yPos, xSpeed, ySpeed, size, life;
  Confetti() {
    xPos = b1.ballX;
    yPos = b1.ballY;
    xSpeed = random(-5, 5);
    ySpeed = random(-5, 5);
    size = random(1, 7);
    life = random(60, 80);
  }
  void render() {
    rectMode(CENTER);
    stroke(random(0, 255), random(0, 255), random(0, 255));
    noFill();
    rect(xPos, yPos, size, size);
    life --;
    xPos += xSpeed;
    yPos += ySpeed;
  }
}
class ParticleBlood {
  float xPos, yPos, xSpeed, ySpeed, size, life;
  ParticleBlood() {
    xPos = b1.ballX;
    yPos = b1.ballY;
    xSpeed = random(-5, 5);
    ySpeed = random(-5, 5);
    size = random(1, 7);
    life = 60;
  }
  void render() {
    rectMode(CENTER);
    stroke(128, 0, 0);
    noFill();
    rect(xPos, yPos, size, size);
    life --;
    xPos += xSpeed;
    yPos += ySpeed;
  }
}
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();
    if (stage == 7) {
      for (int q = 0; q < 25; q ++) {
        confetti.add(new Confetti());
      }
    }
  }
  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 hurtNote() {
  out.pauseNotes();
  out.playNote(0, 0.5, "C3");
  out.resumeNotes();
}

void shootNote() {
  out.pauseNotes();
  out.playNote(0, 0.25, "F4");
  out.playNote(0, 0.25, "Ab4");
  out.resumeNotes();
}
void screen() {
  //bg
  if (stage != 7) {
    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);
    if (stage == 6) {
      text(int(boss.boxLife), 20, 100);
      stroke(0);
      fill(map(boss.boxLife, 1, 20, 255, 0), map(boss.boxLife, 1, 20, 0, 255), 0);
      rect(80, 80, map(boss.boxLife, 0, 20, 0, 500), 20);
    }
  } else {
    background(0);
    textSize(42);
    text("Total time: " + totalTime + " seconds", 150, 200);
    text("You died " + deathCount + " time(s)", 150, 300);
    text("FINAL SCORE: " + finalScore, 150, 400);
  }
}
void stageChange() {
  switch(stage) {
  case 0:
    if (score >= 30) {
      finalScore += score;
      totalTime += time;
      score = 0;
      time = 0;
      ballStart = 0;
      wallX = 790;
      stage ++;
    }
    break;
  case 1:
    if (time >= 25) {
      finalScore += score;
      totalTime += time;
      time = 0;
      score  = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 2:
    if (score >= 10) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 3:
    if (score >= 10) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 4:
    if (boxes.size() <= 0) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 5:
    if (score >= 5) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  case 6:
    if (boss.boxLife <= 0) {
      finalScore += score;
      totalTime += time;
      score = 0;
      ballStart = 0;
      stage ++;
    }
    break;
  }
}

 

Leave a Reply