xxxxxxxxxx
// This will be different in that we will just use the arrow keys to shoot
// and not the mouse
//TODO: Implement states (Gameover, menus, etc.)
//TODO: Implement networking.
//TODO: this.HasHit should be a var (methinks).
//TODO: Use a timer for drops and enemies.
//TODO: Make player zombie when killed (for effect).
//TODO: Add movement acceleration to enemy and player.
Array.prototype.remove = function(item) {
var index = this.indexOf(item);
if (index !== -1) this.splice(index, 1);
};
/**** Global Variables ****/
var myPlayer;
var thePlayers;
var theBullets;
var theEnemies;
var theDrops;
var NORTH = 1;
var EAST = 2;
var SOUTH = 4;
var WEST = 8;
// Not sure if these should be globals, but...
var spawnInterval;
var spawnSpeed;
var spawnModifier;
var dropInterval;
var dropSpeed;
var dropModifier;
var screenFade;
var showDebug;
var gamestate; // 0 = Main menu, 1 = Game, 2 = Gameover
function setup() {
// fill the window
createCanvas(window.innerWidth, window.innerHeight);
frameRate(60);
cursor(CROSS);
initialize();
}
function draw() {
if (gamestate === 1) {
gameUpdate();
gameDraw();
} else if (gamestate === 2) {
gameUpdate();
gameDraw();
gameoverDraw();
}
if (showDebug) {
debugDraw();
}
}
function keyPressed() {
switch (key) {
// For player direction.
case "w":
case "W":
myPlayer.direction |= NORTH;
break;
case "d":
case "D":
myPlayer.direction |= EAST;
break;
case "s":
case "S":
myPlayer.direction |= SOUTH;
break;
case "a":
case "A":
myPlayer.direction |= WEST;
break;
// Restart game if it's over
case "r":
case "R":
if (gamestate === 2) {
initialize();
}
break;
// Enable showDebug mode.
case "t":
case "T":
if (showDebug) {
showDebug = false;
} else {
showDebug = true;
}
break;
// Enemy Spawning (For Debugging!)
//case('z'): theEnemies.push(new Enemy());break;
//case('z'): spawnInterval = 100;break;
}
}
function keyReleased() {
// For player direction.
switch (key) {
case "w":
case "W":
myPlayer.direction ^= NORTH;
break;
case "d":
case "D":
myPlayer.direction ^= EAST;
break;
case "s":
case "S":
myPlayer.direction ^= SOUTH;
break;
case "a":
case "A":
myPlayer.direction ^= WEST;
break;
}
}
function mouseMoved() {
myPlayer.mouseLoc.x = mouseX;
myPlayer.mouseLoc.y = mouseY;
}
function mouseDragged() {
myPlayer.mouseLoc.x = mouseX;
myPlayer.mouseLoc.y = mouseY;
}
function mousePressed() {
myPlayer.weapon.firing = true;
}
function mouseReleased() {
// If the gun is an automatic, enable this.
// Fixes an issue with fully/semi auto guns.
if (myPlayer.weapon.type === 1) {
myPlayer.weapon.firing = false;
}
}
class Bullet {
/**** Constructors ****/
constructor(tempOwner) {
this.owner = tempOwner;
this.location = createVector(this.owner.location.x, this.owner.location.y); // Copy it so we don't modify the original owner's location
this.breadth = 10;
this.speed = 15.0;
this.damage = 1;
this.trajectory = p5.Vector.sub(this.owner.mouseLoc, this.location);
this.trajectory.normalize();
this.trajectory.mult(this.speed);
this.colNorm = color(25, 75, 200);
this.colCur = this.colNorm;
}
/**** Methods ****/
travel() {
// Bullet is moved avar the this.trajectory
this.location.add(this.trajectory);
}
stillAlive() {
// Delete bullet if it goes off screen.
// Also, a cheesy Portal reference.
// http://www.youtube.com/watch?v=8IGS9qY7xko
// Also, thanks amnon.owned from the Processing forums
// for the refactored code tip!
if (
this.location.y > height ||
this.location.y < 0 ||
this.location.x > width ||
this.location.x < 0
) {
this.destroy();
}
}
hasHit() {
for (var i = 0; i < theEnemies.length; i++) {
var e = theEnemies[i];
var distance = dist(
this.location.x,
this.location.y,
e.location.x,
e.location.y
);
if (distance < this.breadth / 2 + e.breadth / 2) {
e.takeDamage(this);
this.destroy();
break;
}
}
}
destroy() {
theBullets.remove(this);
}
/**** Update and Display ****/
update() {
this.travel();
this.stillAlive();
this.hasHit();
}
display() {
push();
ellipseMode(CENTER);
fill(this.colCur);
ellipse(this.location.x, this.location.y, this.breadth, this.breadth);
noFill();
pop();
}
}
class Drop {
constructor(i) {
this.location = createVector(
Math.floor(random(width)),
Math.floor(random(height))
);
this.breadth = 10;
this.type = i;
switch (i) {
// Health pack.
case 0:
this.colNorm = color(255, 118, 246);
break;
// SMG
case 1:
this.colNorm = color(255, 255, 0);
break;
}
}
hasHit() {
for (var i = 0; i < thePlayers.length; i++) {
var p = thePlayers[i];
var distance = dist(
this.location.x,
this.location.y,
p.location.x,
p.location.y
);
if (distance < this.breadth / 2 + p.breadth / 2) {
this.giveEffect(p);
this.destroy();
break;
}
}
}
giveEffect(p) {
switch (this.type) {
case 0:
p.heal(3);
break;
case 1:
p.weapon = new SMG(p);
break;
}
}
destroy() {
theDrops.remove(this);
}
update() {
this.hasHit();
}
display() {
fill(this.colNorm);
ellipse(this.location.x, this.location.y, this.breadth, this.breadth);
noFill();
}
}
class Enemy {
/**** Constructors ****/
constructor() {
this.location = this.generateSpawn();
this.target = this.getTarget();
this.speed = 1;
this.breadth = 18;
this.healthMax = 2;
this.healthCur = this.healthMax;
this.damage = 1;
this.hitTimer = new Timer(5);
this.colNorm = color(125, 75, 100);
this.colHit = color(255, 0, 0);
this.colCur = this.colNorm;
}
/**** Methods ****/
walk() {
if (gamestate === 1) {
// Enemy walks towards player.
this.trajectory = p5.Vector.sub(this.target.location, this.location);
this.trajectory.normalize();
this.trajectory.mult(this.speed);
}
this.location.add(this.trajectory);
}
hasHit() {
for (var i = 0; i < thePlayers.length; i++) {
var p = thePlayers[i];
if (p.alive === true) {
var distance = dist(
this.location.x,
this.location.y,
p.location.x,
p.location.y
);
// If there is a collision...
if (distance < this.breadth / 2 + p.breadth / 2) {
p.takeDamage(this);
this.destroy();
break;
}
}
}
}
takeDamage(b) {
this.healthCur -= b.damage;
if (this.healthCur <= 0) {
this.destroy();
b.owner.score++;
} else {
// Flash colors
this.colCur = this.colHit;
this.hitTimer.start();
}
}
colChange() {
if (this.colCur === this.colHit && this.hitTimer.isFinished()) {
this.colCur = this.colNorm;
}
}
destroy() {
theEnemies.remove(this);
}
shamble() {
this.speed = random(0.3, 0.6);
this.trajectory.set([random(-50, 50), random(-50, 50), 0]);
this.trajectory.normalize();
this.trajectory.mult(this.speed);
}
generateSpawn() {
var side;
var spawnX = 0;
var spawnY = 0;
side = Math.floor(random(4));
switch (side) {
case 0:
spawnX = Math.floor(random(width));
spawnY = 0;
break;
case 1:
spawnX = width;
spawnY = Math.floor(random(height));
break;
case 2:
spawnX = Math.floor(random(width));
spawnY = height;
break;
case 3:
spawnX = 0;
spawnY = Math.floor(random(height));
break;
}
return createVector(spawnX, spawnY);
}
getTarget() {
var targetId;
targetId = Math.floor(random(thePlayers.length));
return thePlayers[targetId];
}
/**** Update and Display ****/
update() {
this.walk();
this.hasHit();
this.colChange();
}
display() {
// Draw the enemy.
push();
ellipseMode(CENTER);
fill(this.colCur);
ellipse(this.location.x, this.location.y, this.breadth, this.breadth);
pop();
// Show health as number
fill(this.colNorm);
textAlign(CENTER);
text(this.healthCur, this.location.x, this.location.y - 25);
// Health Bar!
push();
stroke(0);
strokeWeight(1);
fill(255, 0, 0);
rect(this.location.x - 25, this.location.y - 20, 50, 5);
stroke(0, 255);
fill(0, 255, 0);
rect(
this.location.x - 25,
this.location.y - 20,
map(this.healthCur, 0, this.healthMax, 0, 50),
5
);
noFill();
pop();
}
}
/**** Alternate Enemies ****/
class FastEnemy extends Enemy {
/**** Constructors ****/
constructor() {
super();
this.location = this.generateSpawn();
this.speed = 1.9;
this.breadth = 18;
this.healthMax = 1;
this.healthCur = this.healthMax;
this.damage = 1;
this.colNorm = color(25, 255, 25);
this.colHit = color(255, 0, 0);
this.colCur = this.colNorm;
}
}
class BossEnemy extends Enemy {
/**** Constructors ****/
constructor() {
super();
this.location = this.generateSpawn();
this.speed = 1.8;
this.breadth = 125;
this.healthMax = 200;
this.healthCur = this.healthMax;
this.damage = 100;
this.colNorm = color(255, 0, 0);
this.colHit = color(255, 0, 0);
this.colCur = this.colNorm;
}
}
// Sets all globals to their defaults; handy for restarting.
function initialize() {
background(50);
showDebug = false;
gamestate = 1;
spawnInterval = 0.0;
spawnSpeed = 30.0;
spawnModifier = 0.05;
dropInterval = 0.0;
dropSpeed = 800.0;
dropModifier = 1;
screenFade = 0.0;
myPlayer = new Player();
thePlayers = [];
thePlayers.push(myPlayer);
theBullets = [];
theEnemies = [];
theDrops = [];
}
// Creates enemies on an varerval which decreases slowly.
// Should be a static method of "enemy" but Processing
// doesn't like static methods :(
function enemySpawn() {
if (spawnInterval < spawnSpeed) {
spawnInterval++;
} else {
spawnInterval = 0;
if (spawnSpeed > 15) {
spawnSpeed -= spawnModifier;
} else {
spawnSpeed = 15;
}
var enemyType;
enemyType = Math.floor(random(3));
switch (enemyType) {
case 0:
theEnemies.push(new Enemy());
break;
default:
theEnemies.push(new FastEnemy());
break;
}
}
}
// Creates drops on an varerval which decreases slowly.
// Should be a static method of "drop" but Processing
// doesn't like static methods :(
function dropSpawn() {
if (dropInterval < dropSpeed) {
dropInterval++;
} else {
dropInterval = 0;
if (dropSpeed > 300) {
dropSpeed -= dropModifier;
} else {
dropSpeed = 300;
}
var dropType;
dropType = Math.floor(random(3));
switch (dropType) {
case 0:
theDrops.push(new Drop(1));
break;
default:
theDrops.push(new Drop(0));
break;
}
}
}
function shambleTheZombies() {
for (var i = 0; i < theEnemies.length; i++) {
theEnemies[i].shamble();
}
}
// Adds a circle on the cursor, a line from the player to mouse,
// and some debug information at the top-left.
function debugDraw() {
fill(255);
textAlign(LEFT);
text("FPS: " + Number(frameRate()), 5, 40);
text("Drop Interval: " + dropInterval, 5, 55);
text("Drop Speed: " + dropSpeed, 5, 70);
text("Spawn Interval: " + spawnInterval, 5, 85);
text("Spawn Speed: " + spawnSpeed, 5, 100);
text("Game State: " + gamestate, 5, 115);
noFill();
fill(0);
/*
// Draws a line from each enemy to it's this.target.
for (var i = 0; i < theEnemies.length; i++) {
Enemy e = theEnemies[i];
line(e.location.x, e.location.y, e.target.location.x, e.target.location.y);
}
*/
noFill();
}
class Gun {
/**** Constructors ****/
constructor(tempOwner) {
this.owner = tempOwner;
this.firing = false;
// this.Type: 0 = SemiAuto, 1 = FullyAuto
this.type = 0;
}
/**** Methods *****/
fire() {
if (this.owner.alive) {
if (this.firing) {
theBullets.push(new Bullet(this.owner));
this.firing = false;
}
}
}
update() {
this.fire();
}
}
class Pistol extends Gun {
/**** Constructors ****/
constructor(tempOwner) {
super(tempOwner);
}
}
class SMG extends Gun {
/**** Constructors ****/
constructor(tempOwner) {
super(tempOwner);
this.type = 1;
this.fireRate = new Timer(115);
this.fireRate.start();
this.ammoMax = 100;
this.ammoCur = this.ammoMax;
}
/**** Methods *****/
// This fires the gun like an automatic weapon.
fire() {
if (this.owner.alive) {
if (this.firing && this.fireRate.isFinished() && this.ammoCur > 0) {
// Fire off a bullet and reduce ammo count.
theBullets.push(new Bullet(this.owner));
this.ammoCur--;
// If we have more ammo, restart the this.firing cooldown.
// Otherwise, give the player back the pistol.
if (this.ammoCur > 0) {
this.fireRate.start();
} else {
this.owner.weapon = new Pistol(this.owner);
}
}
}
}
}
// Timer class; currently used for hit flashing.
class Timer {
constructor(tempTimeLength) {
this.timeLength = tempTimeLength;
}
start() {
this.timeStarted = millis();
}
isFinished() {
var timePassed = millis() - this.timeStarted;
if (timePassed >= this.timeLength) {
return true;
} else {
return false;
}
}
}
class Player {
/**** Constructors ****/
constructor() {
this.location = createVector(width / 2, height / 2);
this.mouseLoc = createVector(0, 0);
this.direction = 0;
this.speed = 2;
this.breadth = 20;
this.alive = true;
this.healthMax = 10;
this.healthCur = this.healthMax;
this.score = 0;
this.weapon = new Pistol(this);
this.hitTimer = new Timer(25);
this.colNorm = color(200, 100, 0);
this.colHit = color(255, 0, 0);
this.colCur = this.colNorm;
}
/**** Methods ****/
walk() {
switch (this.direction) {
case NORTH:
this.location.y -= this.speed;
break;
case WEST:
this.location.x -= this.speed;
break;
case SOUTH:
this.location.y += this.speed;
break;
case EAST:
this.location.x += this.speed;
break;
/*
// Normalized?
case NORTH|WEST: this.location.y -= this.speed / 2; this.location.x -= this.speed / 2; break;
case NORTH|EAST: this.location.y -= this.speed / 2; this.location.x += this.speed / 2; break;
case SOUTH|WEST: this.location.y += this.speed / 2; this.location.x -= this.speed / 2; break;
case SOUTH|EAST: this.location.y += this.speed / 2; this.location.x += this.speed / 2; break;
*/
case NORTH | WEST:
this.location.y -= this.speed;
this.location.x -= this.speed;
break;
case NORTH | EAST:
this.location.y -= this.speed;
this.location.x += this.speed;
break;
case SOUTH | WEST:
this.location.y += this.speed;
this.location.x -= this.speed;
break;
case SOUTH | EAST:
this.location.y += this.speed;
this.location.x += this.speed;
break;
case NORTH | WEST | EAST:
this.location.y -= this.speed;
break;
case SOUTH | WEST | EAST:
this.location.y += this.speed;
break;
case NORTH | WEST | SOUTH:
this.location.x -= this.speed;
break;
case NORTH | SOUTH | EAST:
this.location.x += this.speed;
break;
}
this.location.x = constrain(
this.location.x,
0 + this.breadth / 2,
width - this.breadth / 2
);
this.location.y = constrain(
this.location.y,
0 + this.breadth / 2,
height - this.breadth / 2
);
}
takeDamage(e) {
this.healthCur = constrain(this.healthCur - e.damage, 0, this.healthMax);
if (this.healthCur <= 0) {
this.destroy();
} else {
// Flash colors
this.colCur = this.colHit;
this.hitTimer.start();
}
}
heal(healAmt) {
this.healthCur = constrain(this.healthCur + healAmt, 0, this.healthMax);
}
colChange() {
if (this.colCur === this.colHit && this.hitTimer.isFinished()) {
this.colCur = this.colNorm;
}
}
destroy() {
thePlayers.remove(this);
this.alive = false;
gamestate = 2;
for (var i = 0; i < thePlayers.length; i++) {
var p = thePlayers[i];
if (p.alive === true) {
gamestate = 1;
}
}
if (gamestate === 2) {
shambleTheZombies();
} else {
// If the game is still going, zombies choose a new this.target.
for (var i = 0; i < theEnemies.length; i++) {
var e = theEnemies[i];
if (e.target === this) {
e.target = e.getTarget();
}
}
}
}
/**** Update and Display ****/
update() {
this.walk();
this.colChange();
this.weapon.update();
}
display() {
// Draw the player.
fill(this.colCur);
ellipse(this.location.x, this.location.y, this.breadth, this.breadth);
// This code draws a rotating player - good for the future.
/*
push();
translate(this.location.x, this.location.y);
rotate(atan2(mouseY-this.location.y, mouseX-this.location.x));
fill(this.colCur);
ellipse(0, 0, this.breadth, this.breadth);
pop();
*/
// Show health as number.
fill(this.colNorm);
textAlign(CENTER);
text(this.healthCur, this.location.x, this.location.y - 25);
// Health Bar!
push();
stroke(0);
strokeWeight(1);
fill(255, 0, 0);
rect(this.location.x - 25, this.location.y - 20, 50, 5);
stroke(0, 255);
fill(0, 255, 0);
rect(
this.location.x - 25,
this.location.y - 20,
map(this.healthCur, 0, this.healthMax, 0, 50),
5
);
noFill();
pop();
// Only show the score for our player at the top.
if (this === myPlayer) {
fill(255);
textAlign(LEFT);
text("Score: " + this.score, 5, 20);
noFill();
}
}
}
function gameUpdate() {
/**** Miscellaneous ****/
background(50);
if (gamestate === 1) {
enemySpawn();
dropSpawn();
}
/**** Updates ****/
// Player
for (var i = 0; i < thePlayers.length; i++) {
thePlayers[i].update();
}
// Bullets
for (var i = 0; i < theBullets.length; i++) {
theBullets[i].update();
}
// Enemies
for (var i = 0; i < theEnemies.length; i++) {
theEnemies[i].update();
}
// Drops
for (var i = 0; i < theDrops.length; i++) {
theDrops[i].update();
}
}
function gameDraw() {
/**** Displays ****/
// Player
for (var i = 0; i < thePlayers.length; i++) {
thePlayers[i].display();
}
// Bullets
for (var i = 0; i < theBullets.length; i++) {
theBullets[i].display();
}
// Enemies
for (var i = 0; i < theEnemies.length; i++) {
theEnemies[i].display();
}
// Drops
for (var i = 0; i < theDrops.length; i++) {
theDrops[i].display();
}
}
function gameoverDraw() {
// Draw a big transparent square over the window and
// slowly make the square become less transparent.
fill(0, 0, 0, screenFade);
screenFade = constrain(screenFade + 0.8, 0, 255);
rect(0, 0, width, height);
noFill();
// Write the gameover text.
textAlign(CENTER);
textSize(24);
fill(255, 0, 0);
text("BRAAAAAAINS!", width / 2, 50);
textSize(16);
text("Score: " + myPlayer.score, width / 2, 75);
textSize(14);
text("Press R to restart.", width / 2, 100);
textSize(12);
noFill();
}