/*
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;
}
}
}