You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1236 lines
37 KiB
1236 lines
37 KiB
/** |
|
* Copyright 2022 Jamie Munro, All rights reserved |
|
* CS5041 P1 |
|
* Game 1 |
|
*/ |
|
|
|
import java.util.Map; |
|
import java.util.Iterator; |
|
|
|
import processing.serial.*; |
|
import ddf.minim.*; |
|
|
|
Configuration conf; |
|
DisplayDimensions dimensions; |
|
|
|
Map<Integer, PImage> tileSet; //Maps tile id to related image (to avoid repeated/redundant image loads) |
|
Map<String, PImage[]> spriteSet; |
|
|
|
//sounds |
|
Minim minim; |
|
|
|
String activeSong; |
|
|
|
Map<String, AudioPlayer> music; |
|
Map<String, AudioPlayer> soundEffects; |
|
|
|
Controller controller; |
|
Serial port; |
|
|
|
Player player; |
|
Level level; |
|
TileProperties props; |
|
|
|
//viewport variables |
|
int startCol; |
|
int endCol; |
|
int startRow; |
|
int endRow; |
|
|
|
//game state |
|
boolean loading; |
|
boolean titleScreen; |
|
boolean story; |
|
boolean pause; |
|
boolean gameOver; |
|
|
|
//scores |
|
int score; |
|
int lives; |
|
int bombs; |
|
int difficulty; |
|
|
|
//timers |
|
long startTime; |
|
long pauseTime; |
|
long lastShot; |
|
long gameOverTime; |
|
long spawnTime; |
|
|
|
//story screen variables |
|
int storyCounter; |
|
float storyY; |
|
int storyLines; |
|
|
|
//rewards variables |
|
int nextLife; |
|
int nextBomb; |
|
|
|
//projectiles |
|
List<Projectile> playerProjectiles; |
|
List<Projectile> enemyProjectiles; |
|
Bomb bomb; |
|
|
|
//enemy variables |
|
List<Enemy> enemies; |
|
|
|
void setup() { |
|
fullScreen(P2D, 2); |
|
|
|
try { |
|
conf = new Configuration(); |
|
} |
|
catch (Exception e) { |
|
println("Unable to access valid configuration.properties file."); |
|
e.printStackTrace(); |
|
exit(); |
|
} |
|
dimensions = new DisplayDimensions(width, height, conf); |
|
|
|
frameRate(conf.framerate); |
|
|
|
controller = new MicrobitController(conf, false); |
|
|
|
try { |
|
//for (Object device : Serial.list()) { |
|
// println(device); |
|
//} |
|
port = new Serial(this, Serial.list()[conf.serialDevice], conf.baudRate); |
|
} |
|
catch (Exception e) { |
|
println("ERROR: could not access serial port. If your sure the device is connected and you have set the correct serialDevice in config.properties, try running as root/administrator."); |
|
e.printStackTrace(); |
|
exit(); |
|
} |
|
|
|
port.bufferUntil(conf.lineEnding); |
|
|
|
//intialise game state |
|
loading = true; |
|
titleScreen = false; |
|
story = false; |
|
pause = false; |
|
gameOver = false; |
|
|
|
storyY = dimensions.heightOffset + (dimensions.adjustedHeight/1.75); |
|
storyCounter = 0; |
|
storyLines = countLines(conf.storyCrawl); |
|
|
|
//initialise scores |
|
score = 0; |
|
lives = conf.initialLives; |
|
bombs = conf.initialBombs; |
|
difficulty = 0; |
|
|
|
//initialise enemies |
|
enemies = new ArrayList<Enemy>(); |
|
|
|
//initialise projectiles |
|
playerProjectiles = new ArrayList<Projectile>(); |
|
enemyProjectiles = new ArrayList<Projectile>(); |
|
bomb = null; |
|
|
|
//sounds |
|
minim = new Minim(this); |
|
|
|
//fonts |
|
PFont mono = createFont(sketchPath() + System.getProperty("file.separator") + conf.fontPath + System.getProperty("file.separator") + "andalemo.ttf", 128); |
|
textFont(mono); |
|
|
|
//perform loading |
|
thread("loadResources"); |
|
} |
|
|
|
/** |
|
* Called repeatedly, every 1/framerate seconds |
|
*/ |
|
void draw() { |
|
background(0); |
|
|
|
if (loading) { |
|
loadingScreen(); |
|
} |
|
else if (titleScreen) { |
|
titleScreen(); |
|
} |
|
else if (story) { |
|
storyScroll(); |
|
} |
|
else if (pause) { |
|
pause(); |
|
} |
|
else if (gameOver) { |
|
gameOver(); |
|
} |
|
else { |
|
game(); |
|
} |
|
|
|
if (!loading) { |
|
//music |
|
if ((activeSong == null) || (!music.get(activeSong).isPlaying())) { |
|
playRandomSong(); |
|
} |
|
} |
|
|
|
//println(frameRate); |
|
} |
|
|
|
void stop() { |
|
//Close Minim audio classes |
|
for (String song : music.keySet()) { |
|
music.get(song).close(); |
|
} |
|
|
|
for (String effect : soundEffects.keySet()) { |
|
soundEffects.get(effect).close(); |
|
} |
|
|
|
//Stop Minim before exiting |
|
minim.stop(); |
|
|
|
super.stop(); |
|
} |
|
|
|
void serialEvent(Serial p) { |
|
String input = p.readString(); |
|
controller.decode(input.trim()); |
|
} |
|
|
|
/** |
|
* Main game loop |
|
*/ |
|
void game() { |
|
updateViewportVariables(); |
|
|
|
//draw level background |
|
renderLevel(); |
|
|
|
//update and display projectiles |
|
updateProjectiles(); |
|
renderProjectiles(); |
|
|
|
//handle bombs |
|
if (bomb != null) { |
|
boolean shouldDelete = bomb.update(); |
|
renderBomb(); |
|
detectBombCollision(); |
|
|
|
if (shouldDelete) { |
|
bomb = null; |
|
} |
|
} |
|
|
|
//update and display enemies |
|
updateEnemies(); |
|
renderEnemies(); |
|
|
|
//update and display player |
|
updatePosition(); |
|
renderPlayer(); |
|
|
|
//draw HUD |
|
drawHud(); |
|
|
|
//handle button input |
|
buttons(); |
|
|
|
//detect collisions with enemies |
|
detectEnemyCollision(); |
|
|
|
//detect enemy projectile collisions |
|
detectEnemyProjectileCollision(); |
|
|
|
//detect player projectile collisions |
|
detectPlayerProjectileCollisions(); |
|
|
|
//spawn new enemies |
|
spawnEnemy(false); |
|
|
|
//update difficulty |
|
long currentTime = millis(); |
|
long secondsElapsed = (currentTime - startTime) / 1000; |
|
|
|
difficulty = (int) secondsElapsed / conf.difficultyInterval; |
|
|
|
checkForRewards(); |
|
} |
|
|
|
void updateViewportVariables() { |
|
//Calculate viewport bounds based on player position and viewport dimensions |
|
startCol = player.getX() - (conf.viewportWidth / 2); |
|
endCol = player.getX() + (conf.viewportWidth / 2); |
|
startRow = player.getY() - (conf.viewportHeight / 2); |
|
endRow = player.getY() + (conf.viewportHeight / 2); |
|
} |
|
|
|
/** |
|
* Renders the level for the player's current viewport |
|
* Based on example here: https://developer.mozilla.org/en-US/docs/Games/Techniques/Tilemaps/Square_tilemaps_implementation:_Scrolling_maps |
|
*/ |
|
void renderLevel() { |
|
//iterate through appropriate tiles |
|
for (int col = startCol; col <= endCol; col++) { |
|
for (int row = startRow; row <= endRow; row++) { |
|
Tile tile = this.level.getTile(col, row); |
|
PImage img = this.tileSet.get(tile.getId()); |
|
|
|
//Calculate co-orindates to draw tile at based on tile position and dimensions and account for any nessessary offset due to aspect ratio |
|
int x = dimensions.widthOffset + (col - startCol) * dimensions.tileWidth; |
|
int y = dimensions.heightOffset + (row - startRow) * dimensions.tileHeight; |
|
|
|
//render tile at specified location and dimenision |
|
image(img, x, y, dimensions.tileWidth, dimensions.tileHeight); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Update players position based on control input |
|
*/ |
|
void updatePosition() { |
|
//handle Y engines |
|
if (controller.accelerationY() < -conf.thresholdX) { |
|
player.fireMainEngine(); |
|
stopSoundEffect("reverse"); |
|
playSoundEffect("forwards"); |
|
} |
|
else if (controller.accelerationY() > conf.thresholdX) { |
|
player.fireReverseEngine(); |
|
stopSoundEffect("forwards"); |
|
playSoundEffect("reverse"); |
|
} |
|
else { |
|
stopSoundEffect("forwards"); |
|
stopSoundEffect("reverse"); |
|
player.shutdownYEngines(); |
|
} |
|
|
|
//handle rotation |
|
int headingDifference = controller.currentHeading() - controller.initialHeading(); |
|
|
|
if (Math.abs(headingDifference) > conf.headingThreshold) { |
|
playSoundEffect("rotate"); |
|
|
|
if (headingDifference < 0) { |
|
player.fireRightEngine(); |
|
} |
|
else { |
|
player.fireLeftEngine(); |
|
} |
|
} |
|
else { |
|
stopSoundEffect("rotate"); |
|
player.shutdownXEngines(); |
|
} |
|
|
|
float newX = player.getX() + (player.getMomentum() * sin(radians(player.getRotation()))); |
|
float newY = player.getY() + (player.getMomentum() * -cos(radians(player.getRotation()))); |
|
|
|
if (!Boolean.parseBoolean(level.getTile(Math.round(newX), Math.round(newY)).getProperty("solid"))) { |
|
player.setPosition((int) newX, (int) newY); |
|
} |
|
} |
|
|
|
/* |
|
* Update player position and draw to screen |
|
*/ |
|
void renderPlayer() { |
|
pushMatrix(); |
|
|
|
int x = dimensions.widthOffset + dimensions.adjustedWidth / 2; |
|
int y = dimensions.heightOffset + dimensions.adjustedHeight / 2; |
|
|
|
translate(x, y); |
|
rotate(radians(player.getRotation())); |
|
|
|
imageMode(CENTER); |
|
image(player.getSprite(), 0, 0, dimensions.tileWidth * player.getScaleFactor(), dimensions.tileHeight * player.getScaleFactor()); |
|
|
|
if(player.getSheild()) { |
|
tint(255, 126); |
|
image(spriteSet.get("player")[9], 0, 0, dimensions.tileWidth * player.getScaleFactor(), dimensions.tileHeight * player.getScaleFactor()); |
|
noTint(); |
|
} |
|
|
|
popMatrix(); |
|
} |
|
|
|
/** |
|
* Draw HUD elements |
|
*/ |
|
void drawHud() { |
|
//lives |
|
imageMode(CORNER); |
|
for (int i = 0; i < lives; i++) { |
|
int liveX = dimensions.startLivesX + (i * dimensions.tileWidth * 9); |
|
image(spriteSet.get("player")[0], liveX, dimensions.livesY, dimensions.tileWidth * 7, dimensions.tileHeight * 7); |
|
} |
|
|
|
//score |
|
textSize(dimensions.scoreSize); |
|
textAlign(CENTER); |
|
|
|
text(score, dimensions.scoreX, dimensions.scoreY); |
|
|
|
//momentum |
|
rectMode(CORNER); |
|
stroke(255); |
|
strokeWeight(1); |
|
noFill(); |
|
rect(dimensions.nMomentumX, dimensions.nMomentumY, dimensions.nMomentumWidth, dimensions.nMomentumHeight); |
|
rect(dimensions.pMomentumX, dimensions.pMomentumY, dimensions.pMomentumWidth, dimensions.pMomentumHeight); |
|
|
|
if (player.getMomentum() < 0) { |
|
fill(235, 28, 28); |
|
rect(dimensions.nMomentumX, dimensions.nMomentumY, dimensions.nMomentumWidth, (dimensions.nMomentumHeight/conf.maxRearMomentum) * Math.abs(player.getMomentum())); |
|
} |
|
else if (player.getMomentum() > 0) { |
|
fill(28, 235, 38); |
|
|
|
pushMatrix(); |
|
|
|
translate(dimensions.pMomentumX + dimensions.pMomentumWidth, dimensions.pMomentumY + dimensions.pMomentumHeight); |
|
rotate(radians(180)); |
|
|
|
rect(0, 0, dimensions.pMomentumWidth, (dimensions.pMomentumHeight/conf.maxForwardsMomentum) * player.getMomentum()); |
|
|
|
popMatrix(); |
|
} |
|
|
|
//back to defaults |
|
fill(255); |
|
strokeWeight(4); |
|
|
|
//bombs |
|
imageMode(CORNER); |
|
for (int i = 0; i < bombs; i++) { |
|
int bombX = dimensions.startBombsX + (i * dimensions.tileWidth * 9); |
|
image(spriteSet.get("bomb")[0], bombX, dimensions.bombsY, dimensions.tileWidth * 7, dimensions.tileHeight * 7); |
|
} |
|
} |
|
|
|
/** |
|
* Respond to button presses |
|
*/ |
|
void buttons() { |
|
if (controller.buttonC()) { |
|
stopAllSoundEffects(); |
|
playSoundEffect("pause"); |
|
pause = true; |
|
pauseTime = millis(); |
|
} |
|
|
|
if (controller.buttonA()) { |
|
shoot(); |
|
} |
|
|
|
if (controller.buttonB()) { |
|
bomb(); |
|
} |
|
} |
|
|
|
/** |
|
* Shoots a projectile if possible |
|
*/ |
|
void shoot() { |
|
if (playerProjectiles.size() - 1 <= conf.maxPlayerProjectiles) { |
|
long currentTime = millis(); |
|
if (currentTime - lastShot >= conf.reloadTime) { |
|
playSoundEffect("shoot"); |
|
|
|
int x = player.getX();// - Math.round((player.getScaleFactor() / 2) * sin(radians(player.getRotation()))); |
|
int y = player.getY();// + Math.round((player.getScaleFactor() / 2) * -cos(radians(player.getRotation()))); |
|
|
|
playerProjectiles.add(new Projectile(x, y, player.getRotation(), spriteSet.get("player")[10], 10, 1, 260, 2)); |
|
lastShot = currentTime; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Triggers a bomb if possible |
|
*/ |
|
void bomb() { |
|
if ((bomb == null) && (bombs > 0) && (!player.getSheild())) { |
|
playSoundEffect("bomb"); |
|
bomb = new Bomb(player.getX(), player.getY(), conf); |
|
bombs--; |
|
} |
|
} |
|
|
|
/** |
|
* Selects and spawns a random enemy based on difficulty level |
|
*/ |
|
void spawnEnemy(boolean debug) { |
|
long currentTime = millis(); |
|
if (currentTime - spawnTime > conf.spawnInterval) { |
|
spawnTime = currentTime; |
|
|
|
//select random starting position outside of viewport |
|
int col = int(random(0, level.getCols())); |
|
int row = int(random(0, level.getRows())); |
|
|
|
//ensure x is not inside viewport |
|
while ((col >= startCol) && (col <= endCol)) { |
|
col = int(random(0, level.getCols())); |
|
} |
|
|
|
//ensure y is not inside viewport |
|
while ((row >= startRow) && (row <= endRow)) { |
|
row = int(random(0, level.getRows())); |
|
} |
|
|
|
int r = int(random(0, 101)); |
|
|
|
String name = ""; |
|
|
|
if ((r < Boss.CHANCE_OF_SPAWNING) && (difficulty >= Boss.MINIMUM_DIFFICULTY)) { |
|
enemies.add(new Boss(col, row, spriteSet.get("boss"), spriteSet.get("boss")[2], level, player)); |
|
name = "Boss"; |
|
} |
|
else if ((r < Saucer.CHANCE_OF_SPAWNING) && (difficulty >= Saucer.MINIMUM_DIFFICULTY)) { |
|
enemies.add(new Saucer(col, row, spriteSet.get("saucer")[0], spriteSet.get("saucer")[1], level, player)); |
|
name = "Saucer"; |
|
} |
|
else if ((r < GunPlatform.CHANCE_OF_SPAWNING) && (difficulty >= GunPlatform.MINIMUM_DIFFICULTY)) { |
|
enemies.add(new GunPlatform(col, row, spriteSet.get("gunPlatform")[0], spriteSet.get("gunPlatform")[1], player)); |
|
name = "GunPlatform"; |
|
} |
|
else if ((r < Jellyfish.CHANCE_OF_SPAWNING) && (difficulty >= Jellyfish.MINIMUM_DIFFICULTY)) { |
|
enemies.add(new Jellyfish(col, row, spriteSet.get("jellyfish"), level, player)); |
|
name = "Jellyfish"; |
|
} |
|
else if ((r < Asteroid2.CHANCE_OF_SPAWNING) && (difficulty >= Asteroid2.MINIMUM_DIFFICULTY)) { |
|
enemies.add(new Asteroid2(col, row, spriteSet.get("asteroid")[2], level)); |
|
name = "Asteroid2"; |
|
} |
|
else if ((r < Asteroid1.CHANCE_OF_SPAWNING) && (difficulty >= Asteroid1.MINIMUM_DIFFICULTY)) { |
|
enemies.add(new Asteroid1(col, row, spriteSet.get("asteroid")[1], level)); |
|
name = "Asteroid1"; |
|
} |
|
else if ((r < Asteroid0.CHANCE_OF_SPAWNING) && (difficulty >= Asteroid0.MINIMUM_DIFFICULTY)) { |
|
enemies.add(new Asteroid0(col, row, spriteSet.get("asteroid")[0], level)); |
|
name = "Asteroid0"; |
|
} |
|
|
|
if (debug) { |
|
print(name); |
|
print(" spawned at: "); |
|
print(col); |
|
print(", "); |
|
println(row); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* update enemy positions |
|
*/ |
|
void updateEnemies() { |
|
for (Enemy enemy : enemies) { |
|
enemy.update(); |
|
if (enemy.projectile() != null) { |
|
enemyProjectiles.add(enemy.projectile()); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* render enemies within viewport |
|
*/ |
|
void renderEnemies() { |
|
for (Enemy enemy : enemies) { |
|
int col = enemy.getX(); |
|
int row = enemy.getY(); |
|
|
|
//only render if inside viewport |
|
if ((col >= startCol) && (col <= endCol) && (row >= startRow) && (row <= endRow)) { |
|
//Calculate co-orindates to draw tile at based on tile position and dimensions and account for any nessessary offset due to aspect ratio |
|
int x = dimensions.widthOffset + (col - startCol) * dimensions.tileWidth; |
|
int y = dimensions.heightOffset + (row - startRow) * dimensions.tileHeight; |
|
|
|
pushMatrix(); |
|
translate(x,y); |
|
rotate(radians(enemy.getRotation())); |
|
imageMode(CENTER); |
|
tint(enemy.getTintR(), enemy.getTintG(), enemy.getTintB()); |
|
image(enemy.getSprite(), 0, 0, dimensions.tileWidth * enemy.getScaleFactor(), dimensions.tileHeight * enemy.getScaleFactor()); |
|
popMatrix(); |
|
|
|
noTint();//return to default |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* update projectile positions |
|
*/ |
|
void updateProjectiles() { |
|
List<List<Projectile>> projectileLists = new ArrayList<List<Projectile>>(); |
|
projectileLists.add(playerProjectiles); |
|
projectileLists.add(enemyProjectiles); |
|
|
|
for(List<Projectile> projectiles: projectileLists) { |
|
Iterator<Projectile> it = projectiles.iterator(); |
|
while(it.hasNext()) { |
|
Projectile projectile = it.next(); |
|
boolean shouldDelete = projectile.update(); |
|
if (shouldDelete) it.remove(); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* render projectiles within viewport |
|
*/ |
|
void renderProjectiles() { |
|
List<List<Projectile>> projectileLists = new ArrayList<List<Projectile>>(); |
|
projectileLists.add(playerProjectiles); |
|
projectileLists.add(enemyProjectiles); |
|
|
|
|
|
for (List<Projectile> projectiles: projectileLists) { |
|
for (Projectile projectile : projectiles) { |
|
int col = projectile.getX(); |
|
int row = projectile.getY(); |
|
|
|
//only render if inside viewport |
|
if ((col >= startCol) && (col <= endCol) && (row >= startRow) && (row <= endRow)) { |
|
//Calculate co-orindates to draw tile at based on tile position and dimensions and account for any nessessary offset due to aspect ratio |
|
int x = dimensions.widthOffset + (col - startCol) * dimensions.tileWidth; |
|
int y = dimensions.heightOffset + (row - startRow) * dimensions.tileHeight; |
|
|
|
pushMatrix(); |
|
translate(x,y); |
|
rotate(radians(projectile.getRotation())); |
|
imageMode(CENTER); |
|
image(projectile.getSprite(), 0, 0, dimensions.tileWidth * projectile.getScaleFactor(), dimensions.tileHeight * projectile.getScaleFactor()); |
|
popMatrix(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* detect collisions between player projectiles and enemies |
|
*/ |
|
void detectPlayerProjectileCollisions() { |
|
List<Enemy> newEnemies = new ArrayList<Enemy>(); |
|
|
|
Iterator<Enemy> enemyIterator = enemies.iterator(); |
|
while(enemyIterator.hasNext()) { |
|
Enemy enemy = enemyIterator.next(); |
|
|
|
Iterator<Projectile> projectileIterator = playerProjectiles.iterator(); |
|
while(projectileIterator.hasNext()) { |
|
Projectile projectile = projectileIterator.next(); |
|
|
|
//https://silentmatt.com/rectangle-intersection/ |
|
int eX1 = enemy.getX() - (enemy.getScaleFactor() / 2); |
|
int eY1 = enemy.getY() - (enemy.getScaleFactor() / 2); |
|
int eX2 = enemy.getX() + (enemy.getScaleFactor() / 2); |
|
int eY2 = enemy.getY() + (enemy.getScaleFactor() / 2); |
|
|
|
int pX1 = projectile.getX() - (projectile.getScaleFactor() / 2); |
|
int pY1 = projectile.getY() - (projectile.getScaleFactor() / 2); |
|
int pX2 = projectile.getX() + (projectile.getScaleFactor() / 2); |
|
int pY2 = projectile.getY() + (projectile.getScaleFactor() / 2); |
|
|
|
if ((eX1 < pX2) && (eX2 > pX1) && (eY1 < pY2) && (eY2 > pY1)) { |
|
for (int i = 0; i < projectile.getDamage(); i++) { |
|
if(enemy.hit()) { |
|
//DESTROYED! |
|
//TODO:ANIMATION |
|
playSoundEffect("explosion"); |
|
score += enemy.getScore(); |
|
List<Enemy> spawnlings = enemy.spawnlings(); |
|
if (spawnlings != null) newEnemies.addAll(spawnlings); |
|
enemyIterator.remove(); |
|
break; |
|
} |
|
} |
|
projectileIterator.remove(); |
|
} |
|
} |
|
} |
|
|
|
enemies.addAll(newEnemies); |
|
} |
|
|
|
/** |
|
* detect collisions between enemy projectiles and player |
|
*/ |
|
void detectEnemyProjectileCollision() { |
|
//player cannot be hit whilst sheild is up |
|
if(!player.getSheild()) { |
|
Iterator<Projectile> projectileIterator = enemyProjectiles.iterator(); |
|
while(projectileIterator.hasNext()) { |
|
Projectile projectile = projectileIterator.next(); |
|
|
|
//https://silentmatt.com/rectangle-intersection/ |
|
int eX1 = projectile.getX() - (projectile.getScaleFactor() / 2); |
|
int eY1 = projectile.getY() - (projectile.getScaleFactor() / 2); |
|
int eX2 = projectile.getX() + (projectile.getScaleFactor() / 2); |
|
int eY2 = projectile.getY() + (projectile.getScaleFactor() / 2); |
|
|
|
int pX1 = player.getX() - (player.getScaleFactor() / 2); |
|
int pY1 = player.getY() - (player.getScaleFactor() / 2); |
|
int pX2 = player.getX() + (player.getScaleFactor() / 2); |
|
int pY2 = player.getY() + (player.getScaleFactor() / 2); |
|
|
|
if ((eX1 < pX2) && (eX2 > pX1) && (eY1 < pY2) && (eY2 > pY1)) { |
|
//if ((eX == player.getX()) && (eY == player.getY())) { |
|
death(); |
|
projectileIterator.remove(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* detect collisions between the player and enemies |
|
*/ |
|
void detectEnemyCollision() { |
|
//player cannot die whilst sheild is up |
|
if(!player.getSheild()) { |
|
List<Enemy> newEnemies = new ArrayList<Enemy>(); |
|
|
|
Iterator<Enemy> enemyIterator = enemies.iterator(); |
|
while(enemyIterator.hasNext()) { |
|
Enemy enemy = enemyIterator.next(); |
|
|
|
//https://silentmatt.com/rectangle-intersection/ |
|
int eX1 = enemy.getX() - (enemy.getScaleFactor() / 2); |
|
int eY1 = enemy.getY() - (enemy.getScaleFactor() / 2); |
|
int eX2 = enemy.getX() + (enemy.getScaleFactor() / 2); |
|
int eY2 = enemy.getY() + (enemy.getScaleFactor() / 2); |
|
|
|
int pX1 = player.getX() - (player.getScaleFactor() / 2); |
|
int pY1 = player.getY() - (player.getScaleFactor() / 2); |
|
int pX2 = player.getX() + (player.getScaleFactor() / 2); |
|
int pY2 = player.getY() + (player.getScaleFactor() / 2); |
|
|
|
if ((eX1 < pX2) && (eX2 > pX1) && (eY1 < pY2) && (eY2 > pY1)) { |
|
if(enemy.hit()) { |
|
List<Enemy> spawnlings = enemy.spawnlings(); |
|
if (spawnlings != null) newEnemies.addAll(spawnlings); |
|
enemyIterator.remove(); |
|
} |
|
death(); |
|
} |
|
} |
|
|
|
enemies.addAll(newEnemies); |
|
} |
|
} |
|
|
|
/* |
|
* Render bomb |
|
*/ |
|
void renderBomb() { |
|
int col = bomb.x; |
|
int row = bomb.y; |
|
|
|
int x = dimensions.widthOffset + (col - startCol) * dimensions.tileWidth; |
|
int y = dimensions.heightOffset + (row - startRow) * dimensions.tileHeight; |
|
|
|
noFill(); |
|
strokeWeight(16); |
|
ellipseMode(CENTER); |
|
|
|
float diameter = bomb.getDiameter() * dimensions.tileWidth; |
|
|
|
ellipse(x, y, diameter, diameter); |
|
|
|
strokeWeight(4); |
|
fill(255); |
|
} |
|
|
|
/* |
|
* Destory enemies hit by bomb |
|
*/ |
|
void detectBombCollision() { |
|
List<Enemy> newEnemies = new ArrayList<Enemy>(); |
|
|
|
Iterator<Enemy> enemyIterator = enemies.iterator(); |
|
while(enemyIterator.hasNext()) { |
|
Enemy enemy = enemyIterator.next(); |
|
|
|
if (sqrt((float) Math.pow((enemy.getX() - bomb.x), 2) + (float) Math.pow((enemy.getY() - bomb.y), 2)) < bomb.getDiameter()/2) { |
|
score += enemy.getScore(); |
|
List<Enemy> spawnlings = enemy.spawnlings(); |
|
if (spawnlings != null) newEnemies.addAll(spawnlings); |
|
enemyIterator.remove(); |
|
} |
|
} |
|
|
|
enemies.addAll(newEnemies); |
|
} |
|
|
|
/* |
|
* Called when the player dies |
|
*/ |
|
void death() { |
|
player.hit(); |
|
|
|
playerProjectiles = new ArrayList<Projectile>(); |
|
enemyProjectiles = new ArrayList<Projectile>(); |
|
lives--; |
|
|
|
if (lives == 0) { |
|
stopAllSoundEffects(); |
|
playSoundEffect("death"); |
|
gameOverTime = millis(); |
|
gameOver = true; |
|
} |
|
else { |
|
playSoundEffect("shield"); |
|
} |
|
} |
|
|
|
void checkForRewards() { |
|
if (score >= nextBomb) { |
|
nextBomb += conf.extraBombInterval; |
|
if (bombs < conf.initialBombs) { |
|
bombs++; |
|
} |
|
} |
|
|
|
if (score >= nextLife) { |
|
nextLife += conf.extraLifeInterval; |
|
lives++; |
|
} |
|
} |
|
|
|
/** |
|
* Pause screen |
|
*/ |
|
void pause() { |
|
renderLevel(); |
|
|
|
textSize(dimensions.adjustedWidth / 11); |
|
textAlign(CENTER); |
|
|
|
int x = dimensions.widthOffset + dimensions.adjustedWidth / 2; |
|
int y = dimensions.heightOffset + dimensions.adjustedHeight / 2 - (dimensions.adjustedHeight / 23); |
|
|
|
text(conf.pauseMsg1, x, y); |
|
|
|
textSize(dimensions.adjustedWidth / 20); |
|
|
|
y = dimensions.heightOffset + dimensions.adjustedHeight / 2 + (dimensions.adjustedHeight / 23); |
|
|
|
text(conf.pauseMsg2, x, y); |
|
|
|
showControls(); |
|
|
|
if (controller.buttonA()) { |
|
playSoundEffect("resume"); |
|
|
|
controller.resetInitialHeading(); |
|
pause = false; |
|
|
|
//adjust timers to account for time paused |
|
long currentTime = millis(); |
|
startTime += (currentTime - pauseTime); |
|
} |
|
} |
|
|
|
/** |
|
* Display loading screen while resources load |
|
*/ |
|
void loadingScreen() { |
|
textSize(dimensions.adjustedWidth / 11); |
|
textAlign(CENTER); |
|
|
|
int x = dimensions.widthOffset + dimensions.adjustedWidth / 2; |
|
int y = dimensions.heightOffset + dimensions.adjustedHeight / 2; |
|
|
|
text(conf.loadingMsg, x, y); |
|
} |
|
|
|
/** |
|
* Display loading screen while resources load |
|
*/ |
|
void gameOver() { |
|
textSize(dimensions.adjustedWidth / 11); |
|
textAlign(CENTER); |
|
|
|
int x = dimensions.widthOffset + dimensions.adjustedWidth / 2; |
|
int y = dimensions.heightOffset + dimensions.adjustedHeight / 2 - (dimensions.adjustedHeight / 23); |
|
|
|
text(conf.gameOverMsg, x, y); |
|
|
|
textSize(dimensions.adjustedWidth / 20); |
|
|
|
y = dimensions.heightOffset + dimensions.adjustedHeight / 2 + (dimensions.adjustedHeight / 23); |
|
|
|
String scoreString = "Score: " + score; |
|
text(scoreString, x, y); |
|
|
|
textAlign(LEFT); |
|
textSize(dimensions.adjustedWidth / 45); |
|
|
|
x = dimensions.widthOffset + (dimensions.adjustedWidth / 72); |
|
y = dimensions.heightOffset + dimensions.adjustedHeight / 2 + (dimensions.adjustedHeight / 8); |
|
|
|
text(conf.credits, x, y); |
|
|
|
long currentTime = millis(); |
|
|
|
if (currentTime - gameOverTime > 5000) { |
|
titleScreen = true; |
|
gameOver = false; |
|
story = false; |
|
pause = false; |
|
score = 0; |
|
lives = conf.initialLives; |
|
bombs = conf.initialBombs; |
|
difficulty = 0; |
|
enemies = new ArrayList<Enemy>(); |
|
} |
|
} |
|
|
|
/** |
|
* Show title Screen |
|
*/ |
|
void titleScreen() { |
|
storyCounter++; |
|
renderLevel(); |
|
|
|
textSize(dimensions.adjustedWidth / 11); |
|
textAlign(CENTER); |
|
|
|
int x = dimensions.widthOffset + dimensions.adjustedWidth / 2; |
|
int y = dimensions.heightOffset + dimensions.adjustedHeight / 2 - (dimensions.adjustedHeight / 23); |
|
|
|
text(conf.title, x, y); |
|
|
|
textSize(dimensions.adjustedWidth / 25); |
|
|
|
y = dimensions.heightOffset + dimensions.adjustedHeight / 2 + (dimensions.adjustedHeight / 23); |
|
|
|
text(conf.titleMsg1, x, y); |
|
|
|
showControls(); |
|
|
|
textSize(dimensions.adjustedWidth / 50); |
|
x = dimensions.widthOffset + dimensions.adjustedWidth - dimensions.adjustedWidth/15; |
|
y = dimensions.heightOffset + dimensions.adjustedHeight - dimensions.adjustedHeight/45; |
|
text(conf.copyright, x, y); |
|
|
|
if (storyCounter >= conf.storyWaitTime * conf.framerate) { |
|
titleScreen = false; |
|
story = true; |
|
} |
|
|
|
if ((controller.buttonA()) && (controller.buttonB())) { |
|
playSoundEffect("shield"); |
|
titleScreen = false; |
|
|
|
storyCounter = 0; |
|
|
|
controller.resetInitialHeading(); |
|
|
|
nextLife = conf.extraLifeInterval; |
|
nextBomb = conf.extraBombInterval; |
|
|
|
player = new Player(level.getStartX(), level.getStartY(), 0, spriteSet.get("player")); |
|
|
|
startTime = millis(); |
|
lastShot = millis(); |
|
spawnTime = millis(); |
|
} |
|
} |
|
|
|
/* |
|
* Show controls |
|
*/ |
|
void showControls() { |
|
textAlign(LEFT); |
|
textSize(dimensions.adjustedWidth / 45); |
|
|
|
int x = dimensions.widthOffset + (dimensions.adjustedWidth / 72); |
|
int y = dimensions.heightOffset + dimensions.adjustedHeight / 2 + (dimensions.adjustedHeight / 8); |
|
|
|
text(conf.controlsMsg, x, y); |
|
} |
|
|
|
/* |
|
* count the number of lines in a string |
|
* taken from: https://stackoverflow.com/a/18816371 |
|
*/ |
|
int countLines(String str) { |
|
if(str == null || str.isEmpty()) |
|
{ |
|
return 0; |
|
} |
|
|
|
int lines = 1; |
|
int pos = 0; |
|
while ((pos = str.indexOf("\n", pos) + 1) != 0) { |
|
lines++; |
|
} |
|
return lines; |
|
} |
|
|
|
/* |
|
* Star wars style scrolling text |
|
*/ |
|
void storyScroll() { |
|
renderLevel(); |
|
|
|
pushMatrix(); |
|
|
|
int x = dimensions.widthOffset + (dimensions.adjustedWidth / 2); |
|
int y = dimensions.heightOffset + (dimensions.adjustedHeight/2); |
|
|
|
translate(x, y); |
|
|
|
fill(255,255,0); |
|
textAlign(CENTER); |
|
textSize(dimensions.adjustedWidth / 20); |
|
text(conf.storyCrawl, 0, storyY); |
|
storyY -= conf.scrollSpeed; |
|
|
|
fill(255); |
|
popMatrix(); |
|
|
|
//if any button is pressed or end of story is reached |
|
if ((controller.buttonA()) || (controller.buttonB()) || (controller.buttonC()) || (storyY < (dimensions.heightOffset + (dimensions.adjustedHeight/1.75) - (storyLines * (dimensions.adjustedWidth / 10))))) { |
|
titleScreen = true; |
|
story = false; |
|
storyCounter = 0; |
|
storyY = dimensions.heightOffset + (dimensions.adjustedHeight/1.75); |
|
} |
|
} |
|
|
|
/* |
|
* Play sound effect |
|
*/ |
|
void playSoundEffect(String name) { |
|
if (soundEffects.containsKey(name)) { |
|
if (!soundEffects.get(name).isPlaying()) { |
|
soundEffects.get(name).play(0); |
|
} |
|
} |
|
else { |
|
print(name); |
|
println(" not found!"); |
|
} |
|
} |
|
|
|
void stopSoundEffect(String name) { |
|
if (soundEffects.containsKey(name)) { |
|
soundEffects.get(name).pause(); |
|
} |
|
else { |
|
print(name); |
|
println(" not found!"); |
|
} |
|
} |
|
|
|
void stopAllSoundEffects() { |
|
for (String effect : soundEffects.keySet()) { |
|
stopSoundEffect(effect); |
|
} |
|
} |
|
|
|
/* |
|
* Play random song |
|
*/ |
|
void playRandomSong() { |
|
List<String> keys = new ArrayList<String>(music.keySet()); |
|
int index = int(random(0, keys.size())); |
|
|
|
String key = keys.get(index); |
|
|
|
if (key == activeSong) { |
|
playRandomSong(); |
|
} |
|
|
|
activeSong = key; |
|
|
|
music.get(activeSong).play(0); |
|
} |
|
|
|
/** |
|
* Perform all intensive laod operations |
|
* Ideally in a seperate thread |
|
*/ |
|
void loadResources() { |
|
try { |
|
tileSet = generateTileSet(); |
|
props = new TileProperties(conf); |
|
|
|
spriteSet = new HashMap<String, PImage[]>(); |
|
spriteSet.put("player", loadPlayerSprites()); |
|
spriteSet.put("bomb", loadBombSprites()); |
|
spriteSet.put("asteroid", loadAsteroidSprites()); |
|
spriteSet.put("jellyfish", loadJellyfishSprites()); |
|
spriteSet.put("gunPlatform", loadGunPlatformSprites()); |
|
spriteSet.put("saucer", loadSaucerSprites()); |
|
spriteSet.put("boss", loadBossSprites()); |
|
|
|
soundEffects = loadSoundEffects(); |
|
music = loadMusic(); |
|
} |
|
catch (Exception e) { |
|
e.printStackTrace(); |
|
} |
|
|
|
level = new Level(conf, props); |
|
level.generateRandomLevel(conf.levelWidth, conf.levelHeight, 2, 24, 1, 99); |
|
|
|
//temp player |
|
player = new Player(level.getStartX(), level.getStartY(), 0, spriteSet.get("player")); |
|
|
|
titleScreen = true; |
|
loading = false; |
|
} |
|
|
|
/** |
|
* Load every image in the tile directory into an id->image map |
|
* @return populate id->image map |
|
* @throws IOException if tile directory cannot be accessed |
|
*/ |
|
Map<Integer, PImage> generateTileSet() throws IOException { |
|
Map<Integer, PImage> map = null; |
|
|
|
File tileDirectory = new File(sketchPath() + System.getProperty("file.separator") + conf.tilePath); |
|
|
|
//if conf.tilePath can be accessed, loop through and add each tile to the map |
|
if ((tileDirectory.exists()) && (tileDirectory.isDirectory())) { |
|
//Return the unknown tile (id=99999) if a tile for the requested id cannot be found |
|
PImage unknownTile = loadImage(conf.tilePath + System.getProperty("file.separator") + conf.unknownTitle + conf.tileImageExtension); |
|
map = new DefaultHashMap<Integer, PImage>(unknownTile); |
|
|
|
for (File file : tileDirectory.listFiles()) { |
|
if (file.getName().endsWith(conf.tileImageExtension)) { |
|
int id = Integer.parseInt(file.getName().replace(conf.tileImageExtension, "")); |
|
PImage image = loadImage(file.getAbsolutePath()); |
|
map.put(id, image); |
|
} |
|
} |
|
} |
|
else { |
|
println("Could not find tile directory."); |
|
exit(); |
|
} |
|
|
|
return map; |
|
} |
|
|
|
/* |
|
* Load player sprites |
|
* @return PImage[] of player sprites |
|
*/ |
|
PImage[] loadPlayerSprites() { |
|
PImage[] sprites = { |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "player0.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "player1.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "player2.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "player3.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "player4.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "player5.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "player6.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "player7.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "player8.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "sheild.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "playerProjectile.png"), |
|
}; |
|
|
|
return sprites; |
|
} |
|
|
|
PImage[] loadBombSprites() { |
|
PImage[] sprites = { |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "bomb-icon.png") |
|
}; |
|
|
|
return sprites; |
|
} |
|
|
|
PImage[] loadAsteroidSprites() { |
|
PImage[] sprites = { |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "asteroid0.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "asteroid1.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "asteroid2.png") |
|
}; |
|
|
|
return sprites; |
|
} |
|
|
|
PImage[] loadJellyfishSprites() { |
|
PImage[] sprites = { |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "jellyfish0.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "jellyfish1.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "jellyfish2.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "jellyfish3.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "jellyfish4.png") |
|
}; |
|
|
|
return sprites; |
|
} |
|
|
|
PImage[] loadGunPlatformSprites() { |
|
PImage[] sprites = { |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "gunPlatform.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "gunPlatformProjectile.png") |
|
}; |
|
|
|
return sprites; |
|
} |
|
|
|
PImage[] loadSaucerSprites() { |
|
PImage[] sprites = { |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "saucer.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "saucerProjectile.png") |
|
}; |
|
|
|
return sprites; |
|
} |
|
|
|
PImage[] loadBossSprites() { |
|
PImage[] sprites = { |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "boss0.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "boss1.png"), |
|
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "bossProjectile.png") |
|
}; |
|
|
|
return sprites; |
|
} |
|
|
|
Map<String, AudioPlayer> loadSoundEffects() { |
|
Map<String, AudioPlayer> map = new HashMap<String, AudioPlayer>(); |
|
|
|
map.put("bomb", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.soundPath + System.getProperty("file.separator") + "bomb.mp3")); |
|
map.put("death", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.soundPath + System.getProperty("file.separator") + "death.mp3")); |
|
map.put("explosion", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.soundPath + System.getProperty("file.separator") + "explosion.mp3")); |
|
map.put("forwards", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.soundPath + System.getProperty("file.separator") + "forwards.mp3")); |
|
map.put("reverse", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.soundPath + System.getProperty("file.separator") + "reverse.mp3")); |
|
map.put("rotate", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.soundPath + System.getProperty("file.separator") + "rotate.mp3")); |
|
map.put("shield", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.soundPath + System.getProperty("file.separator") + "shield.mp3")); |
|
map.put("shoot", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.soundPath + System.getProperty("file.separator") + "shoot.mp3")); |
|
map.put("pause", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.soundPath + System.getProperty("file.separator") + "pause.mp3")); |
|
map.put("resume", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.soundPath + System.getProperty("file.separator") + "resume.mp3")); |
|
|
|
return map; |
|
} |
|
|
|
Map<String, AudioPlayer> loadMusic() { |
|
Map<String, AudioPlayer> map = new HashMap<String, AudioPlayer>(); |
|
|
|
map.put("cold-moon", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.musicPath + System.getProperty("file.separator") + "Cold-Moon.mp3")); |
|
map.put("cyber-rem",minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.musicPath + System.getProperty("file.separator") + "Cyber-R.E.M.mp3")); |
|
map.put("dystopic-factory", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.musicPath + System.getProperty("file.separator") + "Dystopic-Factory.mp3")); |
|
map.put("escape-from-the-insane-machines", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.musicPath + System.getProperty("file.separator") + "Escape-From-the-Insane-Machines.mp3")); |
|
map.put("light-years", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.musicPath + System.getProperty("file.separator") + "Light-Years.mp3")); |
|
map.put("retro-scifi-planet", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.musicPath + System.getProperty("file.separator") + "Retro-Sci-Fi-Planet.mp3")); |
|
map.put("sector-off-limits", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.musicPath + System.getProperty("file.separator") + "Sector-Off-Limits.mp3")); |
|
map.put("terraforming-begins", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.musicPath + System.getProperty("file.separator") + "Terraforming-Begins.mp3")); |
|
map.put("trouble-on-mercury", minim.loadFile(sketchPath() + System.getProperty("file.separator") + conf.musicPath + System.getProperty("file.separator") + "Trouble-On-Mercury.mp3")); |
|
|
|
return map; |
|
}
|
|
|