Your browser does not support the canvas tag.

previous        Show / Hide Source        Download        next
/*
Bullet Limbo
Shawn Morey
Oct 21st, 2016

Directions:
Use WASD to move the UFO and hold down SPACE to shoot
Pressing H will toggle the display of the exact hitboxes of the player/enemies
when you die you can click anywhere to restart the game 
*/

ArrayList<Bullet> bullets;
ArrayList<Enemy> enemies;
ArrayList<EnemyBullet> orbs;

Star listOfStars[];

boolean spacePressed;
boolean upPressed;
boolean downPressed;
boolean leftPressed;
boolean rightPressed;
boolean gameOver;

Ship player;
HUD healthBar;

Timer bulletTimer;
Timer enemyTimer;

void setup()
{
  size(600, 300);
  loop();

  bullets = new ArrayList<Bullet>();
  enemies = new ArrayList<Enemy>();
  orbs = new ArrayList<EnemyBullet>();

  //adds 100 stars to an array
  listOfStars = new Star[100];
  for (int i=0; i<listOfStars.length; i++)
  {
    listOfStars[i] = new Star();
  }

  player = new Ship();
  healthBar = new HUD();

  bulletTimer = new Timer(0.075 * 1000);
  bulletTimer.start();

  enemyTimer = new Timer(random(4, 6) * 1000);
  enemyTimer.start();

  spacePressed = false;
  upPressed = false;
  downPressed = false;
  leftPressed = false;
  rightPressed = false;
  gameOver = false;
}

void draw()
{
  //update
  updateStars();
  updateShip();
  updateBullets();
  updateEnemies();
  updateOrbs();
  healthBar.update();


  //display
  background(0);
  displayStars();
  displayShip();
  displayBullets();
  displayEnemies();
  displayOrbs();
  healthBar.display();
}

//remembers what keys were last pressed
void keyPressed()
{
  switch(key) 
  {
  case ' ':
    spacePressed = true;
    break;
  case 'w': 
    upPressed = true;
    break;
  case 's':
    downPressed = true;
    break;
  case 'a': 
    leftPressed = true;
    break;
  case 'd': 
    rightPressed = true;
    break;
  }
}

//remembers which keys were last released
void keyReleased()
{
  switch(key) 
  {
  case ' ':
    spacePressed = false;
    break;
  case 'w': 
    upPressed = false;
    break;
  case 's':
    downPressed = false;
    break;
  case 'a': 
    leftPressed = false;
    break;
  case 'd': 
    rightPressed = false;
    break;
  case 'h':
    //toggles visible hitboxes on the enemy and player
    healthBar.hitBoxOn = !healthBar.hitBoxOn;
    break;
  }
}

//clicking the mouse after a Game Over resets the game
void mousePressed()
{
  if (gameOver)
  {
    setup();
  }
}

void updateBullets()
{
  if (spacePressed)
  {
    //adds a new bullet after a set time interval
    if (bulletTimer.isFinished())
    {
      bullets.add(new Bullet()); 
      bulletTimer.start();
    }
  }

  //removes bullets that travel offscreen and bullets that collide with enemies
  //also lowers health of enemies collided with
  for (int i=bullets.size()-1; i>=0; i--) 
  {
    Bullet bullet = bullets.get(i);
    bullet.update();
    if (bullet.position.x < 0 || bullet.position.x > width || bullet.position.y < 0 ||bullet.position.y > height) 
      bullets.remove(i);

    for (int j=enemies.size()-1; j>=0; j--)
    {
      Enemy enemy = enemies.get(j);
      if (circleCircleIntersect(bullet.position.x, bullet.position.y, bullet.radius, enemy.position.x, enemy.position.y, enemy.radius))
      {
        bullets.remove(i);
        enemy.health--;
      }
    }
  }
}

void updateEnemies()
{
  //adds new enemies after a random amount of time in a randomly selected patern
  if (enemyTimer.isFinished())
  {
    switch((int) random(5))
    {
    case 0:
      enemies.add(new Enemy((int)random(3)));
      break;
    case 1:
      enemies.add(new Enemy(0));
      enemies.add(new Enemy(1));
      enemies.add(new Enemy(2));
      break;
    case 2:
      enemies.add(new Enemy(0));
      enemies.add(new Enemy(1));
      break;
    case 3:
      enemies.add(new Enemy(1));
      enemies.add(new Enemy(2));
      break;
    case 4:
      enemies.add(new Enemy(0));
      enemies.add(new Enemy(2));
      break;
    }

    enemyTimer = new Timer((int) random(4, 6) * 1000);
    enemyTimer.start();
  }

  //removes enemies that travel offscreen and enemies with no health left
  for (int i=enemies.size()-1; i>=0; i--)
  {
    Enemy enemy = enemies.get(i);
    enemy.update();
    if (enemy.position.x < -20)
      enemies.remove(i);
    if (enemy.health <= 0)
      enemies.remove(i);

    //checks if enemies have collided with the player
    playerCollisionHandler(enemy.position.x, enemy.position.y, enemy.radius);
  }
}

void updateStars()
{
  for (int i=0; i<listOfStars.length; i++)
  {
    listOfStars[i].update();
  }
}

void updateShip()
{
  player.update();
}

void updateOrbs()
{
  //removes enemy bullets that travel offscreen
  for (int i=orbs.size()-1; i>=0; i--)
  {
    EnemyBullet orb = orbs.get(i);
    orb.update();

    if (orb.position.x < -10 || orb.position.x > width + 10 || orb.position.y < -10 || orb.position.y > height + 10) 
      orbs.remove(i);

    //checks if any enemy bullets have collided with the player
    playerCollisionHandler(orb.position.x, orb.position.y, orb.radius);
  }
}

void displayBullets()
{
  for (int i=0; i<bullets.size(); i++)
  {
    Bullet bullet = bullets.get(i);
    bullet.display();
  }
} 

void displayEnemies()
{
  for (int i=0; i<enemies.size(); i++)
  {
    Enemy enemy = enemies.get(i);
    enemy.display();
  }
}

void displayStars()
{
  for (int i=0; i<listOfStars.length; i++)
  {
    listOfStars[i].display();
  }
}

void displayShip()
{
  player.display();
}

void displayOrbs()
{
  for (int i=orbs.size()-1; i>=0; i--)
  {
    EnemyBullet orb = orbs.get(i);
    orb.display();
  }
}

// FROM http://www.openprocessing.org/sketch/8005
boolean circleCircleIntersect(float cx1, float cy1, float cr1, float cx2, float cy2, float cr2) 
{
  if (dist(cx1, cy1, cx2, cy2) < cr1 + cr2) {
    return true;
  } else {
    return false;
  }
}

//if the player collides with enemies/enemy bullets, lowers their health, and grants brief invincibility
//also triggers a game over if the players health reaches zero  
void playerCollisionHandler(float tempX, float tempY, float tempR)
{
  if (circleCircleIntersect(player.position.x, player.position.y-player.offSet, player.radius, tempX, tempY, tempR) && player.isInvincible == false)
  {
    player.health--;
    if (player.health <= 0)
    {
      println("Yo have died: click anywhere to restart");
      gameOver = true;
      noLoop();
    }

    player.isInvincible = true;
    player.invincibility.start();
  } else if (player.isInvincible && player.invincibility.isFinished())
  {
    player.isInvincible = false;
  }
}
class Bullet
{
  float speed;
  float radius;
  float bulletSpread;
  PVector position;
  PVector velocity;
  PVector direction;

  Bullet()
  {
    speed = 5;
    radius = 3;
    bulletSpread = 3;

    //sets the bullets position in front of the player, and randomly angles the direction to create spread
    position = new PVector(player.position.x+30, player.position.y);
    direction = new PVector(1, 0);
    direction.rotate(random(-bulletSpread * PI/180, bulletSpread * PI/180));
    velocity = PVector.mult(direction, speed);
  }

  void update()
  {
    position.add(velocity);
  }

  void display()
  {
    ellipseMode(RADIUS);
    noStroke();
    
    fill(#6AB7F5,150);
    ellipse(position.x, position.y, radius, radius);
  }
}
class Enemy
{
  float speed;
  float maxHealth;
  float currentHealth;
  float startTime;
  float waitTime = 1.5;
  float radius = 15;

  int startPosition;
  int health = 12;

  boolean pointReached = false;

  PVector position;
  PVector velocity;

  Timer timer;

  Enemy(int startPositionTemp)
  {
    position=new PVector();
    speed = 1.5;
    startPosition = startPositionTemp;
    pickPosition();
    velocity = new PVector(-speed, 0);
    timer = new Timer(waitTime*1000);
  }

  void update()
  {
    position.add(velocity);

    //ship waits before continuing after passing 1/4th of the screen
    //after reaching that point, shoot 3 enemyBullets at the player in a wave
    if (position.x <= width*0.75 && pointReached == false)
    {
      velocity.x = 0;
      pointReached = true;
      timer.start();
      orbs.add(new EnemyBullet(position.x, position.y, 0));
      orbs.add(new EnemyBullet(position.x, position.y, 10 * PI/180));
      orbs.add(new EnemyBullet(position.x, position.y, -10 * PI/180));
    } else if (timer.isFinished())
    {
      velocity.x = -speed;
    }
  }

  void display()
  {
    ellipseMode(RADIUS);
    rectMode(RADIUS);
    noStroke();

    //feet
    fill(#D66A99);
    ellipse(position.x-10, position.y+10, 5, 5);
    ellipse(position.x+10, position.y+10, 5, 5);
    //base
    fill(#FF9BC6);
    ellipse(position.x, position.y, 15, 15);
    //top
    fill(#D66A99);
    rect(position.x, position.y+2.5, 20, 3, 0, 0, 3, 3);
    fill(#FF6CAC);
    arc(position.x, position.y, 20, 20, PI, PI*2);
    //lights
    fill(#FFB4D5);
    ellipse(position.x, position.y-10, 5, 5);
    ellipse(position.x-10, position.y-5, 2.5, 2.5);
    ellipse(position.x+10, position.y-5, 2.5, 2.5);
    //hitbox
    if (healthBar.hitBoxOn)
    {
      fill(0, 255, 0, 100);
      ellipse(position.x, position.y, radius, radius);
    }
  }

  //assigns the enemy what position to start in
  void pickPosition()
  {
    switch(startPosition)
    {
    case 0:
      position = new PVector(width+160, height*0.25);
      break;
    case 1:
      position = new PVector(width+160, height*0.5);
      break;
    case 2:
      position = new PVector(width+160, height*0.75);
      break;
    }
  }
}
class EnemyBullet
{
  float speed;
  float radius;

  PVector position;
  PVector velocity;
  PVector targetPosition;
  PVector direction;

  EnemyBullet(float tempX, float tempY, float tempRotation)
  {
    
    speed = 1.5;
    radius = 10;

    //move towards the player's last position plus an external angle
    position = new PVector(tempX, tempY);
    targetPosition =new PVector(player.position.x, player.position.y);
    direction = PVector.sub(targetPosition, position);
    direction.normalize();
    direction.rotate(tempRotation);
    velocity = PVector.mult(direction, speed);
  }

  void update()
  {
    position.add(velocity);
  }

  void display()
  {
    ellipseMode(RADIUS);
    noStroke();

    fill(#FA871C);
    ellipse(position.x, position.y, radius, radius);
    fill(#FFBF1C);
    ellipse(position.x, position.y, radius/2, radius/2);
  }
}
class HUD
{ 
  float healthFraction;
  
  boolean hitBoxOn = false;
  
  HUD()
  {
    healthFraction = player.health/player.maxHealth;
  }
  
  void update() 
  {
    healthFraction = player.health/player.maxHealth;
  }
  
  void display()
  {
    rectMode(CORNER);
    noStroke();
    
    fill(255,100);
    rect(10,275,120,15,10);
    
    fill(#0589FA,100);
    rect(10,275,120*healthFraction,15,10);
  }
}
class Ship
{
  PVector position;
  PVector velocity;

  float speed = 2;
  float friction = 0.9;
  float radius = 12;
  float offSet = 5;
  float iTime = 1;
  
  float maxHealth = 4;
  float health;
  
  boolean isInvincible = false;
  boolean flashing = false;
  
  Timer invincibility = new Timer(iTime*1000);

  Ship()
  {
    position = new PVector(50,height/2);
    velocity = new PVector(0,0);
    
    health = maxHealth;
  }

  void update()
  {
    if (keyPressed)
    {
      //move the ship when WASD is pressed. decelerate when keys are released
      if (upPressed)
        velocity.y = -speed;
      else if (downPressed)
        velocity.y = speed;
      else
        velocity.y *= friction;
        
      if (leftPressed)
        velocity.x = -speed;
      else if (rightPressed)
        velocity.x = speed;
      else
        velocity.x *= friction;
    }
    else
      velocity.mult(friction);
    
    position.add(velocity);
    
    //contrains the ship to within window borders
    if (position.x < 0)
      position.x = 0;
    else if (position.x > width)
      position.x = width;
    
    if (position.y < 0)
      position.y = 0;
    else if (position.y > height)
      position.y = height;
  }

  void display()
  {
    ellipseMode(RADIUS);
    rectMode(RADIUS);
    noStroke();
    
    //stops drawing ship every other frame while invincible
    if (isInvincible)
    {
      flashing = !flashing;
    }
    else 
      flashing = false;
    
    if (flashing == false)
    {
      //base
      fill(#5F8193);
      ellipse(position.x, position.y, 25, 8);
      //dome
      fill(#9EDEED);
      ellipse(position.x, position.y-10, 12, 12);
      fill(#C8EEF7);
      ellipse(position.x+4, position.y-16, 2.5, 2.5);
      //top
      fill(#87ADC1);
      ellipse(position.x, position.y-5, 30, 5);
      rect(position.x, position.y-2.5, 30, 5, 3, 3, 1, 1);
    }
    //hit box
    if (healthBar.hitBoxOn)
    {
      fill(0,255,0,100);
      ellipse(position.x,position.y-offSet,radius,radius);
    }
  }
}
class Star
{
  float size;
  float plannetChance;

  color plannetColor;

  boolean isAStar;

  PVector position;
  PVector velocity;

  Star()
  {
    plannetChance = 2.5;
    
    starOrPlannet();

    //assigns velocity and size based on if the object is a star or a plannet
    position = new PVector(random(width), random(height));
    if (isAStar)
    {
      size = random(2, 5);
      velocity = new PVector(random(-0.5, -0.25), 0);
    } else
    {
      size = random(30, 60);
      velocity = new PVector(random(-0.25, -0.125), 0);
    }
  }

  void update()
  {
    position.add(velocity);

    //if the star/plannet goes offscreen, reroll it and set it's x position to just beyond the right border
    if (position.x < -60)
    { 
      starOrPlannet();
      if (isAStar)
      {
        position = new PVector(width+10, random(height));
        velocity = new PVector(random(-0.5, -0.25), 0);
        size = random(2, 5);
      } else
      {
        position = new PVector(width+30, random(height));
        velocity = new PVector(random(-0.25, -0.125), 0);
        size = random(30, 60);
      }
    }
  }

  void display()
  {
    ellipseMode(CENTER);
    noStroke();

    if (isAStar)
    {
      fill(255, 255, 150);
    } else
    {
      fill(plannetColor);
    }
    ellipse(position.x, position.y, size, size);
  }

  //randomly picks the object to be a star or plannet, if it's a plannet assign it a random colour
  void starOrPlannet()
  {
    if (random(100) > plannetChance)
      isAStar = true;
    else
    {
      isAStar = false;
      plannetColor = color (random(50, 70), 40, random(50, 70));
    }
  }
}
// Learning Processing
// Daniel Shiffman
// http://www.learningprocessing.com

// Example 10-5: Object-oriented timer

class Timer {
 
  float savedTime; // When Timer started
  float totalTime; // How long Timer should last
  
  Timer(float tempTotalTime) {
    totalTime = tempTotalTime;
  }
  
  // Starting the timer
  void start() {
    // When the timer starts it stores the current time in milliseconds.
    savedTime = millis(); 
  }
  
  // The function isFinished() returns true if X ms have passed. 
  // The work of the timer is farmed out to this method.
  boolean isFinished() { 
    // Check how much time has passed
    float passedTime = millis()- savedTime;
    if (passedTime > totalTime) {
      return true;
    } else {
      return false;
    }
  }
}