Added scrolling story if idle on title page. Implemented projectile and bomb. Added 3 different types of asteroid to shoot. Added shield on spawn and after taking damage.

master
Jamie Munro 2 years ago
parent 477cc2d019
commit 2aa1ae05e8
  1. 144
      Asteroid1.pde
  2. 55
      Asteroid2.pde
  3. 55
      Asteroid3.pde
  4. 43
      Bomb.pde
  5. 135
      Configuration.pde
  6. 7
      DisplayDimensions.pde
  7. 26
      Enemy.pde
  8. 664
      Game.pde
  9. 2
      Level.pde
  10. 33
      Player.pde
  11. 78
      PlayerProjectile.pde
  12. 17
      Projectile.pde
  13. 116
      config.properties
  14. BIN
      res/sprites/asteroid1.png
  15. BIN
      res/sprites/playerProjectile.png
  16. BIN
      res/sprites/sheild.png

@ -0,0 +1,144 @@
/**
* Copyright 2022 Jamie Munro, All rights reserved
* CS5041 P1
* Game 1
*/
public class Asteroid1 implements Enemy {
public static final int MINIMUM_DIFFICULTY = 0;
public static final int SCORE = 25;
public static final int INITIAL_HIT_POINTS = 1;
public static final int SCALE_FACTOR = 10;
public static final int CHANCE_OF_SPAWNING = 20;
public static final int MAX_SPEED = 4;
private final PImage sprite;
private final int tintR;
private final int tintG;
private final int tintB;
private final Level level;
private int x;
private int y;
private int rotation;
private float speed;
private int hitPoints;
public Asteroid1(int x, int y, PImage sprite, Level level) {
this.x = x;
this.y = y;
this.rotation = int(random(0,360));
this.level = level;
this.hitPoints = INITIAL_HIT_POINTS;
this.sprite = sprite;
this.tintR = int(random(0, 256));
this.tintG = int(random(0, 256));
this.tintB = int(random(0, 256));
this.speed = random(0,MAX_SPEED+1);
}
@Override
public int getX() {
return this.x;
}
@Override
public int getY(){
return this.y;
}
@Override
public int getRotation(){
return this.rotation;
}
@Override
public PImage getSprite(){
return this.sprite;
}
@Override
public int getScaleFactor() {
return SCALE_FACTOR;
}
@Override
public int getTintR() {
return this.tintR;
}
@Override
public int getTintG() {
return this.tintG;
}
@Override
public int getTintB() {
return this.tintB;
}
@Override
public int getMinimumDifficulty() {
return MINIMUM_DIFFICULTY;
}
@Override
public int getInitialHitPoints() {
return INITIAL_HIT_POINTS;
}
@Override
public int getHitPoints(){
return this.hitPoints;
}
@Override
public int getScore() {
return SCORE;
}
@Override
public int getChanceOfSpawning() {
return CHANCE_OF_SPAWNING;
}
@Override
public void update(){
int newX = this.getX() + Math.round(this.speed * sin(radians(this.getRotation())));
int newY = this.getY() + Math.round(this.speed * -cos(radians(this.getRotation())));
//bounce off boundaries: https://forum.processing.org/two/discussion/10649/maths-calculate-reflection-angle.html
if ((newX <= 0) || (newX >= level.getCols()-1)) {
//verticle wall
this.rotation = 360 - rotation;
while (this.rotation < 0) this.rotation += 360;
}
else if ((newY <= 0) || (newY >= level.getRows()-1)){
//horizontal wall
this.rotation = 180 - rotation;
while (this.rotation < 0) this.rotation += 360;
}
else {
this.x = newX;
this.y = newY;
}
}
@Override
public boolean hit() {
this.hitPoints--;
if (this.hitPoints <= 0) return true;
return false;
}
@Override
public List<Enemy> spawnlings() {
return null;
}
}

@ -0,0 +1,55 @@
/**
* Copyright 2022 Jamie Munro, All rights reserved
* CS5041 P1
* Game 1
*/
public class Asteroid2 extends Asteroid1 {
public static final int SCALE_FACTOR = 20;
public static final int ENEMIES_TO_SPAWN = 3;
public static final int MINIMUM_DIFFICULTY = 1;
public static final int SCORE = 50;
public static final int CHANCE_OF_SPAWNING = 5;
public static final int MAX_SPEED = 2;
public Asteroid2(int x, int y, PImage sprite, Level level) {
super(x, y, sprite, level);
super.speed = random(0,MAX_SPEED+1);
}
@Override
public int getScaleFactor() {
return SCALE_FACTOR;
}
@Override
public int getMinimumDifficulty() {
return MINIMUM_DIFFICULTY;
}
@Override
public int getScore() {
return SCORE;
}
@Override
public int getChanceOfSpawning() {
return CHANCE_OF_SPAWNING;
}
@Override
public boolean hit() {
super.hitPoints--;
if (super.hitPoints <= 0) return true;
return false;
}
@Override
public List<Enemy> spawnlings() {
List<Enemy> spawnlings = new ArrayList<Enemy>();
for (int i = 0; i < ENEMIES_TO_SPAWN; i++) {
spawnlings.add(new Asteroid1(super.x, super.y, super.sprite, super.level));
}
return spawnlings;
}
}

@ -0,0 +1,55 @@
/**
* Copyright 2022 Jamie Munro, All rights reserved
* CS5041 P1
* Game 1
*/
public class Asteroid3 extends Asteroid1 {
public static final int SCALE_FACTOR = 40;
public static final int ENEMIES_TO_SPAWN = 3;
public static final int MINIMUM_DIFFICULTY = 2;
public static final int SCORE = 100;
public static final int CHANCE_OF_SPAWNING = 2;
public static final int MAX_SPEED = 1;
public Asteroid3(int x, int y, PImage sprite, Level level) {
super(x, y, sprite, level);
super.speed = random(0,MAX_SPEED+1);
}
@Override
public int getScaleFactor() {
return SCALE_FACTOR;
}
@Override
public int getMinimumDifficulty() {
return MINIMUM_DIFFICULTY;
}
@Override
public int getScore() {
return SCORE;
}
@Override
public int getChanceOfSpawning() {
return CHANCE_OF_SPAWNING;
}
@Override
public boolean hit() {
super.hitPoints--;
if (super.hitPoints <= 0) return true;
return false;
}
@Override
public List<Enemy> spawnlings() {
List<Enemy> spawnlings = new ArrayList<Enemy>();
for (int i = 0; i < ENEMIES_TO_SPAWN; i++) {
spawnlings.add(new Asteroid2(super.x, super.y, super.sprite, super.level));
}
return spawnlings;
}
}

@ -0,0 +1,43 @@
/**
* Copyright 2022 Jamie Munro, All rights reserved
* CS5041 P1
* Game 1
*/
public class Bomb {
private final Configuration conf;
public final int x;
public final int y;
private int expansion;
private boolean out;
public Bomb(int x, int y, Configuration conf) {
this.conf = conf;
this.x = x;
this.y = y;
this.expansion = 0;
this.out = true;
}
public int getExpansion() {
return this.expansion;
}
public float getDiameter() {
return map(this.expansion, 0, 100, 0, 1.5*conf.viewportWidth);
}
public boolean update() {
if (out) {
expansion += conf.bombSpeed;
if (expansion == 100) out = false;
}
else {
expansion -= conf.bombSpeed;
if (expansion == 0) return true;
}
return false;
}
}

@ -18,87 +18,87 @@ class Configuration {
*/
private static final String DEFAULT_CONFIG_PATH = "config.properties";
public int aspectX;
public int aspectY;
public final int aspectX;
public final int aspectY;
public int framerate;
public final int framerate;
public int viewportWidth;
public int viewportHeight;
public final int viewportWidth;
public final int viewportHeight;
public final int levelWidth;
public final int levelHeight;
public String resPath;
public String tilePath;
public String levelPath;
public String spritePath;
public final String resPath;
public final String tilePath;
public final String levelPath;
public final String spritePath;
public String levelExtension;
public String tileDeliminator;
public String tileImageExtension;
public String tilePropertiesExtension;
public int emptyTile;
public int unknownTitle;
public final String levelExtension;
public final String tileDeliminator;
public final String tileImageExtension;
public final String tilePropertiesExtension;
public final int emptyTile;
public final int unknownTitle;
public final String title;
public final String titleMsg1;
public final String loadingMsg;
public final String pauseMsg1;
public final String pauseMsg2;
public final String controlsMsg;
public final String storyCrawl;
public String title;
public String titleMsg1;
public String loadingMsg;
public String pauseMsg1;
public String pauseMsg2;
public String controlsMsg;
public final int storyWaitTime;
public final int scrollSpeed;
public int serialDevice;
public int baudRate;
public int lineEnding;
public int messageLength;
public String microbitSplitRegex;
public String microbitControlChar;
public final int serialDevice;
public final int baudRate;
public final int lineEnding;
public final int messageLength;
public final String microbitSplitRegex;
public final String microbitControlChar;
public int thresholdX;
public int thresholdY;
public int headingThreshold;
public int playerScaleFactor;
public final int playerScaleFactor;
public float mainEngineForce;
public float reverseEngineForce;
public float sideEngineForce;
public int rotationSpeed;
public final float mainEngineForce;
public final float reverseEngineForce;
public final int rotationSpeed;
public float maxForwardsMomentum;
public float maxRearMomentum;
public float maxSideMomentum;
public final float maxForwardsMomentum;
public final float maxRearMomentum;
public float friction;
public final float friction;
public final int initialLives;
public final int initialBombs;
public final int bombSpeed;
public final int enemyTypes;
public int initialLives;
public int initialBombs;
public final int maxPlayerProjectiles;
public final int reloadTime;
public final int sheildTime;
public final int extraLifeInterval;
public final int extraBombInterval;
/**
* Private constructor
* Parses properties file and loads each parameter into relevent variable
* @param filepath configuration properties filepath
*/
public Configuration() {
public Configuration() throws IOException{
Properties prop = new Properties();
InputStream input = null;
try {
input = new FileInputStream(sketchPath() + System.getProperty("file.separator") + DEFAULT_CONFIG_PATH);
prop.load(input);
this.loadValuesFromProp(prop);
input.close();
}
catch(Exception e) {
println("Could not access configuration file");
e.printStackTrace();
exit();
}
}
/**
* Load configuration values from properties file
* @param prop configuration.properties
*/
private void loadValuesFromProp(Properties prop) {
InputStream input = new FileInputStream(sketchPath() + System.getProperty("file.separator") + DEFAULT_CONFIG_PATH);
prop.load(input);
this.aspectX = Integer.parseInt(prop.getProperty("aspectX"));
this.aspectY = Integer.parseInt(prop.getProperty("aspectY"));
@ -106,6 +106,9 @@ class Configuration {
this.viewportWidth = Integer.parseInt(prop.getProperty("viewportWidth"));
this.viewportHeight = Integer.parseInt(prop.getProperty("viewportHeight"));
this.levelWidth = Integer.parseInt(prop.getProperty("levelWidth"));
this.levelHeight = Integer.parseInt(prop.getProperty("levelHeight"));
this.resPath = prop.getProperty("resPath");
this.tilePath = prop.getProperty("tilePath");
@ -125,6 +128,10 @@ class Configuration {
this.pauseMsg1 = prop.getProperty("pauseMsg1");
this.pauseMsg2 = prop.getProperty("pauseMsg2");
this.controlsMsg = prop.getProperty("controlsMsg");
this.storyCrawl = prop.getProperty("storyCrawl");
this.storyWaitTime = Integer.parseInt(prop.getProperty("storyWaitTime"));
this.scrollSpeed = Integer.parseInt(prop.getProperty("scrollSpeed"));
this.serialDevice = Integer.parseInt(prop.getProperty("serialDevice"));
this.baudRate = Integer.parseInt(prop.getProperty("baudRate"));
@ -150,5 +157,17 @@ class Configuration {
this.initialLives = Integer.parseInt(prop.getProperty("initialLives"));
this.initialBombs = Integer.parseInt(prop.getProperty("initialBombs"));
this.bombSpeed = Integer.parseInt(prop.getProperty("bombSpeed"));
this.enemyTypes = Integer.parseInt(prop.getProperty("enemyTypes"));
this.maxPlayerProjectiles = Integer.parseInt(prop.getProperty("maxPlayerProjectiles"));
this.reloadTime = Integer.parseInt(prop.getProperty("reloadTime"));
this.sheildTime = Integer.parseInt(prop.getProperty("sheildTime")); //<>//
this.extraLifeInterval = Integer.parseInt(prop.getProperty("extraLifeInterval"));
this.extraBombInterval = Integer.parseInt(prop.getProperty("extraBombInterval"));
input.close();
}
}

@ -82,5 +82,12 @@ class DisplayDimensions {
this.startBombsX = this.widthOffset + this.adjustedWidth - (this.adjustedWidth / 60) - ((this.tileWidth * 9) * conf.initialBombs);
this.bombsY = this.heightOffset + this.adjustedHeight - (this.adjustedHeight / 20);
//println(this.heightOffset);
//println(this.adjustedHeight);
//println(this.widthOffset);
//println(this.adjustedWidth);
//println(this.tileHeight);
//println(this.tileWidth);
}
}

@ -0,0 +1,26 @@
/**
* Copyright 2022 Jamie Munro, All rights reserved
* CS5041 P1
* Game 1
*/
public interface Enemy {
public int getX();
public int getY();
public int getRotation();
public PImage getSprite();
public int getScaleFactor();
public int getTintR();
public int getTintG();
public int getTintB();
public int getMinimumDifficulty();
public int getScore();
public int getInitialHitPoints();
public int getHitPoints();
public int getChanceOfSpawning();
public void update();
public boolean hit();
public List<Enemy> spawnlings();
}

@ -5,36 +5,70 @@
*/
import java.util.Map;
import java.util.Iterator;
import processing.serial.*;
Configuration conf;
DisplayDimensions dimensions;
Map<Integer, PImage> tileSet; //Maps tile id to related image (to avoid repeated/redundant image loads)
Map<String, PImage[]> spriteSet;
Controller controller;
Serial port;
Player player;
Level level;
TileProperties props;
//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;
Configuration conf = null;
DisplayDimensions dimensions = null;
//story screen variables
int storyCounter;
float storyY;
int storyLines;
Map<Integer, PImage> tileSet = null; //Maps tile id to related image (to avoid repeated/redundant image loads)
Map<String, PImage[]> spriteSet = null;
//rewards variables
int nextLife;
int nextBomb;
Controller controller = null;
Serial port = null;
//projectiles
List<Projectile> playerProjectiles;
List<Projectile> enemyProjectiles;
Bomb bomb;
Player player = null;
Level level = null;
TileProperties props = null;
//enemy variables
List<Enemy> enemies;
void setup() {
fullScreen(2);
fullScreen(P2D, 2);
conf = new Configuration();
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);
@ -55,12 +89,26 @@ void setup() {
//intialise game state
loading = true;
titleScreen = false;
story = false;
pause = 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;
//perform loading
thread("loadResources");
@ -78,12 +126,20 @@ void draw(){
else if (titleScreen) {
titleScreen();
}
else if (story) {
storyScroll();
}
else if (pause) {
pause();
}
else if (gameOver) {
gameOver();
}
else {
game();
}
//println(frameRate);
}
void serialEvent(Serial p) {
@ -95,13 +151,55 @@ void serialEvent(Serial p) {
* Main game loop
*/
void game() {
//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();
drawHud();
//detect collisions with enemies
detectEnemyCollision();
//detect enemy projectile collisions
detectEnemyProjectileCollision();
//detect player projectile collisions
detectPlayerProjectileCollisions();
//spawn new enemies
spawnEnemy(true);
//update difficulty
long currentTime = millis();
long secondsElapsed = (currentTime - startTime) / 1000;
difficulty = (int) secondsElapsed / 60;
checkForRewards();
}
/**
@ -131,26 +229,6 @@ void renderLevel() {
}
}
/*
* Update player position and draw to screen
*/
void renderPlayer() {
updatePosition();
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 * conf.playerScaleFactor, dimensions.tileHeight * conf.playerScaleFactor);
popMatrix();
}
/**
* Update players position based on control input
*/
@ -189,6 +267,30 @@ void updatePosition() {
}
}
/*
* 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
*/
@ -209,6 +311,7 @@ void drawHud() {
//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);
@ -232,6 +335,7 @@ void drawHud() {
//back to defaults
fill(255);
strokeWeight(4);
//bombs
imageMode(CORNER);
@ -247,10 +351,372 @@ void drawHud() {
void buttons() {
if (controller.buttonC()) {
pause = true;
}
pauseTime = millis();
}
if (controller.buttonA()) {
shoot();
}
if (controller.buttonB()) {
bombs--;
bomb();
}
}
/**
* Shoots a projectile if possible
*/
void shoot() {
if (playerProjectiles.size() - 1 <= conf.maxPlayerProjectiles) {
long currentTime = millis();
if (currentTime - lastShot >= conf.reloadTime) {
playerProjectiles.add(new PlayerProjectile(player.getX(), player.getY(), player.getRotation(), spriteSet.get("projectile")[0]));
lastShot = currentTime;
}
}
}
/**
* Triggers a bomb if possible
*/
void bomb() {
if ((bomb == null) && (bombs > 0) && (!player.getSheild())) {
bomb = new Bomb(player.getX(), player.getY(), conf);
bombs--;
}
}
/**
* Selects and spawns a random enemy based on difficulty level
*/
void spawnEnemy(boolean debug) {
int startCol = player.getX() - (conf.viewportWidth / 2);
int endCol = player.getX() + (conf.viewportWidth / 2);
int startRow = player.getY() - (conf.viewportHeight / 2);
int endRow = player.getY() + (conf.viewportHeight / 2);
//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 r0 = int(random(0,conf.enemyTypes));
int r1 = int(random(1, 101));
switch(r0) {
case 0: {
if ((r1 < Asteroid1.CHANCE_OF_SPAWNING) && (difficulty >= Asteroid1.MINIMUM_DIFFICULTY)) {
enemies.add(new Asteroid1(col, row, spriteSet.get("asteroid")[0], level));
if (debug) {
print("Asteroid1 spawned at: ");
print(col);
print(", ");
println(row);
}
}
}
break;
case 1: {
if ((r1 < Asteroid2.CHANCE_OF_SPAWNING) && (difficulty >= Asteroid2.MINIMUM_DIFFICULTY)) {
enemies.add(new Asteroid2(col, row, spriteSet.get("asteroid")[0], level));
if (debug) {
print("Asteroid2 spawned at: ");
print(col);
print(", ");
println(row);
}
}
}
break;
case 2: {
if ((r1 < Asteroid3.CHANCE_OF_SPAWNING) && (difficulty >= Asteroid3.MINIMUM_DIFFICULTY)) {
enemies.add(new Asteroid3(col, row, spriteSet.get("asteroid")[0], level));
if (debug) {
print("Asteroid3 spawned at: ");
print(col);
print(", ");
println(row);
}
}
}
break;
}
}
/**
* update enemy positions
*/
void updateEnemies() {
for (Enemy enemy : enemies) {
enemy.update();
}
}
/**
* render enemies within viewport
*/
void renderEnemies() {
int startCol = player.getX() - (conf.viewportWidth / 2);
int endCol = player.getX() + (conf.viewportWidth / 2);
int startRow = player.getY() - (conf.viewportHeight / 2);
int endRow = player.getY() + (conf.viewportHeight / 2);
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() {
int startCol = player.getX() - (conf.viewportWidth / 2);
int endCol = player.getX() + (conf.viewportWidth / 2);
int startRow = player.getY() - (conf.viewportHeight / 2);
int endRow = player.getY() + (conf.viewportHeight / 2);
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, dimensions.tileHeight);
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();
int pX = projectile.getX();
int pY = projectile.getY();
int eX = enemy.getX();
int eY = enemy.getY();
if ((pX > eX - enemy.getScaleFactor()) && (pX < eX + enemy.getScaleFactor()) && (pY > eY - enemy.getScaleFactor()) && (pY < eY + enemy.getScaleFactor())) {
for (int i = 0; i < projectile.getDamage(); i++) {
if(enemy.hit()) {
//DESTROYED!
//TODO:ANIMATION
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();
int eX = projectile.getX();
int eY = projectile.getY();
if ((eX > player.getX() - player.getScaleFactor()) && (eX < player.getX() + player.getScaleFactor()) && (eY > player.getY() - player.getScaleFactor()) && (eY > player.getY() + player.getScaleFactor())) {
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 startCol = player.getX() - (conf.viewportWidth / 2);
int startRow = player.getY() - (conf.viewportHeight / 2);
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) {
gameOverTime = millis();
gameOver = true;
}
}
void checkForRewards() {
if (score >= nextBomb) {
nextBomb += conf.extraBombInterval;
if (bombs < conf.initialBombs) {
bombs++;
}
}
if (score >= nextLife) {
nextLife += conf.extraLifeInterval;
lives++;
}
}
@ -276,9 +742,13 @@ void pause() {
showControls();
if ((controller.buttonA()) && (controller.buttonB())) {
if (controller.buttonA()) {
controller.resetInitialHeading();
pause = false;
//adjust timers to account for time paused
long currentTime = millis();
startTime += (currentTime - pauseTime);
}
}
@ -295,10 +765,45 @@ void loadingScreen() {
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("GAME OVER", x, y);
textSize(dimensions.adjustedWidth / 20);
y = dimensions.heightOffset + dimensions.adjustedHeight / 2 + (dimensions.adjustedHeight / 23);
String scoreString = "Score: " + score;
text(scoreString, 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);
@ -317,8 +822,25 @@ void titleScreen() {
showControls();
if (storyCounter >= conf.storyWaitTime * conf.framerate) {
titleScreen = false;
story = true;
}
if ((controller.buttonA()) && (controller.buttonB())) {
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();
}
}
@ -335,6 +857,55 @@ void showControls() {
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);
}
}
/**
* Perform all intensive laod operations
* Ideally in a seperate thread
@ -347,13 +918,17 @@ void loadResources() {
spriteSet = new HashMap<String, PImage[]>();
spriteSet.put("player", loadPlayerSprites());
spriteSet.put("bomb", loadBombSprites());
spriteSet.put("asteroid", loadAsteroidSprites());
spriteSet.put("projectile", loadProjectileSprites());
}
catch (Exception e) {
e.printStackTrace();
}
level = new Level(conf, props);
level.generateRandomLevel(5000, 5000, 2, 24, 1, 997);
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;
@ -407,6 +982,7 @@ PImage[] loadPlayerSprites() {
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"),
};
return sprites;
@ -419,3 +995,19 @@ PImage[] loadBombSprites() {
return sprites;
}
PImage[] loadAsteroidSprites() {
PImage[] sprites = {
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "asteroid1.png")
};
return sprites;
}
PImage[] loadProjectileSprites() {
PImage[] sprites = {
loadImage(sketchPath() + System.getProperty("file.separator") + conf.spritePath + System.getProperty("file.separator") + "playerProjectile.png")
};
return sprites;
}

@ -208,7 +208,7 @@ class Level {
}
else {
int randomId = minId;
int r1 = int(random(0, 1001));
int r1 = int(random(0, 101));
if (r1 > p0) {
randomId = int(random(minId+1, maxId + 1));

@ -1,6 +1,7 @@
/**
* Copyright 2022 Jamie Munro, Distributed under the Common Public Attribution License Version 1.0 (CPAL-1.0)
* Tile engine
* Copyright 2022 Jamie Munro, All rights reserved
* CS5041 P1
* Game 1
*/
/*
@ -19,13 +20,17 @@ public enum EngineXState {
* Extends camera (as camera follows player) and represents a player
*/
class Player extends Camera {
private final boolean GOD_MODE = true;
private int scaleFactor;
private PImage sprites[];
private EngineXState engineXState;
private EngineYState engineYState;
private float momentum;
private long hitTime;
/**
* Constructor
* @param initialX starting x position of player
@ -34,11 +39,31 @@ class Player extends Camera {
*/
public Player(int initialX, int initialY, int initialRotation, PImage image[]) {
super(initialX, initialY, initialRotation);
this.scaleFactor = conf.playerScaleFactor;
this.sprites = image;
this.engineXState = EngineXState.NONE;
this.engineYState = EngineYState.NONE;
this.momentum = 0;
this.hitTime = millis();
}
/**
* get scale factor
* @param multiple of original sprite
*/
public int getScaleFactor() {
return this.scaleFactor;
}
/**
* get sheild
*/
public boolean getSheild() {
if (GOD_MODE) return true;
long currentTime = millis();
return (currentTime - hitTime <= conf.sheildTime);
}
/**
@ -166,4 +191,8 @@ class Player extends Camera {
}
}
}
public void hit() {
this.hitTime = millis();
}
}

@ -0,0 +1,78 @@
/**
* Copyright 2022 Jamie Munro, All rights reserved
* CS5041 P1
* Game 1
*/
public class PlayerProjectile implements Projectile {
public final static int SPEED = 10;
public final static int DAMAGE = 1;
public final static int RANGE = 260;
private final int rotation;
private PImage sprite;
private int x;
private int y;
private int distanceTravelled;
public PlayerProjectile(int initialX, int initialY, int initialRotation, PImage sprite) {
this.x = initialX;
this.y = initialY;
this.rotation = initialRotation;
this.sprite = sprite;
this.distanceTravelled = 0;
}
@Override
public int getX() {
return this.x;
}
@Override
public int getY() {
return this.y;
}
@Override
public int getRotation() {
return this.rotation;
}
@Override
public PImage getSprite() {
return this.sprite;
}
@Override
public int getSpeed() {
return SPEED;
}
@Override
public int getDamage() {
return DAMAGE;
}
@Override
public int getRange() {
return RANGE;
}
/*
* Update projectile position
* @return true if projectile should be destroyed
*/
@Override
public boolean update() {
distanceTravelled++;
this.x = this.getX() + Math.round(SPEED * sin(radians(this.getRotation())));
this.y = this.getY() + Math.round(SPEED * -cos(radians(this.getRotation())));
if (distanceTravelled >= RANGE) return true;
return Boolean.parseBoolean(level.getTile(this.x, this.y).getProperty("solid"));
}
}

@ -0,0 +1,17 @@
/**
* Copyright 2022 Jamie Munro, All rights reserved
* CS5041 P1
* Game 1
*/
public interface Projectile {
public int getX();
public int getY();
public int getRotation();
public int getSpeed();
public int getDamage();
public int getRange();
public PImage getSprite();
public boolean update();
}

@ -21,17 +21,31 @@ aspectY=3
###################################################################################################
#frames per second (lower is performance is struggling)
framerate=60
framerate=30
###################################################################################################
# VIEWPORT PARAMETERS #
###################################################################################################
#viewport width in tiles (will be stretched if not set according to aspect ratio, and slightly off center if even)
viewportWidth=256
#This cause stretching if not set according to aspect ratio, and slightly off center if even
#METHOD: select a common factor of your adjusted width and height
#set viewport width to adjusted width / common factor
#set viewport height to adjusted height / common factor
#viewport height in tiles (will be stretched if not set according to aspect ratio, and slightly off center if even)
viewportHeight=192
#viewport width in tiles
viewportWidth=360
#viewport height in tiles
viewportHeight=270
###################################################################################################
# LEVEL PARAMETERS #
###################################################################################################
#Level width in tiles
levelWidth=1000
#Level Height in tiles
levelHeight=1000
###################################################################################################
# RESOURCE FILEPATHS #
@ -74,41 +88,113 @@ unknownTitle=99999
###################################################################################################
# STRINGS #
###################################################################################################
title=PLACEHOLDER
#Game of the game
title=INTO THE BREACH
#Title screen message
titleMsg1=Press A+B to start...
#Loading message
loadingMsg=Loading...
#Pause title
pauseMsg1=Paused
pauseMsg2=Press A+B to resume...
#Pause message
pauseMsg2=Press A to resume...
#Controls description
controlsMsg=Tip forwards - Accelerate\nTip backwards - Decelerate/Reverse\nRotate - Steer\nA - Shoot\nB - Bomb\nC - Pause (and reset steering neutral zone)
#Story text
storyCrawl=INTO THE BREACH\n\nHow did it come to this?\n\nFirst there were the weak men.\nThe incompetent men.\nThe complacent leaders.\n\nThen there were the strong men.\nMen who were willing to do\nwhat needed to be done.\nMen who could make dificult\ndecisions and stand by them.\nBut by then it was too late.\n\nEarth was gone.\nThere was nothing left.\nNothing but a burning\ndesire for vengance.\nA need to cause as much pain\nto those that had done this\nas was humanly possible... \nRevenge\n\nA last stand.\nA last battle.\nEarth's legacy.\nA fitting end to\nhumanity's short existance.\n\nAvenge your people.\nDie a hero's death.\nInto the breach...\n\n\n\n\nPress any button to continue...
###################################################################################################
# CONTROLLER PARAMETERS #
###################################################################################################
#Which serial device is the controller connected too
serialDevice=0
#Serial baud rate
baudRate=115200
#ASCII code for line endings (\n)
lineEnding=10
#How long should the serial message be
messageLength=33
#Character starting a micro:bit message
microbitControlChar=*
#Regex to split micro:bit message into array
microbitSplitRegex=[*abcxyzlh]
thresholdX=200
thresholdY=200
headingThreshold=7
#Acceleratometer threshold for game to recognise as input (x-axis)
thresholdX=180
#Acceleratometer threshold for game to recognise as input (y-axis)
thresholdY=280
#Compass threshold for game to recognise as input (degrees)
headingThreshold=5
###################################################################################################
# MOVEMENT PARAMETERS #
###################################################################################################
mainEngineForce=0.5
reverseEngineForce=0.25
rotationSpeed=7
#Amount of momentum added per unit time forwards engine is fired
mainEngineForce=0.25
#Amount of momentum subtracted per unit time rear engine is fired
reverseEngineForce=0.175
#number of degrees rotated per unit time side engine is firing
rotationSpeed=5
#Maximum forwards momentum
maxForwardsMomentum=6
#Maximum rearwards momentum
maxRearMomentum=4
friction=0.125
#Amount of momentum added/remove (towards zero) per unit time no engines are fired
friction=0.0625
###################################################################################################
# GAME PARAMETERS #
###################################################################################################
playerScaleFactor=10
#How long should be waited before showing story screen
storyWaitTime=15
#How fast should the story text scroll
scrollSpeed = 2
#Player scale factor in relation to tile size
playerScaleFactor=12
#starting lives
initialLives=3
#starting bombs
initialBombs=5
#number of different types of enemy
enemyTypes=5
#maximum number of player projectiles in flight
maxPlayerProjectiles=100
#minmium time in between shots (ms)
reloadTime=100
#time shielded for after a hit
sheildTime=5000
#speed of bomb expansion
bombSpeed=1
###################################################################################################
# REWARD PARAMETERS #
###################################################################################################
extraLifeInterval=50000
extraBombInterval=25000

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

Loading…
Cancel
Save