/* Tianyao Liu
* Object-Oriented Toy: Bullet Rain
* Intro to Media Computation
*
* Press Z to start the game.
* Use Arrow Keys to move, Z to shoot, and Shift to focus.
* Defeat the enemy while getting hit as little as possible.
*/
boolean gameStart = false; //has the boss been spawned?
Background[] scrollingBackground = new Background[2]; //scrolling background
Player player; //player character
Enemy enemy; //enemy boss
PFont font; //font object for death counter
ArrayList<PlayerBullet> playerBullet = new ArrayList<PlayerBullet>(); //player bullets
ArrayList<EnemyBullet> enemyBullet = new ArrayList<EnemyBullet>(); //enemy bullets
ArrayList<Particle> particle = new ArrayList<Particle>(); //particles
void setup() {
size(800, 600);
frameRate(60);
noCursor();
rectMode(CENTER);
ellipseMode(CENTER);
colorMode(HSB, 360, 100, 100, 100);
player = new Player(); //create player
font = createFont("Cirno.ttf", 30); //create font object
textAlign(LEFT, TOP);
textFont(font);
textSize(30);
//create backgrounds
scrollingBackground[0] = new Background(-600);
scrollingBackground[1] = new Background(0);
}
void draw() {
background(0, 0, 0);
//update backgrounds
scrollingBackground[0].update();
scrollingBackground[1].update();
//display backgrounds
scrollingBackground[0].display();
scrollingBackground[1].display();
//spawn star particle every 4 frames
if (frameCount % 4 == 0) {
spawnParticle(random(0, 800), -50, 1, 90, random(2, 11));
}
//update and display particles
for (Particle i : particle) {
if (!i.unused) {
i.update();
i.display();
}
}
//update player
player.update();
//display player
player.display();
if (gameStart) {
enemy.update(); //update enemy
enemy.display(); //display enemy
//update and display player bullets
for (PlayerBullet i : playerBullet) {
if (!i.unused) {
i.update();
i.display();
}
}
//update and display enemy bullets
for (EnemyBullet i : enemyBullet) {
if (!i.unused) {
i.update();
i.display();
}
}
noStroke();
fill(0, 0, 100);
text("DEATHS: "+player.deaths, 10, 10); //death counter
//battle start animation for enemy
if (enemy.timer == 0) {
noStroke();
fill(0, 0, 100);
rect(400, 300, 900, 700);
spawnParticle(enemy.pos.x, enemy.pos.y, 3, 0, 0);
}
//death animation for enemy
if (!enemy.dead) {
if (enemy.hp <= 0 && enemy.timer > 0) {
enemy.dead = true;
//destroy all enemy bullets
for (EnemyBullet i : enemyBullet) {
if (!i.destroyed) {
i.destroyed = true;
}
}
noStroke();
fill(0, 0, 100);
rect(400, 300, 900, 700);
//explosion!
for (int j = 0; j < 8; j++) {
spawnParticle(enemy.pos.x, enemy.pos.y, 2, j*45, 5);
}
spawnParticle(enemy.pos.x, enemy.pos.y, 3, 0, 0);
}
}
}
}
//spawn a particle object with the following parameters: x position, y position, type, direction, velocity
void spawnParticle(float x, float y, int t, float d, float v) {
boolean recycle = false;
//check for unused particle objects and recycle if available
for (Particle i : particle) {
if (i.unused) {
i.recycle(x, y, t, d, v);
recycle = true;
break;
}
}
//create new particle object if no available unused objects
if (!recycle) {
particle.add(new Particle(x, y, t, d, v));
}
}
//key press events
void keyPressed() {
if (keyCode == RIGHT && !player.right) { //move right
player.right = true;
}
if (keyCode == LEFT && !player.left) { //move left
player.left = true;
}
if (keyCode == DOWN && !player.down) { //move down
player.down = true;
}
if (keyCode == UP && !player.up) { //move up
player.up = true;
}
if (keyCode == SHIFT && !player.focus) { //focus
player.focus = true;
}
if ((key == 'z' || key == 'Z') && !player.fire) { //fire
player.fire = true;
//pressing Z starts game
if (!gameStart) {
gameStart = true;
enemy = new Enemy(); //create enemy boss
}
}
}
//key release events
void keyReleased() {
if (keyCode == RIGHT && player.right) { //stop moving right
player.right = false;
}
if (keyCode == LEFT && player.left) { //stop moving left
player.left = false;
}
if (keyCode == DOWN && player.down) { //stop moving down
player.down = false;
}
if (keyCode == UP && player.up) { //stop moving up
player.up = false;
}
if (keyCode == SHIFT && player.focus) { //unfocus
player.focus = false;
}
if ((key == 'z' || key == 'Z') && player.fire) { //stop firing
player.fire = false;
}
}/* Background class
* Pieces of scrolling background; will constantly scroll downwards, moving above screen after passing bottom edge
*/
class Background {
float y; //y position
int col; //colour
Background(float yPos) {
y = yPos; //set initial y position
col = 0;
}
//update function
void update() {
y += 3; //move down 3 pixels every frame
col += 1; //change hue 1 unit every frame
//move back to top once it passes bottom
if (y == 600) {
y = -600;
}
if (col == 360) {
col = 0;
}
}
//display function
void display() {
strokeWeight(1);
stroke(col, 100, 100);
noFill();
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
rect(j*200, y+i*300, 50, 50);
}
}
for (int i = 0; i < 3; i++) {
ellipse(i*400, y+150, 50, 50);
}
for (int i = 0; i < 3; i++) {
ellipse(i*400, y+450, 50, 50);
}
for (int i = 0; i < 2; i++) {
rect(i*400+200, y+150, 50, 50);
}
for (int i = 0; i < 2; i++) {
rect(i*400+200, y+450, 50, 50);
}
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
quad(j*400+200, y+i*300-100, j*400+300, y+i*300, j*400+200, y+i*300+100, j*400+100, y+i*300);
}
}
for (int i = 0; i < 3; i++) {
ellipse(i*400, y+150, 200, 200);
}
for (int i = 0; i < 3; i++) {
ellipse(i*400, y+450, 200, 200);
}
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
line(j*400+200-50, y+i*300-50, j*400+200-25, y+i*300-25);
line(j*400+200+50, y+i*300-50, j*400+200+25, y+i*300-25);
line(j*400+200-50, y+i*300+50, j*400+200-25, y+i*300+25);
line(j*400+200+50, y+i*300+50, j*400+200+25, y+i*300+25);
}
}
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
line(j*400+200, y+i*300+150-50, j*400+200, y+i*300+150-25);
line(j*400+200+100, y+i*300+150, j*400+200+25, y+i*300+150);
line(j*400+200, y+i*300+150+50, j*400+200, y+i*300+150+25);
line(j*400+200-100, y+i*300+150, j*400+200-25, y+i*300+150);
}
}
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
line(j*400+25, y+i*300, j*400+100, y+i*300);
line(j*400+300, y+i*300, j*400+375, y+i*300);
}
}
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
line(j*400+200-175, y+i*300+150-125, j*400+200-25, y+i*300+150-25);
line(j*400+200+175, y+i*300+150-125, j*400+200+25, y+i*300+150-25);
line(j*400+200-175, y+i*300+150+125, j*400+200-25, y+i*300+150+25);
line(j*400+200+175, y+i*300+150+125, j*400+200+25, y+i*300+150+25);
}
}
}
}/* Enemy class
* The enemy boss; moves around shooting bullet patterns, and can be hit by player bullets; is destroyed when its health reaches 0
*/
class Enemy {
PVector pos; //position vector
int hp; //health/hitpoints
int pType; //pattern type
int timer, pTimer; //timer & pattern timer
int glow; //glow timer
int col; //colour
float calcNum1, calcNum2; //numbers used for various values in bullet patterns (e.g. angle, velocity)
boolean dead; //is enemy dead?
//initialize all variables
Enemy() {
pos = new PVector(400, 120);
hp = 0;
pType = 0;
timer = -120; //equivalent to 2 seconds of startup animation
pTimer = 0;
glow = 0;
calcNum1 = 0;
calcNum2 = 0;
dead = false;
}
//update function
void update() {
if (!dead) {
timer += 1; //increment timer
if (timer <= 0) { //startup animation
hp = (int)((120+timer)*30); //increase hp along with timer
col = (int)(0.05*hp); //colour corresponds to hp
} else { //in battle
//when hp drops below certain points (every 450 hp lost)
if (hp <= 3600-(pType+1)*450) {
pType += 1; //switch to next pattern
hp = 3600-pType*450;
pTimer = 0;
//destroy all enemy bullets
for (EnemyBullet i : enemyBullet) {
if (!i.destroyed) {
i.destroyed = true;
}
}
spawnParticle(pos.x, pos.y, 3, 0, 0);
}
//decrement glow timer
if (glow > 0) {
glow -= 1;
}
//colour corresponds to hp
col = (int)(0.05*hp);
//move enemy boss along figure 8 path
pos.x = wave(400, 960, 200, timer);
pos.y = wave(120, 480, 40, timer);
pattern(); //call bullet pattern function
}
//detect enemy boss/player bullet collision
for (PlayerBullet i : playerBullet) {
if (pos.dist(i.pos) < 45 && !i.destroyed) { //if collided
if (timer > 0) {
//decrement health and set glow to last 2 frames
hp -= 1;
glow = 2;
}
//destroy collided player bullet
i.destroyed = true;
i.lifeTime = 0;
}
}
}
}
//display function
void display() {
if (!dead) {
if (timer <= 0) { //startup animation
//draw enemy boss
noStroke();
fill(col, 100, 100, wave(wave(0, 480, 75, timer+120), 120, -15, frameCount));
ellipse(pos.x, pos.y, wave(wave(0, 480, 100, timer+120), 120, 5, frameCount), wave(wave(0, 480, 100, timer+120), 120, 5, frameCount));
fill(col, 25, 100, wave(0, 480, 50, timer+120));
quad(pos.x-28.284, pos.y-28.284, rotateX(pos.x-80, pos.y-70, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)), rotateY(pos.x-80, pos.y-70, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)), rotateX(pos.x-150, pos.y-80, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)), rotateY(pos.x-150, pos.y-80, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)), rotateX(pos.x-100, pos.y, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)), rotateY(pos.x-100, pos.y, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)));
triangle(pos.x-40, pos.y, rotateX(pos.x-90, pos.y+20, pos.x-40, pos.y, wave(0, 120, 15, frameCount-15)), rotateY(pos.x-90, pos.y+20, pos.x-40, pos.y, wave(0, 120, 15, frameCount-15)), rotateX(pos.x-100, pos.y+80, pos.x-40, pos.y, wave(0, 120, 15, frameCount-15)), rotateY(pos.x-100, pos.y+80, pos.x-40, pos.y, wave(0, 120, 15, frameCount-15)));
quad(pos.x+28.284, pos.y-28.284, rotateX(pos.x+80, pos.y-70, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)), rotateY(pos.x+80, pos.y-70, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)), rotateX(pos.x+150, pos.y-80, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)), rotateY(pos.x+150, pos.y-80, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)), rotateX(pos.x+100, pos.y, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)), rotateY(pos.x+100, pos.y, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)));
triangle(pos.x+40, pos.y, rotateX(pos.x+90, pos.y+20, pos.x+40, pos.y, wave(0, 120, -15, frameCount-15)), rotateY(pos.x+90, pos.y+20, pos.x+40, pos.y, wave(0, 120, -15, frameCount-15)), rotateX(pos.x+100, pos.y+80, pos.x+40, pos.y, wave(0, 120, -15, frameCount-15)), rotateY(pos.x+100, pos.y+80, pos.x+40, pos.y, wave(0, 120, -15, frameCount-15)));
fill(0, 0, 100, wave(0, 480, 100, timer+120));
ellipse(pos.x, pos.y, wave(0, 480, 80, timer+120), wave(0, 480, 80, timer+120));
//draw enemy health bar (circular)
noFill();
strokeWeight(3);
stroke(0, 0, 100);
strokeCap(SQUARE);
arc(pos.x, pos.y, 160, 160, -PI/2+(PI*(float)(3600-hp)/1800), 3*PI/2);
strokeCap(ROUND);
} else { //in battle
//draw enemy boss
noStroke();
fill(col, 100, 100, wave(75, 120, -15, frameCount));
ellipse(pos.x, pos.y, wave(100, 120, 5, frameCount), wave(100, 120, 5, frameCount));
fill(col, 25, 100, 50);
quad(pos.x-28.284, pos.y-28.284, rotateX(pos.x-80, pos.y-70, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)), rotateY(pos.x-80, pos.y-70, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)), rotateX(pos.x-150, pos.y-80, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)), rotateY(pos.x-150, pos.y-80, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)), rotateX(pos.x-100, pos.y, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)), rotateY(pos.x-100, pos.y, pos.x-28.284, pos.y-28.284, wave(0, 120, 15, frameCount)));
triangle(pos.x-40, pos.y, rotateX(pos.x-90, pos.y+20, pos.x-40, pos.y, wave(0, 120, 15, frameCount-15)), rotateY(pos.x-90, pos.y+20, pos.x-40, pos.y, wave(0, 120, 15, frameCount-15)), rotateX(pos.x-100, pos.y+80, pos.x-40, pos.y, wave(0, 120, 15, frameCount-15)), rotateY(pos.x-100, pos.y+80, pos.x-40, pos.y, wave(0, 120, 15, frameCount-15)));
quad(pos.x+28.284, pos.y-28.284, rotateX(pos.x+80, pos.y-70, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)), rotateY(pos.x+80, pos.y-70, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)), rotateX(pos.x+150, pos.y-80, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)), rotateY(pos.x+150, pos.y-80, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)), rotateX(pos.x+100, pos.y, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)), rotateY(pos.x+100, pos.y, pos.x+28.284, pos.y-28.284, wave(0, 120, -15, frameCount)));
triangle(pos.x+40, pos.y, rotateX(pos.x+90, pos.y+20, pos.x+40, pos.y, wave(0, 120, -15, frameCount-15)), rotateY(pos.x+90, pos.y+20, pos.x+40, pos.y, wave(0, 120, -15, frameCount-15)), rotateX(pos.x+100, pos.y+80, pos.x+40, pos.y, wave(0, 120, -15, frameCount-15)), rotateY(pos.x+100, pos.y+80, pos.x+40, pos.y, wave(0, 120, -15, frameCount-15)));
//yin-yang; will glow brighter when enemy hit
fill(0, 0, 100);
ellipse(pos.x, pos.y, 80, 80);
fill(col, 50-glow*25, 100);
arc(pos.x, pos.y, 80, 80, PI/2+(float)frameCount/60*PI, 3*PI/2+(float)frameCount/60*PI);
ellipse(rotateX(pos.x, pos.y-20, pos.x, pos.y, degrees((float)frameCount/60*PI)), rotateY(pos.x, pos.y-20, pos.x, pos.y, degrees((float)frameCount/60*PI)), 40, 40);
fill(0, 0, 100);
ellipse(rotateX(pos.x, pos.y+20, pos.x, pos.y, degrees((float)frameCount/60*PI)), rotateY(pos.x, pos.y+20, pos.x, pos.y, degrees((float)frameCount/60*PI)), 40, 40);
ellipse(rotateX(pos.x, pos.y-20, pos.x, pos.y, degrees((float)frameCount/60*PI)), rotateY(pos.x, pos.y-20, pos.x, pos.y, degrees((float)frameCount/60*PI)), 10, 10);
fill(col, 50-glow*25, 100);
ellipse(rotateX(pos.x, pos.y+20, pos.x, pos.y, degrees((float)frameCount/60*PI)), rotateY(pos.x, pos.y+20, pos.x, pos.y, degrees((float)frameCount/60*PI)), 10, 10);
//draw enemy health bar (circular)
noFill();
strokeWeight(3);
stroke(0, 0, 100);
strokeCap(SQUARE);
arc(pos.x, pos.y, 160, 160, -PI/2+(PI*(float)(3600-hp)/1800), 3*PI/2);
strokeCap(ROUND);
}
}
}
//enemy bullet pattern function; bullets determined by current pattern
void pattern() {
if (pType == 0) { //rapidly fire 2 expanding rings of bullets with different speeds; one ring accelerates
if (pTimer == 0) {
calcNum1 = 90;
}
if (pTimer % 20 == 0) { //every 20 frames
for (int i = 0; i < 10; i++) { //10 bullets per ring
spawnBullet(pos.x, pos.y, 30, 0, calcNum1+i*36, 4, 3, 0, 0);
spawnBullet(pos.x, pos.y, 20, 60, -calcNum1-18-i*36, 0, 6, 0.05, 0);
}
calcNum1 += 10;
}
}
if (pType == 1) { //rapidly fire 2 expanding rings of bullets with different speeds from a point near the enemy
if (pTimer % 20 == 0) { //every 20 frames
float tempX = random(pos.x-100, pos.x+100);
float tempY = random(pos.y-100, pos.y+100);
calcNum1 = random(0, 30);
for (int i = 0; i < 12; i++) { //12 bullets per ring
spawnBullet(tempX, tempY, 20, 120, calcNum1+i*30, 4, 4, 0, 0);
spawnBullet(tempX, tempY, 10, 180, calcNum1+i*30, 2, 2, 0, 0);
}
}
}
if (pType == 2) { //randomly fire lines of accelerating bullets that linger
if (pTimer % 10 == 0) { //every 10 frames
calcNum1 = random(angle(pos.x, pos.y, player.pos.x, player.pos.y)-120, angle(pos.x, pos.y, player.pos.x, player.pos.y)+120);
for (int i = 1; i < 9; i++) { //9 bullets per line
spawnBullet(pos.x, pos.y, 20, 240, calcNum1, 0, i, (float)i/30, 15);
}
}
}
if (pType == 3) { //fire 4 turning streams of bullets with varying spread, forming both spread-out and condensed formations
if (pTimer == 0) {
calcNum1 = 90;
calcNum2 = 0;
}
if (pTimer % 2 == 0) { //every 2 frames
for (int i = 0; i < 4; i++) { //4 bullets
spawnBullet(pos.x, pos.y, 10, 300, calcNum1+i*90, 4, 4, 0, 0);
}
calcNum2 += 0.5;
calcNum1 += calcNum2;
}
}
if (pType == 4) { //fire 3 turning streams of bullets and less often, 4 giant slow bullets
if (pTimer == 0) {
calcNum1 = 90;
calcNum2 = 90;
}
if (pTimer % 30 == 0) { //every 30 frames
for (int i = 0; i < 4; i++) { //4 bullets
spawnBullet(pos.x, pos.y, 90, 60, calcNum1+i*90, 0, 2, 0.01, 30);
}
calcNum1 += 25;
}
if (pTimer % 2 == 0) { //every 2 frames
for (int i = 0; i < 3; i++) { //3 bullets
spawnBullet(pos.x, pos.y, 10, 0, calcNum2+i*120, 3, 3, 0, 0);
}
calcNum2 -= 6;
}
}
if (pType == 5) { //fire 10 condensed streams of bullets with breaks for player to pass through
if (pTimer == 0) {
calcNum1 = 90;
calcNum2 = 90;
}
if (pTimer % 60 >= 0 && pTimer % 60 < 45 && pTimer % 2 == 0) { //every 2 frames, with a break every 60 frames lasting 15 frames
for (int i = 0; i < 4; i++) { //4 bullets
spawnBullet(pos.x, pos.y, 20, 120, calcNum1+i*90, 0, 8, 0.05, 0);
}
}
if (pTimer % 2 == 0) { //
calcNum1 -= 2;
}
if (pTimer % 40 >= 0 && pTimer % 40 < 20 && pTimer % 2 == 0) { //every 2 frames, with a break every 40 frames lasting 20 frames
for (int i = 0; i < 6; i++) { //6 bullets
spawnBullet(pos.x, pos.y, 10, 180, calcNum2+i*60, 4, 4, 0, 0);
}
calcNum2 += 1.5;
}
}
if (pType == 6) { //fire 2 lines of accelerating bullets while firing a stream of bullets aimed at the player with varying velocity
if (pTimer == 0) {
calcNum1 = 0;
}
if (pTimer % 10 == 0) { //every 10 frames
for (int i = 1; i < 5; i++) { //5 bullets per line
spawnBullet(pos.x+100, pos.y, 10, 240, calcNum1, i, i, 0, 0);
spawnBullet(pos.x-100, pos.y, 10, 240, 180-calcNum1, i, i, 0, 0);
}
calcNum1 += 7;
calcNum2 = wave(3, 360, 2, pTimer);
spawnBullet(pos.x, pos.y, 20, 300, angle(pos.x, pos.y, player.pos.x, player.pos.y), calcNum2, calcNum2, 0, 0); //1 aimed bullet
}
}
if (pType == 7) { //rapidly and randomly fire randomly coloured bullets with random sizes in random directions at random speeds
if (pTimer % 1 == 0) { //every frame
float tempVel = random(1, 6);
//2 bullets
spawnBullet(pos.x, pos.y, 10*(int)random(1, 4), 60*(int)random(0, 6), random(0, 360), tempVel, tempVel, 0.1, 0);
spawnBullet(pos.x, pos.y, 10*(int)random(1, 4), 60*(int)random(0, 6), random(0, 360), tempVel, tempVel, 0.1, 0);
}
}
pTimer += 1;
}
//spawn an enemy bullet object with the following parameters: x position, y position, size, colour, direction, velocity, maximum velocity, acceleration, acceleration delay
void spawnBullet(float x, float y, float s, int c, float d, float v, float mv, float a, int ad) {
boolean recycle = false;
//check for unused enemy bullet objects and recycle if available
for (EnemyBullet i : enemyBullet) {
if (i.unused) {
i.recycle(x, y, s, c, d, v, mv, a, ad);
recycle = true;
break;
}
}
//create new enemy bullet object if no available unused objects
if (!recycle) {
enemyBullet.add(new EnemyBullet(x, y, s, c, d, v, mv, a, ad));
}
}
//function used for sine wave based animations; adjustable base value, period in frames, and amplitude
float wave(float base, float period, float amp, int input) {
return(base+amp*sin(PI/(period/2)*input));
}
//function used for calculating a point's X position after rotating around another point a specified number of degrees
float rotateX(float pointX, float pointY, float originX, float originY, float degrees) {
return(originX+(pointX-originX)*cos(radians(degrees))-(pointY-originY)*sin(radians(degrees)));
}
//same as above but for Y position
float rotateY(float pointX, float pointY, float originX, float originY, float degrees) {
return(originY+(pointY-originY)*cos(radians(degrees))+(pointX-originX)*sin(radians(degrees)));
}
//function used to find the angle from one point to another point
float angle(float x1, float y1, float x2, float y2) {
float a = degrees(atan((y2-y1)/(x2-x1)));
if (x1 <= x2) {
return(a);
} else {
return(a+180);
}
}
}/* EnemyBullet class
* Enemy bullets; spawned by the enemy boss, will kill the player upon collision; highly customizable
*/
class EnemyBullet {
PVector pos; //position vector
PVector dir; //direction vector
float bulletSize; //bullet size
float vel; //velocity
float mVel; //maximum velocity
float accel; //acceleration
int timer; //timer
int delay; //delay timer
int col; //colour
boolean destroyed=false; //is bullet destroyed?
boolean unused; //is bullet object unused?
//initialize all variables; parameters: x position, y position, size, colour, direction, velocity, maximum velocity, acceleration, acceleration delay
EnemyBullet(float x, float y, float s, int c, float d, float v, float mv, float a, int ad) {
pos = new PVector(x, y);
dir = PVector.fromAngle(radians(d));
bulletSize = s;
vel = v;
mVel = mv;
accel = a;
delay = ad;
col = c % 360;
timer = -15;
destroyed = false;
unused = false;
}
//identical to constructor but for recycled objects
void recycle(float x, float y, float s, int c, float d, float v, float mv, float a, int ad) {
pos.set(x, y);
dir = PVector.fromAngle(radians(d));
bulletSize = s;
vel = v;
mVel = mv;
accel = a;
delay = ad;
col = c % 360;
timer = -15;
destroyed = false;
unused = false;
}
//update function
void update() {
if (!destroyed) {
//startup animation
if (timer < 0) {
timer += 1;
} else { //bullet active
//delay timer ended
if (delay == 0) {
vel += accel; //accelerate
if (vel > mVel) {
vel = mVel;
}
dir.setMag(vel);
pos.add(dir); //move bullet
} else {
delay -= 1; //decrement delay timer if still active
}
//check if off-screen
if ((pos.x < -bulletSize/2 || pos.x > 800+bulletSize/2 || pos.y < -bulletSize/2 || pos.y > 600+bulletSize/2) && !destroyed) {
destroyed = true;
unused = true;
}
}
} else { //bullet destroyed
timer += 1;
//delay timer ended
if (delay == 0) {
vel += accel; //accelerate
if (vel > mVel) {
vel = mVel;
}
} else {
delay -= 1; //decrement delay timer if still active
}
dir.setMag(vel);
pos.add(dir); //move bullet
//set to unused if destroyed for 15 frames
if (timer == 15) {
unused = true;
}
}
}
//display function
void display() {
if (!destroyed) {
//startup animation
if (timer < 0) {
noStroke();
fill(0, 0, 100, 100+(float)timer*6.666);
ellipse(pos.x, pos.y, bulletSize-(timer*bulletSize/15), bulletSize-(timer*bulletSize/15));
} else {
//draw bullet
strokeWeight(1+bulletSize/10);
stroke(col, 100, 100);
fill(0, 0, 100);
ellipse(pos.x, pos.y, bulletSize, bulletSize);
}
} else { //bullet destroyed
//draw destroyed bullet
strokeWeight((1+bulletSize/10)-timer*(1+bulletSize/10)/15);
stroke(col, 100, 100, 100-timer/6.666);
noFill();
ellipse(pos.x, pos.y, bulletSize, bulletSize);
}
}
}/* Particle class
* Particles; used for visual effects, have no effect on gameplay
*/
class Particle {
PVector pos; //position vector
PVector dir; //direction vector
int type; //particle type
int timer; //timer
float vel; //velocity
boolean unused; //is particle object unused?
//initialize all variables; parameters: x position, y position, type, direction, velocity
Particle(float x, float y, int t, float d, float v) {
pos = new PVector(x, y);
vel = v;
float angleInRadians=radians(d);
dir = PVector.fromAngle(angleInRadians);
dir.mult(vel);
type = t;
unused = false;
timer = 0;
}
//identical to constructor but for recycled objects
void recycle(float x, float y, int t, float d, float v) {
pos.set(x, y);
vel = v;
float angleInRadians=radians(d);
dir = PVector.fromAngle(angleInRadians);
dir.mult(vel);
type = t;
unused = false;
timer = 0;
}
//update function
void update() {
if (!unused) {
pos.add(dir); //move particle
if (type == 2) {
timer += 1;
if (timer == 100) { //disappears after 100 frames
unused = true;
}
}
if (type == 3) {
timer += 1;
if (timer == 30) { //disappears after 30 frames
unused = true;
}
}
//check if off-screen
if (pos.x < -100 || pos.x > 900 || pos.y < -100 || pos.y > 700) {
unused = true;
}
}
}
//display function
void display() {
if (!unused) {
if (type == 1) { //star particle; small, translucent white circles moving downwards from top
noStroke();
fill(0, 0, 100, 25+vel*2);
ellipse(pos.x, pos.y, vel, vel);
}
if (type == 2) { //explode particle; pulsating circles
noStroke();
fill(0, 0, 100, 100-timer);
ellipse(pos.x, pos.y, wave((float)(100-timer), 10, (float)(100-timer)/2, timer), wave((float)(100-timer), 10, (float)(100-timer)/2, timer));
}
if (type == 3) { //blast particle; rapidly expanding circle that fades
strokeWeight((float)(100-timer*3.333)/5);
stroke(0, 0, 100);
fill(0, 0, 100, 100-timer*3.333);
ellipse(pos.x, pos.y, timer*40, timer*40);
}
}
}
//function used for sine wave based animations; adjustable base value, period in frames, and amplitude
float wave(float base, float period, float amp, int input) {
return(base+amp*sin(PI/(period/2)*input));
}
}/* Player class
* The player-controlled entity; can be moved and can fire player bullets
*/
class Player {
PVector pos; //position vector
PVector vel; //velocity vector
int focusTimer; //focus timer for focus animation
int fireTimer; //fire timer for firing intervals
int timer; //timer
int col; //colour
int deaths; //number of deaths
boolean right; //move right
boolean left; //move left
boolean down; //move down
boolean up; //move up
boolean focus; //focus mode
boolean fire; //fire mode
boolean dead=false; //is player dead?
//initialize all variables
Player() {
pos = new PVector(400, 550);
vel = new PVector(0, 0);
focusTimer = 0;
fireTimer = 0;
timer = 60;
col = 0;
deaths = 0;
focus = false;
fire = false;
dead = false;
}
//update function
void update() {
vel.set(0, 0);
//change focus animation timer
if (focusTimer > 0 && !focus) {
focusTimer -= 1;
} else if (focusTimer < 10 && focus) {
focusTimer += 1;
}
//decrement firing timer
if (fireTimer > 0) {
fireTimer -= 1;
}
if (gameStart) {
//detect player/enemy bullet collision
for (EnemyBullet i : enemyBullet) {
if (dist(player.pos.x, player.pos.y, i.pos.x, i.pos.y) < 5+i.bulletSize/2.-(1+i.bulletSize/10.)/2. && !dead && !i.destroyed) { //if collided
dead = true; //die die die
timer = -60;
deaths += 1; //increment death counter
noStroke();
fill(0, 100, 100);
rect(400, 300, 900, 700);
for (int j = 0; j < 8; j++) { //explosion!
spawnParticle(pos.x, pos.y, 2, j*45, 5);
}
spawnParticle(pos.x, pos.y, 3, 0, 0);
pos = new PVector(400, 550); //reset position
}
}
//detect player/enemy collision; same death sequence as above
if (player.pos.dist(enemy.pos) < 45 && !dead && !enemy.dead && enemy.timer > 0) {
dead = true;
timer = -60;
deaths += 1;
noStroke();
fill(0, 100, 100);
rect(400, 300, 900, 700);
for (int j = 0; j < 8; j++) {
spawnParticle(pos.x, pos.y, 2, j*45, 5);
}
spawnParticle(pos.x, pos.y, 3, 0, 0);
pos = new PVector(400, 550);
}
}
if (!dead) {
if (focus) { //focused
//move 3 pixels in active direction
if (right) { //move right
vel.x += 3;
}
if (left) { //move left
vel.x -= 3;
}
if (down) { //move down
vel.y += 3;
}
if (up) { //move up
vel.y -= 3;
}
} else { //unfocused
//move 8 pixels in active direction
if (right) { //move right
vel.x += 8;
}
if (left) { //move left
vel.x -= 8;
}
if (down) { //move down
vel.y += 8;
}
if (up) { //move up
vel.y -= 8;
}
}
pos.add(vel); //move player
//movement bounds; cannot pass screen edges
if (pos.x < 20) {
pos.x = 20;
}
if (pos.x > 780) {
pos.x = 780;
}
if (pos.y < 20) {
pos.y = 20;
}
if (pos.y > 580) {
pos.y = 580;
}
//spawn 4 fast bullets moving upwards
if (fire && fireTimer == 0) {
spawnBullet(rotateX(pos.x, pos.y-50, pos.x, pos.y, -90+focusTimer*6), rotateY(pos.x, pos.y-50, pos.x, pos.y, -90+focusTimer*6), col);
spawnBullet(rotateX(pos.x, pos.y-50, pos.x, pos.y, 90-focusTimer*6), rotateY(pos.x, pos.y-50, pos.x, pos.y, 90-focusTimer*6), col);
spawnBullet(rotateX(pos.x, pos.y-50, pos.x, pos.y, -30+focusTimer*2), rotateY(pos.x, pos.y-50, pos.x, pos.y, -30+focusTimer*2), col);
spawnBullet(rotateX(pos.x, pos.y-50, pos.x, pos.y, 30-focusTimer*2), rotateY(pos.x, pos.y-50, pos.x, pos.y, 30-focusTimer*2), col);
fireTimer = 5; //may fire every 5 frames
}
} else {
timer += 1;
if (timer == 60) {
//destroy all enemy bullets on respawn
for (EnemyBullet i : enemyBullet) {
i.destroyed = true;
}
dead = false;
spawnParticle(pos.x, pos.y, 3, 0, 0);
}
}
//change colour slightly every frame
col += 1;
if (col == 360) {
col = 0;
}
}
//display function
void display() {
if (timer >= 0) {
noStroke();
//draw bullet spawn points
fill(col, 50-fireTimer*10, 100, wave(0, 240, 80+fireTimer*4, timer));
ellipse(rotateX(pos.x, pos.y-50, pos.x, pos.y, -90+focusTimer*6), rotateY(pos.x, pos.y-50, pos.x, pos.y, -90+focusTimer*6), 10+fireTimer, 10+fireTimer);
ellipse(rotateX(pos.x, pos.y-50, pos.x, pos.y, 90-focusTimer*6), rotateY(pos.x, pos.y-50, pos.x, pos.y, 90-focusTimer*6), 10+fireTimer, 10+fireTimer);
ellipse(rotateX(pos.x, pos.y-50, pos.x, pos.y, -30+focusTimer*2), rotateY(pos.x, pos.y-50, pos.x, pos.y, -30+focusTimer*2), 10+fireTimer, 10+fireTimer);
ellipse(rotateX(pos.x, pos.y-50, pos.x, pos.y, 30-focusTimer*2), rotateY(pos.x, pos.y-50, pos.x, pos.y, 30-focusTimer*2), 10+fireTimer, 10+fireTimer);
//draw player
fill(col, 100, 100, wave(wave(0, 240, 75, timer), 120, -15, frameCount));
ellipse(pos.x, pos.y, wave(wave(0, 240, 35, timer), 120, 3, frameCount), wave(wave(0, 240, 35, timer), 120, 3, frameCount));
fill(col, 25, 100, wave(0, 240, 50, timer));
triangle(pos.x-14.142, pos.y-14.142, rotateX(pos.x-50, pos.y-30, pos.x-14.142, pos.y-14.142, wave(0, 120, 15, frameCount)), rotateY(pos.x-50, pos.y-30, pos.x-14.142, pos.y-14.142, wave(0, 120, 15, frameCount)), rotateX(pos.x-40, pos.y-10, pos.x-14.142, pos.y-14.142, wave(0, 120, 15, frameCount)), rotateY(pos.x-40, pos.y-10, pos.x-14.142, pos.y-14.142, wave(0, 120, 15, frameCount)));
triangle(pos.x-20, pos.y, rotateX(pos.x-45, pos.y, pos.x-20, pos.y, wave(0, 120, 15, frameCount-10)), rotateY(pos.x-45, pos.y, pos.x-20, pos.y, wave(0, 120, 15, frameCount-10)), rotateX(pos.x-35, pos.y+10, pos.x-20, pos.y, wave(0, 120, 15, frameCount-10)), rotateY(pos.x-35, pos.y+10, pos.x-20, pos.y, wave(0, 120, 15, frameCount-10)));
triangle(pos.x-14.142, pos.y+14.142, rotateX(pos.x-30, pos.y+20, pos.x-14.142, pos.y+14.142, wave(0, 120, 15, frameCount-20)), rotateY(pos.x-30, pos.y+20, pos.x-14.142, pos.y+14.142, wave(0, 120, 15, frameCount-20)), rotateX(pos.x-20, pos.y+25, pos.x-14.142, pos.y+14.142, wave(0, 120, 15, frameCount-20)), rotateY(pos.x-20, pos.y+25, pos.x-14.142, pos.y+14.142, wave(0, 120, 15, frameCount-20)));
triangle(pos.x+14.142, pos.y-14.142, rotateX(pos.x+50, pos.y-30, pos.x+14.142, pos.y-14.142, wave(0, 120, -15, frameCount)), rotateY(pos.x+50, pos.y-30, pos.x+14.142, pos.y-14.142, wave(0, 120, -15, frameCount)), rotateX(pos.x+40, pos.y-10, pos.x+14.142, pos.y-14.142, wave(0, 120, -15, frameCount)), rotateY(pos.x+40, pos.y-10, pos.x+14.142, pos.y-14.142, wave(0, 120, -15, frameCount)));
triangle(pos.x+20, pos.y, rotateX(pos.x+45, pos.y, pos.x+20, pos.y, wave(0, 120, -15, frameCount-10)), rotateY(pos.x+45, pos.y, pos.x+20, pos.y, wave(0, 120, -15, frameCount-10)), rotateX(pos.x+35, pos.y+10, pos.x+20, pos.y, wave(0, 120, -15, frameCount-10)), rotateY(pos.x+35, pos.y+10, pos.x+20, pos.y, wave(0, 120, -15, frameCount-10)));
triangle(pos.x+14.142, pos.y+14.142, rotateX(pos.x+30, pos.y+20, pos.x+14.142, pos.y+14.142, wave(0, 120, -15, frameCount-20)), rotateY(pos.x+30, pos.y+20, pos.x+14.142, pos.y+14.142, wave(0, 120, -15, frameCount-20)), rotateX(pos.x+20, pos.y+25, pos.x+14.142, pos.y+14.142, wave(0, 120, -15, frameCount-20)), rotateY(pos.x+20, pos.y+25, pos.x+14.142, pos.y+14.142, wave(0, 120, -15, frameCount-20)));
fill(0, 0, 100, wave(0, 240, 80, timer));
ellipse(pos.x, pos.y, wave(0, 240, 30, timer), wave(0, 240, 30, timer));
//draw hitbox
fill(col, 100, 40, wave(0, 240, 100, timer));
ellipse(pos.x, pos.y, 10, 10);
fill(col, 100, 100, wave(0, 240, 100, timer));
ellipse(pos.x, pos.y, 8, 8);
fill(0, 0, 100, wave(0, 240, 100, timer));
ellipse(pos.x, pos.y, wave(6, 120, 1, frameCount), wave(6, 120, 1, frameCount));
}
}
//spawn a player bullet object with the following parameters: x position, y position, colour
void spawnBullet(float x, float y, int c) {
boolean recycle = false;
//check for unused player bullet objects and recycle if available
for (PlayerBullet i : playerBullet) {
if (i.unused) {
i.recycle(x, y, c);
recycle = true;
break;
}
}
//create new player bullet object if no available unused objects
if (!recycle) {
playerBullet.add(new PlayerBullet(x, y, c));
}
}
//function used for sine wave based animations; adjustable base value, period in frames, and amplitude
float wave(float base, float period, float amp, int input) {
return(base+amp*sin(PI/(period/2)*input));
}
//function used for calculating a point's X position after rotating around another point a specified number of degrees
float rotateX(float pointX, float pointY, float originX, float originY, float degrees) {
return(originX+(pointX-originX)*cos(radians(degrees))-(pointY-originY)*sin(radians(degrees)));
}
//same as above but for Y position
float rotateY(float pointX, float pointY, float originX, float originY, float degrees) {
return(originY+(pointY-originY)*cos(radians(degrees))+(pointX-originX)*sin(radians(degrees)));
}
}/* PlayerBullet class
* Player bullets; are fired from the player entity, and collide with the enemy boss to decrease its health
*/
class PlayerBullet {
PVector pos; //position vector
int lifeTime; //frames active
int destroyTimer; //destroy timer
int col; //colour
boolean destroyed; //is bullet destroyed?
boolean unused; //is bullet object unused?
//initialize all variables; parameters: x position, y position, colour
PlayerBullet(float x, float y, int c) {
pos = new PVector();
pos.set(x, y);
lifeTime = 0;
destroyTimer = 0;
col = c;
destroyed = false;
unused = false;
}
//identical to constructor but for recycled objects
void recycle(float x, float y, int c) {
pos.set(x, y);
lifeTime = 0;
destroyTimer = 0;
col = c;
destroyed = false;
unused = false;
}
//update function
void update() {
if (!destroyed) { //bullet active
pos.y -= 30; //move up 30 pixels every frame
lifeTime += 1;
//check if offscreen
if (pos.y < -100 && !destroyed) {
destroyed = true;
unused = true;
}
} else { //bullet destroyed
destroyTimer += 1;
//set to unused if destroyed for 15 frames
if (destroyTimer == 15) {
unused = true;
}
}
}
//display function
void display() {
if (!destroyed) { //bullet active
//draw bullet; tail grows longer with time active
noStroke();
fill(col, 50, 100, 50);
quad(pos.x, pos.y+5+lifeTime*4, pos.x-5, pos.y, pos.x, pos.y-5, pos.x+5, pos.y);
fill(0, 0, 100);
ellipse(pos.x, pos.y, 6, 6);
} else { //bullet destroyed
noStroke();
fill(col, 50-(float)destroyTimer*3.333, 100, 50);
//impact heightens and narrows
quad(pos.x, pos.y+5, pos.x-(5-(float)destroyTimer*0.333), pos.y, pos.x, pos.y-5-destroyTimer*4, pos.x+(5-(float)destroyTimer*0.333), pos.y);
}
}
}