From 1975a99b4a5b7c9b8b201bb2e265d6688da47e7f Mon Sep 17 00:00:00 2001 From: Kai Simon Date: Thu, 2 Jun 2022 22:09:05 +0200 Subject: [PATCH] Basic Breakout Game --- .../.settings/org.eclipse.jdt.ui.prefs | 4 + .../org.eclipse.ltk.core.refactoring.prefs | 2 + GameProject/src/base/BreakoutGame.java | 38 ++++ GameProject/src/base/MovingObjectsGame.java | 38 ++++ GameProject/src/collider/package-info.java | 9 + .../src/controller/ReboundController.java | 17 ++ .../src/controller/ReboundController2.java | 35 ++++ GameProject/src/gameobjects/package-info.java | 7 + .../src/playground/BreakoutLevel1.java | 103 +++++++++++ .../src/playground/BreakoutLevelBase.java | 173 ++++++++++++++++++ .../src/playground/LevelMovingHitObjects.java | 139 ++++++++++++++ .../src/playground/LevelMovingObjects.java | 45 +++++ GameProject/src/playground/LevelWithBox.java | 35 ++++ GameProject/src/playground/package-info.java | 9 + 14 files changed, 654 insertions(+) create mode 100644 GameProject/.settings/org.eclipse.jdt.ui.prefs create mode 100644 GameProject/.settings/org.eclipse.ltk.core.refactoring.prefs create mode 100644 GameProject/src/base/BreakoutGame.java create mode 100644 GameProject/src/base/MovingObjectsGame.java create mode 100644 GameProject/src/collider/package-info.java create mode 100644 GameProject/src/controller/ReboundController.java create mode 100644 GameProject/src/controller/ReboundController2.java create mode 100644 GameProject/src/gameobjects/package-info.java create mode 100644 GameProject/src/playground/BreakoutLevel1.java create mode 100644 GameProject/src/playground/BreakoutLevelBase.java create mode 100644 GameProject/src/playground/LevelMovingHitObjects.java create mode 100644 GameProject/src/playground/LevelMovingObjects.java create mode 100644 GameProject/src/playground/LevelWithBox.java create mode 100644 GameProject/src/playground/package-info.java diff --git a/GameProject/.settings/org.eclipse.jdt.ui.prefs b/GameProject/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..3540004 --- /dev/null +++ b/GameProject/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +formatter_profile=_TabSpace2 +formatter_settings_version=22 +org.eclipse.jdt.ui.text.custom_code_templates= diff --git a/GameProject/.settings/org.eclipse.ltk.core.refactoring.prefs b/GameProject/.settings/org.eclipse.ltk.core.refactoring.prefs new file mode 100644 index 0000000..b196c64 --- /dev/null +++ b/GameProject/.settings/org.eclipse.ltk.core.refactoring.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.ltk.core.refactoring.enable.project.refactoring.history=false diff --git a/GameProject/src/base/BreakoutGame.java b/GameProject/src/base/BreakoutGame.java new file mode 100644 index 0000000..d9cc8b5 --- /dev/null +++ b/GameProject/src/base/BreakoutGame.java @@ -0,0 +1,38 @@ +package base; +import java.io.IOException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import playground.*; + + +/** + * main class to start a game with only one level. + * + */ +public class BreakoutGame extends GameLoop { + + private static Logger logger = LogManager.getLogger(BreakoutGame.class); + + /** + * adds only one level to play ({@link playground.LevelBreakout1}). + */ + @Override + public void defineLevels() { + this.resetLevels(); // removes Level1 added by superclass constructor + this.addLevel(new BreakoutLevel1()); // FIXME add this as soon as your level exists + } + + /** + * starts this game. + * + * @param args command line parameters (forwarded to {@link GameLoop#runGame(String[])}). + * @throws IOException if highscore.txt file cannot be written or accessed, the exception is + * thrown (and game ends). + */ + public static void main(String[] args) throws IOException { + GameLoop myGame = new BreakoutGame(); + logger.info("BreakoutGame program started."); + myGame.runGame(args); + } + +} diff --git a/GameProject/src/base/MovingObjectsGame.java b/GameProject/src/base/MovingObjectsGame.java new file mode 100644 index 0000000..93ec2ad --- /dev/null +++ b/GameProject/src/base/MovingObjectsGame.java @@ -0,0 +1,38 @@ +package base; + +import java.io.IOException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import playground.*; + +/** + * main class to start a game with only one level {@link playground.LevelMovingObjects}. + * + */ +public class MovingObjectsGame extends GameLoop { + + private static Logger logger = LogManager.getLogger(MovingObjectsGame.class); + + /** + * starts this game. + * + * @param args command line parameters (forwarded to {@link GameLoop#runGame(String[])}). + * @throws IOException if highscore.txt file cannot be written or accessed, the exception is + * thrown (and game ends). + */ + public static void main(String[] args) throws IOException { + logger.info("Starting Game Program...let's play and don't get hit!"); + GameLoop myGame = new MovingObjectsGame(); + myGame.runGame(args); + } + + /** + * adds only one level to play ({@link playground.LevelMovingObjects}). + */ + @Override + public void defineLevels() { + this.resetLevels(); + this.addLevel(new LevelMovingHitObjects()); + } + +} diff --git a/GameProject/src/collider/package-info.java b/GameProject/src/collider/package-info.java new file mode 100644 index 0000000..f101edb --- /dev/null +++ b/GameProject/src/collider/package-info.java @@ -0,0 +1,9 @@ +/** + * The package contains classes implementing a 'bounding box' area around game objects.
+ * The abstract base class {@link Collider} provides the abstract method {@link Collider#collidesWith(Collider)}, + * which needs to be implemented by child classes to detect and decide whether or not an object with such instance really collides with the other. + * {@link Collider} instances are to be used for game objects ({@link gameobjects}); see constructors.
+ * + * The benefit of seperating Colliders from visual representations is that the area for collisions can be smaller/bigger/other shape to improve game play experience. + */ +package collider; diff --git a/GameProject/src/controller/ReboundController.java b/GameProject/src/controller/ReboundController.java new file mode 100644 index 0000000..456d6ad --- /dev/null +++ b/GameProject/src/controller/ReboundController.java @@ -0,0 +1,17 @@ +package controller; + +public class ReboundController extends ObjectController{ + + @Override + public void updateObject() { + if(getX() < 30 || getX() > 670) { + setVX(getVX() * -1); + } + + if(getY() < 30 || getY() > 670) { + setVY(getVY() * -1); + } + applySpeedVector(); + } + +} diff --git a/GameProject/src/controller/ReboundController2.java b/GameProject/src/controller/ReboundController2.java new file mode 100644 index 0000000..1bee573 --- /dev/null +++ b/GameProject/src/controller/ReboundController2.java @@ -0,0 +1,35 @@ +package controller; + +/** Controller to let Objects bounce from the outer level limits back and forth. + * + */ +public class ReboundController2 extends ObjectController { + + /** inverts the x y direction speeds if the outer limits are reached. + * + */ + @Override + public void updateObject() { + double sizeX = this.getPlayground().preferredSizeX(); + double sizeY = this.getPlayground().preferredSizeY(); + double objSizeX = 30; + double objSizeY = 30; + + if (this.getX() < objSizeX) { + this.setVX(this.getVX() * -1); + this.setX(objSizeX); + } else if (this.getX() > sizeX - objSizeX) { + this.setVX(this.getVX() * -1); + this.setX(sizeX - objSizeX); + } + if (this.getY() < objSizeY) { + this.setVY(this.getVY() * -1); + this.setY(objSizeY); + } else if (this.getY() > sizeY - objSizeY) { + this.setVY(this.getVY() * -1); + this.setY(sizeY - objSizeY); + } + this.applySpeedVector(); + } + +} diff --git a/GameProject/src/gameobjects/package-info.java b/GameProject/src/gameobjects/package-info.java new file mode 100644 index 0000000..7ec704e --- /dev/null +++ b/GameProject/src/gameobjects/package-info.java @@ -0,0 +1,7 @@ +/** + * The package gameobjects contains all objects with a visual representation on screen. + * They can be combined to use controller instances for their behavior (subclasses of {@link controller.ObjectController}). + * The abstract base class is {@link GameObject}, which forces child-classes to implement the method + * {@link GameObject#updateObject()}. + */ +package gameobjects; diff --git a/GameProject/src/playground/BreakoutLevel1.java b/GameProject/src/playground/BreakoutLevel1.java new file mode 100644 index 0000000..37fb5b7 --- /dev/null +++ b/GameProject/src/playground/BreakoutLevel1.java @@ -0,0 +1,103 @@ +package playground; + +import java.awt.Color; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import controller.EgoController; +import controller.ReboundController; +import gameobjects.GameObject; +import gameobjects.RectObject; + +public class BreakoutLevel1 extends BreakoutLevelBase{ + + private static Logger logger = LogManager.getLogger(BreakoutLevel1.class); + + /**Deletes the hitted brick and change direction of Ball. + * + * @param ball GameObject + * @param brick GameObject + */ + @Override + protected void actionIfBallHitsBrick(GameObject ball, GameObject brick) { + logger.info(brick.getId()+" is hitted and deleted!"); + this.deleteObject(brick.getId()); + ball.setVX(ball.getVX() * 1.0); + ball.setVY(ball.getVY() * -1.0); + } + + /**Change Direction from Ball if hits the Ego. + * + * @param ball GameObject + * @param ego GameObject + */ + @Override + protected void actionIfBallHitsEgo(GameObject ball, GameObject ego) { + logger.info("Ball is collided with ego."); + ball.setVX(ball.getVX() * 1.0); + ball.setVY(ball.getVY() * -1.0); + } + + /**Creates Ego object and returns it, but doesn't add it here. + * add it at the prepare Level Method + * + * @return ego + */ + @Override + protected GameObject createEgoObject() { + RectObject ego = new RectObject("ego", this, 350, 550, 0, 0, 80, 10, Color.blue); + this.addObject(ego); + ego.addController(new EgoController(ego.getWidth(), ego.getHeight())); + ego.addCollider(new collider.RectCollider(ego.getId(), ego, ego.getWidth(), ego.getHeight())); + return ego; + } + + /**Creates Ball object and returns it, but doesn't add it here. + * add it at the prepare Level Method + * + * @return ball + */ + @Override + protected GameObject createBall() { + gameobjects.FallingStar ball = new gameobjects.FallingStar("ball", this, 350, 350, 120, 120, Color.red, 5); + ball.addController(new ReboundController()); + + return ball; + } + + /** Creates a Brick, but doesn't add it here. + * @param row int + * @param column int + * + * @return brick GameObject + */ + @Override + protected GameObject createBrick(int row, int column) { + RectObject brick = new RectObject("brick"+row+column, this, 40+(column*68), 40+(row*60), 0, 0, 60, 30, Color.green); + brick.addCollider(new collider.RectCollider(brick.getId(), brick, brick.getWidth(), brick.getHeight())); + + return brick; + } + + /**Prepares the Level, sets up the Playing Grid of Bricks and an ego and Ball object. + * + * @param level String + */ + @Override + public void prepareLevel(String level) { + + this.ego = createEgoObject(); + this.ball = createBall(); + + this.addObject(this.ego); + this.addObject(this.ball); + + for (int i = 0; i < 3; i++) { + for (int k = 0; k < 10; k++) { + this.addObject(createBrick(i, k)); + } + } + logger.info("Level is ready!"); + } +} diff --git a/GameProject/src/playground/BreakoutLevelBase.java b/GameProject/src/playground/BreakoutLevelBase.java new file mode 100644 index 0000000..79ddd44 --- /dev/null +++ b/GameProject/src/playground/BreakoutLevelBase.java @@ -0,0 +1,173 @@ +package playground; + +import gameobjects.*; +import java.util.LinkedList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import java.awt.Graphics2D; +import controller.*; + +public abstract class BreakoutLevelBase extends Playground { + + /** + * instance of the ball, needs to be set by {@link #prepareLevel(String) } + * + */ + protected GameObject ball = null, + /** + * instance of the ego objects, needs to be set by {@link #prepareLevel(String) } + * + */ + ego = null; + + private static Logger logger = LogManager.getLogger(BreakoutLevelBase.class); + + public BreakoutLevelBase() { + super(); + this.canvasX = this.preferredSizeX(); + this.canvasY = this.preferredSizeY(); + } + + /** + * signals to game engine that the game has finished by game over. called every game loop. default + * implementation is always false. + * + * @return false + */ + public boolean gameOver() { + return false; + } + + + /** + * signals to game engine that the game has finished by success. called every game loop. default + * implementation is always false. + * + * @return false + */ + public boolean levelFinished() { + return false; + } + + + /** + * signals to game engine that the game has been requested to be reseted (restart). called every + * game loop. default implementation is always false. + * + * @return false + */ + public boolean resetRequested() { + return false; + } + + /** + * unimplemented empty method called by game engine every loop. + * + */ + public void redrawLevel(Graphics2D g) { + + } + + + + /** + * Signal that the level has a size of 700x700 pixels. + * + * @return x size of level 700 + */ + @Override + public int preferredSizeX() { + return 700; + } + + /** + * Signal that the level has a size of 700x700 pixels. + * + * @return y size of level 700 + */ + @Override + public int preferredSizeY() { + return 700; + } + + + /** + * Method that gets called by applyGameLogic() whenever the ball collides with a brick. + * + * + * @param ball A reference to the current ball object + * @param brick A reference to the ego object + */ + protected abstract void actionIfBallHitsBrick(GameObject ball, GameObject brick); + + /** + * Method that gets called by applyGameLogic() whenever the ball collides with the ego object. + * + * @param ball A reference to the current ball object + * @param ego A reference to the ego object + */ + protected abstract void actionIfBallHitsEgo(GameObject ball, GameObject ego); + + /** + * checks for interactions between GameObjects; notably ball with ego and ball with brick. + * In case of detected collisions, it calls either {@link #actionIfBallHitsBrick(GameObject, GameObject)} + * or {@link #actionIfBallHitsEgo(GameObject, GameObject)}. + * Called every game loop. + */ + @Override + public void applyGameLogic() { + LinkedList bricks = collectObjects("brick", false); + + for (GameObject brick : bricks) { + if (this.ball.collisionDetection(brick)) { + logger.trace("Collision detected of ball and brick " + brick.getId()); + this.actionIfBallHitsBrick(this.ball, brick); + } + } + + if (this.ego.collisionDetection(ball)) { + logger.trace("Collision detected of ball and ego"); + this.actionIfBallHitsEgo(this.ball, this.ego); + } + } + + /** + * Creates the ego object and returns it, called by {@link #prepareLevel}. Does NOT add the ego + * object to the playground, but returns it. + * + * @return The created ego object instance (of class {@link RectObject} with + * {@link EgoController}. + */ + protected abstract GameObject createEgoObject(); + + /** + * Creates the ball object and returns it, called by #prepareLevel. Does NOT add the ball object + * to the playground, but returns it. + * + * @return The created ball object instance (of class {@link FallingStar}) + */ + protected abstract GameObject createBall(); + + /** + * Creates the GameObject (RectObject) instance representing a single brick at a certain grid + * position. The brick is NOT added here, but returned. + * + * @param row row position in the grid, ranges from 0 to calcNrBricksY()-1 + * @param column column position in the grid of bricks, ranges from 0 to calcNrBricksX()-1 + * @return The GameObject instance (really a RectObject) representing the created brick. + */ + protected abstract GameObject createBrick(int row, int column); + + /** + * Prepares a generic Breakout-Type level. This method relies on the methods {@link #createEgoObject()}, + * {@link #createBall} and {@link #createBrick}, among others, which are meant to be overwritten + * in subclasses.
+ * Attention: the attributes {@link #ball} and {@link #ego} need to be set properly to GameObject + * instances when implementing this method {@link #prepareLevel(String)}. + * + * @param level String passes by the game engine (not used currently and can be ignored). + */ + @Override + abstract public void prepareLevel(String level); + +} diff --git a/GameProject/src/playground/LevelMovingHitObjects.java b/GameProject/src/playground/LevelMovingHitObjects.java new file mode 100644 index 0000000..b0c7548 --- /dev/null +++ b/GameProject/src/playground/LevelMovingHitObjects.java @@ -0,0 +1,139 @@ +package playground; + +import java.awt.Color; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import controller.ReboundController; +import gameobjects.GameObject; +import gameobjects.RectObject; + +/** + * Level that creates two RectObjects moving around and if ego is hit by them + * game is directly lost (lives = 0). + * + */ +public class LevelMovingHitObjects extends SpaceInvadersLevel { + + + private static Logger logger = LogManager.getLogger(LevelMovingHitObjects.class); + + /** + * Prepares the Level and generates two Rectangles with rbeound controller and collider. + * + * @param id String + */ + @Override + public void prepareLevel(String id) { + logger.info("PREPARE The Level"); + + super.prepareLevel(id); + + /** Generate the rectangle Objects */ + RectObject fly_enemy1 = new RectObject("fly_enemy1", this, 300, 300, 75, 40, 40, 40, Color.blue); + RectObject fly_enemy2 = new RectObject("fly_enemy2", this, 200, 200, 20, 90, 40, 40, Color.green); + + /** Add Rectangles to Level */ + this.addObject(fly_enemy1); + this.addObject(fly_enemy2); + + /** Add Controller(Own ReboundController) to the rectangle Objects */ + fly_enemy1.addController(new ReboundController()); + fly_enemy2.addController(new ReboundController()); + + fly_enemy1.addCollider( + new collider.RectCollider(fly_enemy1.getId(), fly_enemy1, fly_enemy1.getWidth(), fly_enemy1.getHeight())); + fly_enemy2.addCollider( + new collider.RectCollider(fly_enemy2.getId(), fly_enemy2, fly_enemy2.getWidth(), fly_enemy2.getHeight())); + } + + /** + * "Moving Hitting Objects Level!" is the message. + * + * @return String "Moving & Hitting Objects Level!" + */ + @Override + protected String getStartupMessage() { + logger.info("Level has started!"); + return "Moving & Hitting Objects Level!"; + } + + + /** Gets the type of an enemy if a sub type is defined, otherwise returns the whole id as type. + * + * @param id ID of the enemy + * @return type String type or wholeID + */ + String getEnemyType(String id) + { + String enemyType = ""; + + /** Get the type of an enemy, currently only one sub type is allowed, + * something like "fly_bird_enemy" and "fly_bat_enemy" is not supported yet. + * If no sub Type is defined the whole id is transmitted. + * If the type only contains "enemy" there is no further testing(numeration is ignored)*/ + for (int i = 0; i < id.length(); i++) { + if (id.charAt(i) != '_' && !enemyType.equals("enemy")) { + enemyType += id.charAt(i); + } else { + break; + } + } + logger.debug("Enemy Type to check return string: "+enemyType); + return enemyType; + } + + /** This Method controls what happens when the Ego(Player) Collides with an enemy. + * + * @param enemy GameObject + * @param ego Gameobject + */ + @Override + void actionIfEgoCollidesWithEnemy(GameObject enemy, GameObject ego) { + String enemyType = getEnemyType(enemy.id); + + /** Switch, to act out specific enemy behaviors based on their Type, + * if the id is misspelled or the type doesn't exist it behaves like an ordinary Alien Attack! + */ + switch(enemyType) { + case "fly": { + logger.info("Enemy is an Flying Rect Object"); + setGlobalFlag("egoLives", Integer.valueOf(0)); + this.lost = true; + };break; + case "enemy": { + logger.info("Enemy is Alien"); + super.actionIfEgoCollidesWithEnemy(enemy, ego); + };break; + default: { + logger.warn("Enemy is unknown! default case triggered"); + super.actionIfEgoCollidesWithEnemy(enemy, ego); + };break; + } + } + + + /** Matching Behaviors to the given enemy Types if hitting them. + * + * @param e GameObject enemy + * @param shot GameObject + */ + @Override + void actionIfEnemyIsHit(GameObject e, GameObject shot) + { + String enemyType = getEnemyType(e.id); + switch(enemyType) { + case "fly": { + logger.info("Shot doesn't harm the flying enemy!"); + shot.setActive(false); + };break; + case "enemy": { + super.actionIfEnemyIsHit(e, shot); + };break; + default: { + logger.warn("Enemy is unknown! default case triggered, nothing happened, maybe its Innocent?!"); + };break; + } + } +} diff --git a/GameProject/src/playground/LevelMovingObjects.java b/GameProject/src/playground/LevelMovingObjects.java new file mode 100644 index 0000000..0644ef6 --- /dev/null +++ b/GameProject/src/playground/LevelMovingObjects.java @@ -0,0 +1,45 @@ +package playground; + +import java.awt.Color; + +import controller.ReboundController; +import gameobjects.RectObject; + +/** + * This level adds two distracting objects to the canvas that cannot collide but + * bounce around all the time. + */ +public class LevelMovingObjects extends SpaceInvadersLevel { + + + /**Prepares the level with two rectangles which can move around and bounce of the walls + * + * @param id String + */ + @Override + public void prepareLevel(String id) { + super.prepareLevel(id); + + /** Generate the rectangle Objects*/ + RectObject rectObj1 = new RectObject("Rectangle1", this, 300, 300, 170, 70, 30, 30, Color.blue); + RectObject rectObj2 = new RectObject("Rectangle2", this, 200, 200, 50, 170, 30, 30, Color.green); + + /** Add Rectangles to Level*/ + this.addObject(rectObj1); + this.addObject(rectObj2); + + /** Add Controller(Own ReboundController) to the rectangle Objects*/ + rectObj1.addController(new ReboundController()); + rectObj2.addController(new ReboundController()); + } + + /** + * "Moving Objects Level!" is the message. + * + * @return String "Moving Objects Level!" + */ + @Override + protected String getStartupMessage() { + return "Moving Objects Level!"; + } +} diff --git a/GameProject/src/playground/LevelWithBox.java b/GameProject/src/playground/LevelWithBox.java new file mode 100644 index 0000000..1e1cb84 --- /dev/null +++ b/GameProject/src/playground/LevelWithBox.java @@ -0,0 +1,35 @@ +package playground; + +import java.awt.Color; + +import gameobjects.RectObject; + +/** + * Level with an Red Box at the top of the Screen, + * imitates a standard level but with an custom Object and custom-startup Message. + * + */ +public class LevelWithBox extends SpaceInvadersLevel { + + /**Prepares the Level with various things + * Calls the Overriden function and then, + * adds an Rectangle Object in red to the top of the Screen. + * + * @param id String : id of level + */ + @Override + public void prepareLevel(String id) { + super.prepareLevel(id); + addObject(new RectObject(id, this, 350, 100, 0, 0, 700, 250, Color.red)); + } + + + /**Returns a startup Message for the Level + * @return String + */ + @Override + protected String getStartupMessage() { + return "Box-Level!"; + } + +} diff --git a/GameProject/src/playground/package-info.java b/GameProject/src/playground/package-info.java new file mode 100644 index 0000000..972351d --- /dev/null +++ b/GameProject/src/playground/package-info.java @@ -0,0 +1,9 @@ +/** + * The package playground contains all level specific logic and control of level logic. + * The structure and general logic (with global and local flags to be stored/used) + * is provided in abstract base class {@link Playground}.
+ * Child-classes implement specific logic for one level and game type (e.g. {@link SpaceInvadersLevel}).
+ * + * Generally, the base class {@link Playground} supports totally different game types to be implemented. + */ +package playground;