Document GamePanel and remove obsolete documentation

master
John 2022-06-20 13:25:45 -04:00
parent 7595603e54
commit e65e0fc385
2 changed files with 111 additions and 100 deletions

View File

@ -1,25 +0,0 @@
# Function Overview
This document intended to assist developers who seek to extend the game by modifying source code in src/.
## Classes
### BackgroundImage
Its constructor takes the following arguments:
`int x, int y, BufferedImage backgroundImage, int width, int height, int parallaxRatio`
Draws a background image that moves one pixel every `parallaxRatio` pixels when `BackgroundImage.draw()` is called.
### Camera
placeholder
## Utility Functions
These functions are located in the `public final` class UtilityFunction; please note that the UtilityFunction constructor is private to prevent initialization.
### readFromFile
placeholder

View File

@ -1,4 +1,6 @@
/* GamePanel class acts as the main "game loop" - continuously runs the game and calls whatever needs to be called /* Eric Li, Charlie Zhao, ICS4U, Finished 2022-06-20
GamePanel class acts as the main "game loop" - continuously runs the game and calls whatever needs to be called
Child of JPanel because JPanel contains methods for drawing to the screen Child of JPanel because JPanel contains methods for drawing to the screen
@ -46,7 +48,7 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
public transient Thread gameThread; public transient Thread gameThread;
public transient Image image; public transient Image image;
public transient Graphics graphics; public transient Graphics graphics;
public transient boolean isNewStart = false; public transient boolean isNewStart;
public Player player; public Player player;
public BackgroundImage background, cloudOneBackground, cloudTwoBackground, cloudThreeBackground; public BackgroundImage background, cloudOneBackground, cloudTwoBackground, cloudThreeBackground;
public int playerFrame, enemyFrame; public int playerFrame, enemyFrame;
@ -59,22 +61,19 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
public DialogueMenu dialogueMenu; public DialogueMenu dialogueMenu;
public ArrayList<String> dialogueArray = new ArrayList<String>(); public ArrayList<String> dialogueArray = new ArrayList<String>();
public BufferedImageWrapper[][][] playerSpriteArray = new BufferedImageWrapper[2][2][11]; public BufferedImageWrapper[][][] playerSpriteArray = new BufferedImageWrapper[2][2][11];
public BufferedImageWrapper[][][] slimeSpriteArray = new BufferedImageWrapper[2][2][3]; public BufferedImageWrapper[][][] slimeSpriteArray = new BufferedImageWrapper[2][2][3];
public BufferedImageWrapper[] explosionArray = new BufferedImageWrapper[9]; public BufferedImageWrapper[] explosionArray = new BufferedImageWrapper[9];
//public static ArrayList<Tile>map = new ArrayList<Tile>();
public Tile[][]map = new Tile[1000][18]; public Tile[][]map = new Tile[1000][18];
public ArrayList<Middleware> middlewareArray = new ArrayList<Middleware>(); public ArrayList<Middleware> middlewareArray = new ArrayList<>();
public ArrayList<Tile>particleTiles = new ArrayList<Tile>(); public ArrayList<Tile>particleTiles = new ArrayList<>();
public ArrayList<Tile>shootingTiles = new ArrayList<Tile>(); public ArrayList<Tile>shootingTiles = new ArrayList<>();
public ArrayList<FireBall>fireballs = new ArrayList<FireBall>(); public ArrayList<FireBall>fireballs = new ArrayList<>();
public ArrayList<NonPlayer>enemy = new ArrayList<NonPlayer>(); public ArrayList<NonPlayer>enemy = new ArrayList<>();
public ArrayList<StickyBomb>bombs = new ArrayList<>(); public ArrayList<StickyBomb>bombs = new ArrayList<>();
public BombDirectionShow bombDir = null; public BombDirectionShow bombDir = null;
public ArrayList<Particle>particles = new ArrayList<Particle>(); public ArrayList<Particle>particles = new ArrayList<Particle>();
@ -82,15 +81,11 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
// image imports begin here // image imports begin here
public BufferedImageWrapper backgroundImage = new BufferedImageWrapper(("img/backgrounds/pointyMountains.png")); public BufferedImageWrapper backgroundImage = new BufferedImageWrapper(("img/backgrounds/pointyMountains.png"));
public BufferedImageWrapper box = new BufferedImageWrapper(("img/tiles/boxes/box.png"));
public BufferedImageWrapper boxCoin = new BufferedImageWrapper(("img/tiles/boxes/boxCoin.png"));
public BufferedImageWrapper cloud1 = new BufferedImageWrapper(("img/backgrounds/cloud1.png")); public BufferedImageWrapper cloud1 = new BufferedImageWrapper(("img/backgrounds/cloud1.png"));
public BufferedImageWrapper cloud2 = new BufferedImageWrapper(("img/backgrounds/cloud2.png")); public BufferedImageWrapper cloud2 = new BufferedImageWrapper(("img/backgrounds/cloud2.png"));
public BufferedImageWrapper cloud3 = new BufferedImageWrapper(("img/backgrounds/cloud3.png")); public BufferedImageWrapper cloud3 = new BufferedImageWrapper(("img/backgrounds/cloud3.png"));
public BufferedImageWrapper bomb; public BufferedImageWrapper bomb;
public BufferedImageWrapper narratorPortrait = new BufferedImageWrapper(("img/dialogue/Gunther.png")); public BufferedImageWrapper narratorPortrait = new BufferedImageWrapper(("img/dialogue/Gunther.png"));
public BufferedImageWrapper villainPortrait = new BufferedImageWrapper(("img/dialogue/Bouncer.png"));
public String lastText;
public boolean isContinue; public boolean isContinue;
public boolean isRunning; public boolean isRunning;
public ArrayList<WallSign> tutorialSign = new ArrayList<WallSign>(); public ArrayList<WallSign> tutorialSign = new ArrayList<WallSign>();
@ -99,17 +94,22 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
public GamePanel(JPanel gameFrame) throws IOException, SpriteException, UnsupportedAudioFileException, LineUnavailableException { public GamePanel(JPanel gameFrame) throws IOException, SpriteException, UnsupportedAudioFileException, LineUnavailableException {
// set gameFrame to enable switching between different panels (in the gameFrame)
this.gameFrame = gameFrame; this.gameFrame = gameFrame;
camera = new Camera(0); camera = new Camera(0);
// create background mountains and images
background = new BackgroundImage(0, 0, backgroundImage, GAME_WIDTH, GAME_HEIGHT, 10, camera); background = new BackgroundImage(0, 0, backgroundImage, GAME_WIDTH, GAME_HEIGHT, 10, camera);
cloudOneBackground = new BackgroundImage(200, 200, cloud1, cloud1.image.getWidth(), cloud1.image.getHeight(), 5, camera); cloudOneBackground = new BackgroundImage(200, 200, cloud1, cloud1.image.getWidth(), cloud1.image.getHeight(), 5, camera);
cloudTwoBackground = new BackgroundImage(600, 250, cloud2, cloud2.image.getWidth(), cloud3.image.getHeight(), 5, camera); cloudTwoBackground = new BackgroundImage(600, 250, cloud2, cloud2.image.getWidth(), cloud3.image.getHeight(), 5, camera);
cloudThreeBackground = new BackgroundImage(1000, 200, cloud3, cloud2.image.getWidth(), cloud3.image.getHeight(), 5, camera); cloudThreeBackground = new BackgroundImage(1000, 200, cloud3, cloud2.image.getWidth(), cloud3.image.getHeight(), 5, camera);
// create pause menu and text in pause menu
pauseMenu = new PauseMenu(GAME_HEIGHT/2, 100, 400, 400, GAME_WIDTH, new Font(Font.MONOSPACED, Font.BOLD, 60), "Paused", true); pauseMenu = new PauseMenu(GAME_HEIGHT/2, 100, 400, 400, GAME_WIDTH, new Font(Font.MONOSPACED, Font.BOLD, 60), "Paused", true);
pauseMenuExitOne = new PauseMenu(GAME_HEIGHT/2, 0, 400, 400, GAME_WIDTH, new Font(Font.MONOSPACED, Font.BOLD, 24), "Press ENTER to return", true); pauseMenuExitOne = new PauseMenu(GAME_HEIGHT/2, 0, 400, 400, GAME_WIDTH, new Font(Font.MONOSPACED, Font.BOLD, 24), "Press ENTER to return", true);
pauseMenuExitTwo = new PauseMenu(GAME_HEIGHT/2, -20, 400, 400, GAME_WIDTH, new Font(Font.MONOSPACED, Font.BOLD, 24), "to the main menu", true); pauseMenuExitTwo = new PauseMenu(GAME_HEIGHT/2, -20, 400, 400, GAME_WIDTH, new Font(Font.MONOSPACED, Font.BOLD, 24), "to the main menu", true);
pauseMenuResume = new PauseMenu(GAME_HEIGHT/2, -50, 400, 400, GAME_WIDTH, new Font(Font.MONOSPACED, Font.BOLD, 18), "(or press ESC to resume)", true); pauseMenuResume = new PauseMenu(GAME_HEIGHT/2, -50, 400, 400, GAME_WIDTH, new Font(Font.MONOSPACED, Font.BOLD, 18), "(or press ESC to resume)", true);
// create menu shown when loading game
loadingMenu = new PauseMenu(GAME_HEIGHT/2, 0, GAME_WIDTH, GAME_HEIGHT, GAME_WIDTH, new Font(Font.MONOSPACED, Font.BOLD, 60), "Loading...", true); loadingMenu = new PauseMenu(GAME_HEIGHT/2, 0, GAME_WIDTH, GAME_HEIGHT, GAME_WIDTH, new Font(Font.MONOSPACED, Font.BOLD, 60), "Loading...", true);
// create text box shown during dialogue
dialogueMenu = new DialogueMenu(GAME_HEIGHT-100, 200, new Font(Font.MONOSPACED, Font.BOLD, 20), narratorPortrait, true); dialogueMenu = new DialogueMenu(GAME_HEIGHT-100, 200, new Font(Font.MONOSPACED, Font.BOLD, 20), narratorPortrait, true);
try { try {
// load player sprites from disk here // load player sprites from disk here
@ -141,13 +141,15 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
// load bomb sprites // load bomb sprites
bomb = new BufferedImageWrapper(("img/misc/bomb.png")); bomb = new BufferedImageWrapper(("img/misc/bomb.png"));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace(); // TODO: remove stack trace
} }
player = new Player(GAME_WIDTH/2, GAME_HEIGHT/2, playerSpriteArray); //create a player controlled player, set start location to middle of screenk player = new Player(GAME_WIDTH/2, GAME_HEIGHT/2, playerSpriteArray); //create a player controlled player, set start location to middle of screenk
// the height of 35 is set because it is half of the original tile height (i.e., 70px) // add mouse and keyboard listeners
addUserInterface(); addUserInterface();
this.setPreferredSize(new Dimension(GAME_WIDTH, GAME_HEIGHT)); this.setPreferredSize(new Dimension(GAME_WIDTH, GAME_HEIGHT));
// indicate that dialogue should be shown on load
isNewStart = true; isNewStart = true;
// allow while look in run() to run
isRunning = true; isRunning = true;
} }
@ -156,7 +158,7 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
this.addKeyListener(this); //start listening for keyboard input this.addKeyListener(this); //start listening for keyboard input
// request focus when the CardLayout selects this game // request focus when the CardLayout selects this game
requestFocusable(); requestFocusable();
//add the MousePressed method from the MouseAdapter - by doing this we can listen for mouse input. We do this differently from the KeyListener because MouseAdapter has SEVEN mandatory methods - we only need one of them, and we don't want to make 6 empty methods // add the mouse adapter methods
addMouseListener(); addMouseListener();
} }
@ -180,10 +182,8 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
} else if (!isDialogue && !isPaused) { } else if (!isDialogue && !isPaused) {
try { try {
player.mouseReleased(e); player.mouseReleased(e);
} catch (IOException ex) { } catch (IOException | SpriteException ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex); // TODO: remove stack trace
} catch (SpriteException ex) {
throw new RuntimeException(ex);
} }
} }
} }
@ -195,10 +195,8 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
public void mouseDragged(MouseEvent e) { public void mouseDragged(MouseEvent e) {
try { try {
player.mouseDragged(e); player.mouseDragged(e);
} catch (IOException ex) { } catch (IOException | SpriteException ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex); // TODO: remove stack
} catch (SpriteException ex) {
throw new RuntimeException(ex);
} }
} }
public void mouseMoved(MouseEvent e) { public void mouseMoved(MouseEvent e) {
@ -219,12 +217,12 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
}); });
} }
// startThread is to be called after the game has started to avoid any issues TODO: better explanation // startThread is to be called after the game has started to avoid any issues
// this allows serialization to work without having to implement a custom readObject method
public void startThread() { public void startThread() {
//make this class run at the same time as other classes (without this each class would "pause" while another class runs). By using threading we can remove lag, and also allows us to do features like display timers in real time! //make this class run at the same time as other classes (without this each class would "pause" while another class runs). By using threading we can remove lag, and also allows us to do features like display timers in real time!
gameThread = new Thread(this); gameThread = new Thread(this);
gameThread.start(); gameThread.start();
//System.out.println(gameThread);
} }
//paint is a method in java.awt library that we are overriding. It is a special method - it is called automatically in the background in order to update what appears in the window. You NEVER call paint() yourself //paint is a method in java.awt library that we are overriding. It is a special method - it is called automatically in the background in order to update what appears in the window. You NEVER call paint() yourself
@ -233,12 +231,8 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
image = createImage(GAME_WIDTH, GAME_HEIGHT); //draw off screen image = createImage(GAME_WIDTH, GAME_HEIGHT); //draw off screen
graphics = image.getGraphics(); graphics = image.getGraphics();
try { try {
draw(graphics, playerFrame, enemyFrame);//update the positions of everything on the screen draw(graphics, playerFrame);//update the positions of everything on the screen
} catch (IOException e) { } catch (IOException | LineUnavailableException | UnsupportedAudioFileException e) {
throw new RuntimeException(e);
} catch (UnsupportedAudioFileException e) {
throw new RuntimeException(e);
} catch (LineUnavailableException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
g.drawImage(image, 0, 0, this); //move the image on the screen g.drawImage(image, 0, 0, this); //move the image on the screen
@ -246,7 +240,8 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
} }
//call the draw methods in each class to update positions as things move //call the draw methods in each class to update positions as things move
public void draw(Graphics g, int playerFrame, int enemyFrame) throws IOException, UnsupportedAudioFileException, LineUnavailableException { public void draw(Graphics g, int playerFrame) throws IOException, UnsupportedAudioFileException, LineUnavailableException {
// save old color and font to enable resetting to these after drawing the tutorial
Color oldColor = g.getColor(); Color oldColor = g.getColor();
Font oldFont = g.getFont(); Font oldFont = g.getFont();
background.draw(g); background.draw(g);
@ -263,10 +258,10 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
g.setColor(oldColor); g.setColor(oldColor);
g.setFont(oldFont); g.setFont(oldFont);
if (isPaused || isDialogue) { if (isPaused || isDialogue) {
// set player frame to 7 to prevent frame from changing when player moves // set player frame to 7 to prevent frame from changing when player inputs key presses
playerFrame = 7; playerFrame = 7;
Graphics2D g2d = (Graphics2D)(g); Graphics2D g2d = (Graphics2D)(g);
// remove extraneous details from game when paused // remove extraneous details (like bomb counter) from game when paused
g2d.setPaint(Color.white); g2d.setPaint(Color.white);
} }
//Don't want to draw off screen items //Don't want to draw off screen items
@ -279,11 +274,15 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
} }
} }
} }
// this is not an enhanced for loop to prevent concurrent modification exception
for(int i=0; i<enemy.size(); i++){ for(int i=0; i<enemy.size(); i++){
enemy.get(i).draw(g, enemyFrame); enemy.get(i).draw(g, enemyFrame);
} }
// increment current frame player is on if player.draw reports that the player has moved
playerFrameCounter += player.draw(g, playerFrame); playerFrameCounter += player.draw(g, playerFrame);
for(int i=0; i<bombs.size(); i++){ for(int i=0; i<bombs.size(); i++){
// to prevent concurrent modification exception
// removes exploded bombs, and draws unexploded bombs
if(i<bombs.size()) { if(i<bombs.size()) {
if (bombs.get(i).erase) { if (bombs.get(i).erase) {
bombs.remove(i); bombs.remove(i);
@ -292,6 +291,7 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
} }
} }
} }
// draw fireballs if not exploded, otherwise remove fireballs
for(int i=0; i<fireballs.size(); i++){ for(int i=0; i<fireballs.size(); i++){
if(fireballs.get(i)!=null&&fireballs.get(i).canUpdate(0,0)) { if(fireballs.get(i)!=null&&fireballs.get(i).canUpdate(0,0)) {
fireballs.get(i).draw(g); fireballs.get(i).draw(g);
@ -303,6 +303,7 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
} }
// show particles
for(int i=0; i<particles.size(); i++){ for(int i=0; i<particles.size(); i++){
// todo: find cause of particles being null // todo: find cause of particles being null
if(i<particles.size()&&particles.get(i)!=null) { if(i<particles.size()&&particles.get(i)!=null) {
@ -311,15 +312,18 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
if (particles.get(i).lifeSpan <= 0) { if (particles.get(i).lifeSpan <= 0) {
particles.remove(i); particles.remove(i);
} }
} else {
throw new RuntimeException(); // TODO: remove stack trace
} }
} }
// show bomb trajectory preview if player is able to throw a bomb
if(player.leftMouseDown&&LevelManager.bombs>0&&!player.holdingSteel){ if(player.leftMouseDown&&LevelManager.bombs>0&&!player.holdingSteel){
bombDir = new BombDirectionShow(this.player.x + this.camera.x + player.WIDTH/2, this.player.y+player.HEIGHT/2, bombDir = new BombDirectionShow(this.player.x + this.camera.x + player.WIDTH/2, this.player.y+player.HEIGHT/2,
(player.mouseX - this.player.x) / 20, (player.mouseY - this.player.y) / 10); (Player.mouseX - this.player.x) / 20, (Player.mouseY - this.player.y) / 10);
bombDir.draw(g); bombDir.draw(g);
} }
// render steel being held based on mouse position
// additionally, change color of steel depending on whether the player can place the steel or not
if(player.holdingSteel){ if(player.holdingSteel){
String filePath = ""; String filePath = "";
if(player.canPlaceSteel){ if(player.canPlaceSteel){
@ -331,34 +335,42 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
double y = ((player.mouseY / Tile.length))*Tile.length; double y = ((player.mouseY / Tile.length))*Tile.length;
g.drawImage(getImage(filePath),x*Tile.length - (GamePanel.GAME_WIDTH/2)-camera.x,(int)y,Tile.length,Tile.length,null); g.drawImage(getImage(filePath),x*Tile.length - (GamePanel.GAME_WIDTH/2)-camera.x,(int)y,Tile.length,Tile.length,null);
} }
// draw bomb counter (bomb image and amount of bombs remaining)
g.drawImage(bomb.image,20,20,35,35,null); g.drawImage(bomb.image,20,20,35,35,null);
g.drawString("X"+LevelManager.bombs,60,40); g.drawString("X"+LevelManager.bombs,60,40);
if (isPaused) { if (isPaused) {
// cover background with translucent rectangle
g.setColor(new Color(255, 255, 255, 100)); g.setColor(new Color(255, 255, 255, 100));
g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
// draw pauseMenu rectangle and "Paused" text
pauseMenu.draw(g, Color.white, Color.black); pauseMenu.draw(g, Color.white, Color.black);
// draw instructions in pause menu
pauseMenuExitOne.draw(g, new Color(0,0, 0, 0), Color.gray); pauseMenuExitOne.draw(g, new Color(0,0, 0, 0), Color.gray);
pauseMenuExitTwo.draw(g, new Color(0,0, 0, 0), Color.gray); pauseMenuExitTwo.draw(g, new Color(0,0, 0, 0), Color.gray);
pauseMenuResume.draw(g, new Color(0,0, 0, 0), Color.gray); pauseMenuResume.draw(g, new Color(0,0, 0, 0), Color.gray);
} else if (isDialogue) { } else if (isDialogue) {
// cover background with translucent rectangle
g.setColor(new Color(255, 255, 255, 100)); g.setColor(new Color(255, 255, 255, 100));
g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
try { try {
if (waitForDialogue) { if (waitForDialogue) {
// draws complete dialogue sentence
dialogueMenu.currentFrame = dialogueArray.get(0).length(); dialogueMenu.currentFrame = dialogueArray.get(0).length();
dialogueMenu.frameCounter = 0; dialogueMenu.frameCounter = 0;
dialogueMenu.draw(g, dialogueArray.get(0), Color.white, Color.black); dialogueMenu.draw(g, dialogueArray.get(0), Color.white, Color.black);
} else if (dialogueMenu.draw(g, dialogueArray.get(0), Color.white, Color.black)) { } else if (dialogueMenu.draw(g, dialogueArray.get(0), Color.white, Color.black)) { // draws partial sentence
// if the partial sentence is the same length as the complete sentence
// indicate that the game should draw the complete sentence in the future
waitForDialogue = true; waitForDialogue = true;
} }
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
// if there is no more dialogue to draw, this means that all the dialogue has already been iterated over and removed
// therefore, stop drawing the partially translucent rectangle and resume the game
isDialogue = false; isDialogue = false;
// throw new RuntimeException(e);
} }
} }
if (!isLoaded) { if (!isLoaded) {
// if the game is not loaded, draw the loading screen
loadingMenu.draw(g, Color.white, Color.black); loadingMenu.draw(g, Color.white, Color.black);
} }
} }
@ -366,18 +378,23 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
//call the move methods in other classes to update positions //call the move methods in other classes to update positions
//this method is constantly called from run(). By doing this, movements appear fluid and natural. If we take this out the movements appear sluggish and laggy //this method is constantly called from run(). By doing this, movements appear fluid and natural. If we take this out the movements appear sluggish and laggy
public void move() throws IOException, UnsupportedAudioFileException, LineUnavailableException { public void move() throws IOException, UnsupportedAudioFileException, LineUnavailableException {
// move player character
player.move(); player.move();
// move all enemies
for (NonPlayer n: enemy) { for (NonPlayer n: enemy) {
n.move(); n.move();
} }
// move bombs flying through the air
for(int i=0; i<bombs.size(); i++){ for(int i=0; i<bombs.size(); i++){
bombs.get(i).move(); bombs.get(i).move();
} }
// move generated particles
for(int i=0; i<particles.size(); i++){ for(int i=0; i<particles.size(); i++){
if(particles.get(i)!=null) { if(particles.get(i)!=null) {
particles.get(i).move(); particles.get(i).move();
} }
} }
// move fireballs in flight
for(int i=0; i<fireballs.size(); i++){ for(int i=0; i<fireballs.size(); i++){
if(fireballs.get(i)!=null){ if(fireballs.get(i)!=null){
fireballs.get(i).move(); fireballs.get(i).move();
@ -387,36 +404,38 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
//handles all collision detection and responds accordingly //handles all collision detection and responds accordingly
public void checkCollision() { public void checkCollision() {
// assume player is not grounded until further checks in checkCollision() say that player is grounded
player.isGrounded = false; player.isGrounded = false;
// regular for loop used to prevent concurrent modification error
// shift maps in accordance
for(int i=0; i<map.length; i++){ for(int i=0; i<map.length; i++){
for(int j=0; j<map[0].length; j++){ for(int j=0; j<map[0].length; j++){
if(map[i][j]!=null) { if(map[i][j]!=null) {
// updates the realX position (which is affected by camera.x)
map[i][j].update(); map[i][j].update();
} }
} }
} }
// update enemy realX positions (which are affected by camera.x)
for (NonPlayer n: enemy) { for (NonPlayer n: enemy) {
n.update(); n.update();
n.isGrounded = false; n.isGrounded = false;
// kill player if player hits enemy realX
if(n.collidePlayer(player)&&!n.isDead){ if(n.collidePlayer(player)&&!n.isDead){
player.alive = false; player.alive = false;
} }
} }
//force player to remain on screen (For the most part) // prevent player from falling through the floor
// added to make level editing easier
if (player.y >= GAME_HEIGHT - Player.PLAYER_HEIGHT) { if (player.y >= GAME_HEIGHT - Player.PLAYER_HEIGHT) {
player.y = GAME_HEIGHT - Player.PLAYER_HEIGHT; player.y = GAME_HEIGHT - Player.PLAYER_HEIGHT;
player.yVelocity = 0; player.yVelocity = 0;
player.isGrounded = true; player.isGrounded = true;
} }
if (player.x <= 0) {
player.x = 0;
}
if (player.x + Player.PLAYER_WIDTH >= GAME_WIDTH) {
player.x = GAME_WIDTH - Player.PLAYER_WIDTH;
}
// prevent enemies from falling through the floor
// also added to help level editing
for (NonPlayer n: enemy) { for (NonPlayer n: enemy) {
if (n.y >= GAME_HEIGHT - n.npcHeight) { if (n.y >= GAME_HEIGHT - n.npcHeight) {
n.y = GAME_HEIGHT - n.npcHeight; n.y = GAME_HEIGHT - n.npcHeight;
@ -428,6 +447,8 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
//run() method is what makes the game continue running without end. It calls other methods to move objects, check for collision, and update the screen //run() method is what makes the game continue running without end. It calls other methods to move objects, check for collision, and update the screen
public void run() { public void run() {
// if the game was not serialized but rather started, initialize the map through LevelManager
// also enable dialogue
if (isNewStart) { if (isNewStart) {
LevelManager.setLevel(1); LevelManager.setLevel(1);
try { try {
@ -439,6 +460,7 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
} else { } else {
LevelManager.bombs = bombCount; LevelManager.bombs = bombCount;
} }
// let draw() know that the game is done loading so it can stop drawing the loading screen
isLoaded = true; isLoaded = true;
//the CPU runs our game code too quickly - we need to slow it down! The following lines of code "force" the computer to get stuck in a loop for short intervals between calling other methods to update the screen. //the CPU runs our game code too quickly - we need to slow it down! The following lines of code "force" the computer to get stuck in a loop for short intervals between calling other methods to update the screen.
long lastTime = System.nanoTime(); long lastTime = System.nanoTime();
@ -447,7 +469,7 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
double delta = 0; double delta = 0;
long now; long now;
int fireballCounter = 0; int fireballCounter = 0;
while(isRunning){ //this is the game loop, terminates on game restart to prevent race conditions while(isRunning){ //this is the game loop, terminates on game restart to prevent race conditions. Also reduces lag
now = System.nanoTime(); now = System.nanoTime();
delta = delta + (now-lastTime)/ns; delta = delta + (now-lastTime)/ns;
lastTime = now; lastTime = now;
@ -458,15 +480,14 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
// only perform game functions if game is not paused or in dialogue // only perform game functions if game is not paused or in dialogue
try { try {
move(); move();
} catch (IOException e) { } catch (IOException | UnsupportedAudioFileException | LineUnavailableException e) {
throw new RuntimeException(e);
} catch (UnsupportedAudioFileException e) {
throw new RuntimeException(e);
} catch (LineUnavailableException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
// check collisions
checkCollision(); checkCollision();
// update enemy positions
updateEnemy(); updateEnemy();
// shoot fireballs every 100 ticks
if(fireballCounter<0){fireballCounter = 100;} if(fireballCounter<0){fireballCounter = 100;}
fireballCounter--; fireballCounter--;
if(fireballCounter == 0){ if(fireballCounter == 0){
@ -478,7 +499,7 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if (playerFrameCounter > 5) { if (playerFrameCounter > 5) {
// increment sprite image to be used and keeps it below 12 // increment player sprite image to be used and keeps it below 12
playerFrame = (playerFrame + 1) % 11; playerFrame = (playerFrame + 1) % 11;
playerFrameCounter -= 5; playerFrameCounter -= 5;
} }
@ -499,11 +520,11 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
repaint(); repaint();
delta--; delta--;
} }
//System.out.println(Thread.currentThread());
} }
} }
// remove dead enemies that have fully faded
public void updateEnemy(){ public void updateEnemy(){
for(int i=0; i<enemy.size(); i++){ for(int i=0; i<enemy.size(); i++){
if(enemy.get(i).isDead){ if(enemy.get(i).isDead){
@ -515,23 +536,25 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
} }
} }
// shoot new fireball if enough time has passed
public void updateShootingBlock(){ public void updateShootingBlock(){
// iterate through every tile that shoots fireballs
for(Tile i: shootingTiles){ for(Tile i: shootingTiles){
if(i.shootingDir.equals("left")){ // shoot fireballs to the left if the shootingTile shoots to the left, etc
fireballs.add(new FireBall(i.x-20,i.y+Tile.length/2-4,-fireballSpeed,0,"left",8,16)); switch (i.shootingDir) {
} else if(i.shootingDir.equals("up")){ case "left" -> fireballs.add(new FireBall(i.x - 20, i.y + Tile.length / 2 - 4, -fireballSpeed, 0, "left", 8, 16));
fireballs.add(new FireBall(i.x+Tile.length/2,i.y-20,0,-fireballSpeed,"up",16,8)); case "up" -> fireballs.add(new FireBall(i.x + Tile.length / 2, i.y - 20, 0, -fireballSpeed, "up", 16, 8));
} else if(i.shootingDir.equals("right")){ case "right" -> fireballs.add(new FireBall(i.x + Tile.length + 20, i.y + Tile.length / 2 - 4, fireballSpeed, 0, "right", 8, 16));
fireballs.add(new FireBall(i.x+Tile.length+20,i.y+Tile.length/2-4,fireballSpeed,0,"right",8,16)); case "down" -> fireballs.add(new FireBall(i.x + Tile.length / 2, i.y + Tile.length + 10, 0, fireballSpeed, "down", 16, 8));
} else if(i.shootingDir.equals("down")){
fireballs.add(new FireBall(i.x+Tile.length/2,i.y+Tile.length+10,0,fireballSpeed,"down",16,8));
} }
} }
} }
// create new particles randomly if amount of particles in the particle array is less than 10
public void updateParticle() throws IOException { public void updateParticle() throws IOException {
if(particles.size()<10) { if(particles.size()<10) {
for (int i = 0; i < particleTiles.size(); i++) { for (int i = 0; i < particleTiles.size(); i++) {
if (UtilityFunction.randInt(1, 20) == 1) { if (UtilityFunction.randInt(1, 20) == 1) {
// create particle with the lava particle image
particles.add(new Particle(particleTiles.get(i).x + UtilityFunction.randInt(0, Tile.length), particleTiles.get(i).y + UtilityFunction.randInt(0, Tile.length / 2), particles.add(new Particle(particleTiles.get(i).x + UtilityFunction.randInt(0, Tile.length), particleTiles.get(i).y + UtilityFunction.randInt(0, Tile.length / 2),
UtilityFunction.randInt(-3, 3), UtilityFunction.randInt(-5, 2), UtilityFunction.randInt(5, 9), "img/particles/LavaParticle.png")); UtilityFunction.randInt(-3, 3), UtilityFunction.randInt(-5, 2), UtilityFunction.randInt(5, 9), "img/particles/LavaParticle.png"));
} }
@ -541,25 +564,36 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
//if a key is pressed, we'll send it over to the Player class for processing //if a key is pressed, we'll send it over to the Player class for processing
public void keyPressed(KeyEvent e){ public void keyPressed(KeyEvent e){
// intercept keys through the Middleware class if these are created through the settings
e = UtilityFunction.intercept(e, middlewareArray); e = UtilityFunction.intercept(e, middlewareArray);
// unpause the game if the game is paused, and vice versa
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
isPaused = !isPaused; isPaused = !isPaused;
System.out.println(isPaused);
} else if (e.getKeyCode() == KeyEvent.VK_ENTER) { } else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
// leave the game if the game was already paused
// explicitly ignores MouseEvents that were converted to KeyEvents
// this is to prevent unintentionally quitting the game
if (isPaused && (e.getWhen() != -1)) { if (isPaused && (e.getWhen() != -1)) {
((CardLayout)gameFrame.getLayout()).show(gameFrame, "menu"); ((CardLayout)gameFrame.getLayout()).show(gameFrame, "menu");
} else if (!waitForDialogue) { } else if (!waitForDialogue) { // fast-forward the dialogue animation if it is still playing
waitForDialogue = true; waitForDialogue = true;
} else { } else {
// play the next dialogue item
dialogueMenu.currentFrame = 0; dialogueMenu.currentFrame = 0;
dialogueMenu.frameCounter = 0; dialogueMenu.frameCounter = 0;
dialogueArray.remove(0); // try removing the current dialogue item
// the exception is ignored if it fails because waitForDialogue is still set to false
// so it does not matter
try {
dialogueArray.remove(0);
} catch (IndexOutOfBoundsException ignored) {}
waitForDialogue = false; waitForDialogue = false;
} }
} else { } else {
try { try {
// pass key event to player
player.keyPressed(e); player.keyPressed(e);
} catch (IOException ex) { } catch (IOException ex) { // TODO: why is IOException raised
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
} }
@ -567,9 +601,11 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
//if a key is released, we'll send it over to the Player class for processing //if a key is released, we'll send it over to the Player class for processing
public void keyReleased(KeyEvent e){ public void keyReleased(KeyEvent e){
// intercept key with new key defined in settings
e = UtilityFunction.intercept(e, middlewareArray); e = UtilityFunction.intercept(e, middlewareArray);
player.keyReleased(e); player.keyReleased(e);
if(e.getKeyChar() == 'p'){ // pressing the P key skips to the next level
if(e.getKeyCode() == KeyEvent.VK_P){
LevelManager.nextLevel(); LevelManager.nextLevel();
try { try {
player.resetNoSound(); player.resetNoSound();
@ -580,14 +616,14 @@ public class GamePanel extends JPanel implements Runnable, KeyListener, Serializ
} }
//left empty because we don't need it; must be here because it is required to be overridded by the KeyListener interface //left empty because we don't need it; must be here because it is required to be overridded by the KeyListener interface
public void keyTyped(KeyEvent e){ public void keyTyped(KeyEvent e){}
}
// read new image with getImage
public static BufferedImage getImage(String imageLocation) throws IOException { public static BufferedImage getImage(String imageLocation) throws IOException {
return ImageIO.read(new File(imageLocation)); return ImageIO.read(new File(imageLocation));
} }
// flip image horizontally
public static BufferedImage flipImageHorizontally(BufferedImage originalImage) { public static BufferedImage flipImageHorizontally(BufferedImage originalImage) {
BufferedImage flippedImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_INT_ARGB); BufferedImage flippedImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < originalImage.getWidth(); x++) { for (int x = 0; x < originalImage.getWidth(); x++) {