xxxxxxxxxx
699
// // // Globals
let gameState = 'start'; // [start, menu, playing, saving,pause, gameover, leaderboard]
let debug = false;
let headSize = 28;
let bodySize = 16;
let game, play, leaderboard, quit;
let scores, namebox, savename;
// // Images
let tempBG;
let altBG;
let deadSprite;
let shipSprite;
let shipBullet;
let enemySprite;
let enemyBullet;
let asteroidSprite;
let cometSprite;
let firePowerSprite;
let shieldPowerSprite;
// // // Audio
// let menu_bgm;
let game_bgm;
let win_sound;
let lose_sound;
let player_shoot;
let player_hurt;
// let player_death;
// let power_sound;
// // // Scene
function preload() {
// scores = loadStrings('scores.csv');
scores = loadTable('scores.csv', 'csv', 'header');
// // Images
tempBG = loadImage('/img/bg1.png'); // temp
altBG = loadImage('/img/bg2.png')
deadSprite = loadImage('/img/explosion.png');
shipSprite = loadImage('/img/spaceship.png');
shipBullet = loadImage('/img/bullet.png');
enemySprite = loadImage('/img/enemy.png');
enemyBullet = loadImage('/img/bullet2.png');
asteroidSprite = loadImage('/img/asteroid.png');
cometSprite = loadImage('/img/comet.png');
firePowerSprite = loadImage('/img/powerred.png');
shieldPowerSprite = loadImage('/img/powerblue.png');
// // Audio
game_bgm = loadSound('/audio/background.mp3');
win_sound = loadSound('/audio/win.mp3');
lose_sound = loadSound('/audio/death.mp3');
player_shoot = loadSound('/audio/shoot.mp3');
player_hurt = loadSound('/audio/damage.mp3');
// player_death = loadSound('/audio/death.mp3');
// powersound = loadSound('/audio/powerup.mp3');
}
function setup() {
createCanvas(600, 480);
imageMode(CENTER);
textAlign(CENTER);
fill(255);
textSize(headSize);
// textFont();
// scores.sort('Score', 'desc');
// // Adjust audio volume
player_shoot.setVolume(0.1);
player_hurt.setVolume(0.25);
}
function draw() {
background(20);
// // Control current scene w/ state machine
switch (gameState) {
case 'start':
showStart();
break;
case 'menu':
showMenu();
break;
case 'playing':
// // Instantiate a game if necessary
if (!game || game.gameover) {
game = new Game();
}
game.display();
break;
case 'pause':
game.display();
showPause();
break;
case 'gameover':
game.display(); // show game under gameover
showGameover();
break;
case 'saving':
showSaveMenu();
break;
case 'leaderboard':
showLeaderboard();
break;
default:
console.log("State ???")
}
}
// // // Classes
class Game {
constructor() {
this.paused = false;
this.gameover = false;
this.score = 0;
this.time = 0;
this.spawnTimer = 0;
this.spawnInterval = 120;
this.player = new Player(width/2, height*0.75);
this.enemies = [new Enemy(200, -50)];
this.enemyBullets = [];
this.asteroids = [new Asteroid(400, -50)];
this.comets = [new Comet(-100, -100)];
this.obstacles = [this.enemies, this.asteroids, this.comets];
this.powerups = []; // WIP
}
display() {
this.update();
image(tempBG, width/2, height/2, width, height);
// // Display entities
for (let i=0; i < this.enemyBullets.length; i++) {
this.enemyBullets[i].display(); // on bottom
}
for (let i = 0; i < this.obstacles.length; i++) {
for (let j = 0; j < this.obstacles[i].length;
j++) {
this.obstacles[i][j].check(); // temp
this.obstacles[i][j].display();
}
// // Clear obstacles below player screen
this.obstacles[i] = this.obstacles[i].filter(
ob => ob.health > 0);
}
this.player.display(); // on top
// // Gameplay stats
fill(255);
textSize(14);
text("Health: " + this.player.health, width - 40, 18);
text("Score: " + this.score, width - 40, 36);
text("Time: " + Math.floor(this.time), width - 40, 54);
}
update() {
// // Skip update while paused/gameover
if (this.paused || this.gameover) {return}
// // Check for loss
if (!this.gameover && this.player.health <= 0) {
triggerGameover();
}
// // Game continues
this.time += 1/frameRate();
// // Clear enemy bullets below player screen
this.enemyBullets = this.enemyBullets.filter(
b => !b.spent && b.y < this.player.y + height);
// // Spawn new enemies
this.spawnTimer += 1;
if (this.spawnTimer >= this.spawnInterval) {
spawnObstacle(Math.floor(random(3)));
this.spawnTimer = 0;
// // Increase spawn frequency over time
if (this.spawnInterval > 20) {
this.spawnInterval -= 1;
}
}
}
}
class Player {
constructor(x, y) {
// // Gameplay attributes
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.radius = 32;
this.sprite = shipSprite;
this.firerate = 1;
this.health = 5;
this.iframes = 0;
this.firing = false;
this.shielded = false;
this.bullets = [];
}
display() {
this.update();
// // Add tint to show invulnerability
if (this.shielded) {tint(0, 200, 255)}
else {noTint()}
// // Show live/dead sprite based on health
if (this.health > 0)
image(this.sprite, this.x, this.y, 128, 128);
else
image(deadSprite, this.x, this.y, 128, 128);
noTint();
if (debug) {circle(this.x, this.y, this.radius*2)}
// // Display player bullets
for (let i = 0; i < this.bullets.length; i++) {
this.bullets[i].display();
}
}
update() {
if (gameRunning()) {
// // Horizontal movement
if (keyIsDown(LEFT_ARROW) === true &&
this.x > 0 + this.radius) {
this.vx = -3;
}
else if (keyIsDown(RIGHT_ARROW) === true &&
this.x < width - this.radius) {
this.vx = 3;
}
else {this.vx = 0}
// // Vertical movement
if (keyIsDown(UP_ARROW) === true &&
this.y > height*0.3) { // temp
this.vy = -2;
// if (this.y <= height*0.4) { // consider scroll
// console.log("Scroll up");
// }
}
else if (keyIsDown(DOWN_ARROW) === true &&
this.y < height - this.radius) {
this.vy = 2;
}
else {this.vy = 0}
// // Update position
this.x += this.vx;
this.y += this.vy;
// // Combat behaviour
if (game.enemies.length > 0) {
this.firing = true;
}
else {this.firing = false}
if (this.firing && triggerFire(this.firerate)) {
append(this.bullets, new Bullet(
this.x, this.y-this.radius, true));
playShoot();
}
// // Clear exhausted player bullets
this.bullets = this.bullets.filter(b =>
!b.spent && b.y > this.y - height);
// // Check if invincible
if (this.iframes >= 1) {
this.iframes -= 1;
}
else if (this.shielded) { // shielded w/ 0 frames left
this.shielded = false;
}
// // Check collision w/ enemy bullets
for (let i = 0; i < game.enemyBullets.length; i++) {
if (checkCollision(this, game.enemyBullets[i])) {
if (!game.enemyBullets[i].spent) {
game.enemyBullets[i].spent = true;
if (this.shielded) {return}
this.health -= 1;
playHurt();
if (this.health <= 0) {return}
this.iframes = 60;
this.shielded = true;
}
}
}
}
}
}
class Bullet {
constructor(x, y, friendly=false) {
this.x = x;
this.y = y;
this.radius = 12;
if (friendly)
this.sprite = shipBullet;
else
this.sprite = enemyBullet;
this.spent = false;
}
display() {
this.update();
if (this.sprite === shipBullet)
image(this.sprite, this.x, this.y, 24, 32);
else {
image(this.sprite, this.x, this.y, 32, 40)
this.radius = 6;
}
if (debug) {circle(this.x, this.y, this.radius*2)}
}
update() {
if (gameRunning() && onScreen(this)) {
this.y += this.sprite == shipBullet ? -2 : 1;
}
}
}
class Obstacle {
constructor(x, y) {
this.x = x;
this.y = y;
this.radius = 32;
this.health = 3;
}
display() {
this.update();
circle(this.x,this.y,this.radius*2);
}
update() {
if (gameRunning()) {
this.y += 0.5;
}
}
check() {
if (this.health <= 0) {return}
// // Flag obstacles below screen to be cleared
if (!onScreen(this)) {
// console.log("Destroy offscreen");
this.health = 0;
game.score += 5;
}
// // Check collision w/ player
if (checkCollision(this, game.player)) {
this.health = 0;
if (!game.player.shielded) {
game.score -= 10;
game.player.health -= 1;
playHurt();
if (game.player.health <= 0) {return}
game.player.iframes = 60;
game.player.shielded = true;
}
}
// // Check collision w/ player bullets
for (let i = 0; i < game.player.bullets.length; i++) {
if (checkCollision(this, game.player.bullets[i])) {
if (!game.player.bullets[i].spent) {
game.player.bullets[i].spent = true;
this.health -= 1;
if (this.health <= 0) {
game.score += 20;
}
}
}
}
}
}
class Enemy extends Obstacle{
constructor(x, y) {
// // Gameplay attributes
super(x, y);
this.radius = 32;
this.sprite = enemySprite;
this.firerate = 0.3;
this.offset = random(1000);
}
display() {
this.update();
image(this.sprite, this.x, this.y, 64, 60);
if (debug) {circle(this.x, this.y, this.radius*2)}
}
update() {
if (gameRunning() && onScreen(this)) {
this.x += sin((frameCount + this.offset) *
0.05) * 2;
this.y += 0.5;
if (triggerFire(this.firerate)) {
append(game.enemyBullets, new Bullet(
this.x, this.y+this.radius));
}
}
}
}
class Asteroid extends Obstacle {
constructor(x, y) {
super(x, y);
}
display() {
this.update();
image(asteroidSprite, this.x, this.y);
if (debug) {circle(this.x, this.y, this.radius*2)}
}
update() {
if (gameRunning()) {
this.y += 0.25;
}
}
}
class Comet extends Obstacle {
constructor(x, y) {
super(x, y);
}
display() {
this.update();
image(cometSprite, this.x, this.y);
if (debug) {circle(this.x, this.y, this.radius*2)}
}
update() {
if (gameRunning()) {
this.x += 1;
this.y += 1;
}
}
}
// // // Interaction
function mouseClicked() {
switch (gameState) {
case 'start':
gameState = 'menu';
createMenu();
break;
case 'menu':
break;
case 'playing':
break;
case 'pause':
if (debug) {
triggerGameover();
}
break;
case 'gameover':
gameState = 'saving';
createSaveMenu();
break;
case 'saving':
break;
case 'leaderboard':
break;
default:
console.log("Click ???")
}
}
function keyReleased() {
if (keyCode === ESCAPE) {
if (gameState === 'playing') {
// // Pause active game
game.paused = true;
gameState = 'pause';
game_bgm.pause();
// player_shoot.stop();
// player_hurt.stop();
}
else if (gameState === 'pause') {
// // Quit from pause
gameState = 'start';
game = 0;
}
}
if (keyCode === ENTER) {
if (gameState === 'pause') {
// // Resume from pause
game.paused = false;
gameState = 'playing';
game_bgm.play();
}
}
}
// // // Scene Displays
function showStart() {
textSize(headSize);
text("Click to start", width/2, height*0.2);
}
function createMenu() {
// // Play Button
play = createButton("Play");
play.size(width/5, height/15);
play.position((width-play.width)/2, height/2);
doStyle(play);
play.mousePressed(function() {
gameState = 'playing';
game_bgm.stop();
game_bgm.loop();
removeElements();
});
// // Leaderboard Button
leaderboard = createButton("Leaderboard");
leaderboard.size(width/5, height/15);
leaderboard.position((width-leaderboard.width)/2,
height*0.6);
doStyle(leaderboard);
leaderboard.mousePressed(function() {
gameState = 'leaderboard';
removeElements();
createLeaderboard();
});
// // Quit Button
quit = createButton("Quit");
quit.size(width/5, height/15);
quit.position((width-quit.width)/2, height*0.7);
doStyle(quit);
quit.mousePressed(function() {
gameState = 'start';
game = 0;
removeElements();
});
}
function showMenu() {
image(altBG, width/2, height/2, width, height);
textSize(headSize);
text("Main Menu", width/2, height*0.2);
}
function showPause() {
textSize(20);
text("PAUSED", width/2, height*0.4);
textSize(14);
text("Press ESC to quit", width*0.33, height*0.5);
text("Press ENTER to resume", width*0.66, height*0.5);
}
function showGameover() {
textSize(headSize);
text("GAME OVER", width/2, height*0.4);
textSize(bodySize);
if (game) {
text("Score: " + game.score, width/2, height*0.5);
}
else {
text("Score: NaN", width/2, height*0.5);
}
text("Click to continue", width/2, height*0.7);
}
function createSaveMenu() {
// // Text input for username
namebox = createInput();
namebox.position((width-namebox.width)/2, height*0.5);
// // Button to save name and score
savename = createButton("Save");
savename.position(width/2-savename.width*2, height*0.6);
savename.mousePressed(saveScore);
// // Quit Button (don't save)
quit = createButton("Quit");
quit.position(width/2+quit.width, height*0.6);
quit.mousePressed(function() {
gameState = 'start';
game = 0;
removeElements();
});
}
function showSaveMenu() {
textSize(headSize - 4);
text("Enter username to save score", width/2, height*0.4);
}
function createLeaderboard() {
// // Quit Button
quit = createButton("Quit");
quit.position((width-quit.width)/2, height*0.7);
quit.mousePressed(function() {
gameState = 'start';
game = 0;
removeElements();
});
}
function showLeaderboard() {
// console.log(scores);
tint(100);
image(altBG, width/2, height/2, width, height);
noTint();
textSize(headSize);
text("Leaderboard", width/2, height*0.2);
textSize(bodySize);
// // Leaderboard records
textAlign(LEFT);
for (let r = 0; r < scores.getRowCount(); r++) {
let name = scores.getString(r, 'Name');
let score = scores.getNum(r, 'Score');
text(`${r+1}. ${name} - ${score}`,
width*0.2, height*0.35 + 25*r);
}
textAlign(CENTER);
}
// // // // Helpers
// // Shorthand to check if game is active
function gameRunning() {
if (!game) {return false}
return !game.gameover && !game.paused;
}
// // Determine whether to shoot on current frame
function triggerFire(rate) {
let trigger = Math.floor(60 / rate);
return frameCount % trigger === 0;
}
// // Check if object is on-screen, with 100px margin
function onScreen(obj) {
if (obj.x < -100) {return false}
if (obj.x > width+100) {return false}
// // Ignore case of above current view
if (obj.y > height+100) {return false}
return true;
}
// // Check collision of two objects (circle bounding)
function checkCollision(one, two) {
if (dist(one.x, one.y, two.x, two.y) <=
(one.radius + two.radius)) {return true}
return false;
}
// // Save name and score of current round (WIP)
function saveScore() {
// let saver = createWriter('scores.csv');
let saveName = namebox.value().trim() || "N/A";
if (game) {
console.log(saveName, game.score);
// saver.print(`${saveName}, ${game.score}`);
}
else {
console.log(saveName, "No Score");
// saver.print(`${saveName}, No Score`);
}
// saver.close();
gameState = 'leaderboard';
removeElements();
// // Wait for score to save?
createLeaderboard();
}
// // Spawn a new enemy/asteroid/comet
function spawnObstacle(type) {
let newX = Math.floor(random(30, width - 30));
let newY = Math.floor(random(-50, -200));
let obs;
switch(type) {
case 0:
obs = new Enemy(newX, newY);
append(game.enemies, obs);
break;
case 1:
obs = new Asteroid(newX, newY);
append(game.asteroids, obs);
break;
case 2:
newX = newX % 30;
obs = new Comet(newX, newY);
append(game.comets, obs);
break;
default:
console.log("Spawn " + type);
}
append(game.obstacles[type], obs);
if (debug) {console.log("Spawn new " + type)}
}
// // Standardize button styling for menu
function doStyle(elem) {
// // Styling
elem.style("font-size", "16px");
elem.style("font-family", "sans-serif");
elem.style("background-color", "rgb(20,20,20)");
elem.style("color", "whitesmoke");
elem.style("cursor", "pointer");
// // Hover behaviour
elem.mouseOver(() => {
elem.style("background-color", "firebrick");
});
elem.mouseOut(() => {
elem.style("background-color", "rgb(20, 20, 20)");
});
}
// // Handle gameover
function triggerGameover() {
gameState = 'gameover';
game_bgm.stop();
// player_shoot.stop();
// player_hurt.stop();
if (game) {
game.gameover = true;
// // Play win sound for score (temp)
if (game.score > 500) {
win_sound.play();
return;
}
}
lose_sound.play();
}
// // Play shoot sound
function playShoot() {
if (!player_shoot.isPlaying()) {
player_shoot.stop();
player_shoot.play();
}
}
// // Play hurt sound
function playHurt() {
if (!player_hurt.isPlaying()) {
player_hurt.stop();
player_hurt.play();
}
}