Add even more documentation, fix keybind bug

master
John 2022-06-20 17:19:49 -07:00
parent a94409dd12
commit f83b5c41c8
15 changed files with 95 additions and 63 deletions

View File

@ -1 +0,0 @@
Press P to skip to the next level.

View File

@ -32,7 +32,7 @@ public class GameFrame extends JFrame{
// this ensures that attempting to execute malicious code by tampering with saves will be more difficult, as payloads would have to stick to these classes // this ensures that attempting to execute malicious code by tampering with saves will be more difficult, as payloads would have to stick to these classes
// please note that it is not a perfect mitigation though // please note that it is not a perfect mitigation though
game = (GamePanel)FileManager.readObjectFromFile("local/game_state.dat", game = (GamePanel)FileManager.readObjectFromFile("local/game_state.dat",
Arrays.asList("GamePanel", "javax.swing.JPanel", "javax.swing.JComponent", "java.awt.Container", Arrays.asList("FireBall", "GamePanel", "javax.swing.JPanel", "javax.swing.JComponent", "java.awt.Container",
"java.awt.Component", "javax.swing.plaf.ColorUIResource", "java.awt.Color", "java.awt.Component", "javax.swing.plaf.ColorUIResource", "java.awt.Color",
"javax.swing.plaf.FontUIResource", "java.awt.Font", "java.util.Locale", "java.awt.Dimension", "javax.swing.plaf.FontUIResource", "java.awt.Font", "java.util.Locale", "java.awt.Dimension",
"java.awt.ComponentOrientation", "[Ljava.awt.Component;", "java.awt.FlowLayout", "java.awt.ComponentOrientation", "[Ljava.awt.Component;", "java.awt.FlowLayout",

View File

@ -166,7 +166,7 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{
return false; return false;
} }
//if a key is pressed, we'll send it over to the Player class for processing //if a key is pressed, we'll process it
public void keyPressed(KeyEvent e) { public void keyPressed(KeyEvent e) {
// intercept keypresses and replace them with previously defined keypresses through the Middleware class // intercept keypresses and replace them with previously defined keypresses through the Middleware class
e = UtilityFunction.intercept(e, GameFrame.game.middlewareArray); e = UtilityFunction.intercept(e, GameFrame.game.middlewareArray);
@ -224,7 +224,7 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{
} }
} }
} }
//if a key is released, we'll send it over to the Player class for processing // left empty
public void keyReleased(KeyEvent e){ public void keyReleased(KeyEvent e){
} }

View File

@ -1,3 +1,6 @@
// Eric Li, Charlie Zhao, ICS4U, Finished 6/17/2022
// Render the pause menu and offset text
import java.awt.*; import java.awt.*;
import java.io.Serializable; import java.io.Serializable;
@ -5,21 +8,27 @@ public class PauseMenu extends TextBox implements Serializable {
public boolean hasBorder; public boolean hasBorder;
public PauseMenu(int y, int textYOffset, int xWidth, int yHeight, int totalWidth, Font font, String text, boolean hasBorder) { public PauseMenu(int y, int textYOffset, int xWidth, int yHeight, int totalWidth, Font font, String text, boolean hasBorder) {
super(y, xWidth, yHeight, totalWidth, font, text, null); super(y, xWidth, yHeight, totalWidth, font, text, null);
// shift y up by the offset given
this.y -= textYOffset; this.y -= textYOffset;
this.hasBorder = hasBorder; this.hasBorder = hasBorder;
} }
public void drawCenteredTextBox(Graphics g, String text, Color backgroundColor, Color textColor) { public void drawCenteredTextBox(Graphics g, String text, Color backgroundColor, Color textColor) {
// draw background if backgroundColor is not null
// please note that later uses of this class (and TextBox) pass a fully transparent color to make debugging easier
if (backgroundColor != null) { if (backgroundColor != null) {
// set color of border
g.setColor(textColor); g.setColor(textColor);
// TODO: make drawn line widths consistent
if (hasBorder) { if (hasBorder) {
((Graphics2D) g).setStroke(new BasicStroke(4f)); ((Graphics2D) g).setStroke(new BasicStroke(4f));
} }
// draw border
g.drawRect(newX, newY, xWidth, yHeight); g.drawRect(newX, newY, xWidth, yHeight);
// set color of rectangle and draw rectangle in border
g.setColor(backgroundColor); g.setColor(backgroundColor);
g.fillRect(newX, newY, xWidth, yHeight); g.fillRect(newX, newY, xWidth, yHeight);
} }
// set text color and draw centered string
g.setColor(textColor); g.setColor(textColor);
drawCenteredString(g, y, newX, xWidth, text); drawCenteredString(g, y, newX, xWidth, text);
} }

View File

@ -294,7 +294,7 @@ public class Player extends GenericSprite {
} }
public void reset() throws UnsupportedAudioFileException, LineUnavailableException, IOException { public void reset() throws UnsupportedAudioFileException, LineUnavailableException, IOException {
SoundWrapper.playSound("sound/OOF.wav"); UtilityFunction.playSound("sound/OOF.wav");
holdingSteel = false; holdingSteel = false;
LevelManager.setLevel(GameFrame.game.level, true); LevelManager.setLevel(GameFrame.game.level, true);
GameFrame.game.camera.x = LevelManager.xSpawn; GameFrame.game.camera.x = LevelManager.xSpawn;

View File

@ -1,3 +1,6 @@
// Eric Li, Charlie Zhao, ICS4U, Finished 6/15/2022
// Ensures that no arbitrary classes are loaded from the save files
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;

View File

@ -1,6 +1,7 @@
/* GameFrame.game.class acts as the main "game loop" - continuously runs the game and calls whatever needs to be called // Eric Li, Charlie Zhao, ICS4U, Finished 6/20/2022
/* SettingPanel acts as the JPanel that controls the settings; it mostly creates new Middleware and adds that Middleware to GamePanel
Child of JPanel because JPanel contains methods for drawing to the screen Child of MenuPanel because it shares most of the characteristics, and child of JPanel because JPanel contains methods for drawing to the screen
Implements KeyListener interface to listen for keyboard input Implements KeyListener interface to listen for keyboard input
@ -24,30 +25,30 @@ public class SettingPanel extends MenuPanel {
public TextBox title; public TextBox title;
public TextBox up, down, left, right, tip; public TextBox up, down, left, right, tip;
public ArrayList<TextBox> textBoxArray = new ArrayList<TextBox>(); public ArrayList<TextBox> textBoxArray = new ArrayList<>();
public Font standardFont = new Font(Font.MONOSPACED, Font.BOLD, 60); public Font standardFont = new Font(Font.MONOSPACED, Font.BOLD, 60);
public Font smallFont = new Font(Font.MONOSPACED, Font.PLAIN, 40); public Font smallFont = new Font(Font.MONOSPACED, Font.PLAIN, 40);
public Font smallerFont = new Font(Font.MONOSPACED, Font.ITALIC, 24); public Font smallerFont = new Font(Font.MONOSPACED, Font.ITALIC + Font.BOLD, 24);
public int playerFrame, enemyFrame; public boolean waitForKey;
public boolean waitForKey, notFirstWait;
public int lastKeyCode = -1; public int lastKeyCode = -1;
public int currentBox = 0; public int currentBox = 0;
public PauseMenu pauseMenu; public PauseMenu pauseMenu;
public SettingPanel(CameraPanel gameFrame) throws IOException, SpriteException, UnsupportedAudioFileException, LineUnavailableException { public SettingPanel(CameraPanel gameFrame) throws IOException, SpriteException, UnsupportedAudioFileException, LineUnavailableException {
super(gameFrame); super(gameFrame);
// initialize new textboxes and add the textboxes to the selectable textbox array
title = new TextBox(100, 400, 100, GAME_WIDTH, standardFont, "Settings", null); title = new TextBox(100, 400, 100, GAME_WIDTH, standardFont, "Settings", null);
up = new TextBox(300, 600, 50, GAME_WIDTH, smallFont, "Up", Integer.toString(KeyEvent.VK_W)); up = new TextBox(300, 600, 50, GAME_WIDTH, smallFont, "Up", Integer.toString(KeyEvent.VK_W));
down = new TextBox(350, 600, 50, GAME_WIDTH, smallFont, "Down", Integer.toString(KeyEvent.VK_S)); down = new TextBox(400, 600, 50, GAME_WIDTH, smallFont, "Down", Integer.toString(KeyEvent.VK_S));
left = new TextBox(400, 600, 50, GAME_WIDTH, smallFont, "Left", Integer.toString(KeyEvent.VK_A)); left = new TextBox(350, 600, 50, GAME_WIDTH, smallFont, "Left", Integer.toString(KeyEvent.VK_A));
right = new TextBox(450, 600, 50, GAME_WIDTH, smallFont, "Right", Integer.toString(KeyEvent.VK_D)); right = new TextBox(450, 600, 50, GAME_WIDTH, smallFont, "Right", Integer.toString(KeyEvent.VK_D));
tip = new TextBox(500, 600, 50, GAME_WIDTH, smallerFont, "TIP: Press ESC to return to the main menu", null); tip = new TextBox(500, 600, 50, GAME_WIDTH, smallerFont, "TIP: Press ESC to return to the main menu", null);
textBoxArray.add(up); textBoxArray.add(up);
textBoxArray.add(left); textBoxArray.add(left);
textBoxArray.add(down); textBoxArray.add(down);
textBoxArray.add(right); textBoxArray.add(right);
// initialize new menu
// this menu is displayed when the player is rebinding a key
pauseMenu = new PauseMenu(GAME_HEIGHT/2, 0, 400, 400, GAME_WIDTH, smallFont, "Enter your key", true); pauseMenu = new PauseMenu(GAME_HEIGHT/2, 0, 400, 400, GAME_WIDTH, smallFont, "Enter your key", true);
} }
@ -83,14 +84,16 @@ public class SettingPanel extends MenuPanel {
} }
} }
// move is repurposed to change key bind // doAction is used to change key bind
public void doAction() { public void doAction() {
changeKeyBind(); changeKeyBind();
} }
// check if mouse is hovering over textbox
public boolean hoverCheck(MouseEvent e) { public boolean hoverCheck(MouseEvent e) {
for (TextBox t: textBoxArray) { for (TextBox t: textBoxArray) {
if (t.isHover(e.getX(), e.getY())) { if (t.isHover(e.getX(), e.getY())) {
// set currentBox to the one the mouse is hovering over
currentBox = textBoxArray.indexOf(t); currentBox = textBoxArray.indexOf(t);
return true; return true;
} }
@ -98,7 +101,9 @@ public class SettingPanel extends MenuPanel {
return false; return false;
} }
// change the keybind for a specific control (e.x., rebinding jump from W to another key)
public void changeKeyBind() { public void changeKeyBind() {
// the player can press escape to cancel rebinding keys
if (lastKeyCode == KeyEvent.VK_ESCAPE) { if (lastKeyCode == KeyEvent.VK_ESCAPE) {
lastKeyCode = -1; lastKeyCode = -1;
} else if (lastKeyCode != -1) { } else if (lastKeyCode != -1) {
@ -113,32 +118,27 @@ public class SettingPanel extends MenuPanel {
GameFrame.game.middlewareArray.add(new Middleware(-1, Integer.parseInt(textBoxArray.get(currentBox).id))); GameFrame.game.middlewareArray.add(new Middleware(-1, Integer.parseInt(textBoxArray.get(currentBox).id)));
// lastKeyCode is set to -1 to prevent endless execution // lastKeyCode is set to -1 to prevent endless execution
lastKeyCode = -1; lastKeyCode = -1;
// save new key bind to file
try {
FileManager.writeObjectToFile("local/controls", GameFrame.game.middlewareArray);
} catch (IOException e) {
e.printStackTrace();
}
} }
} }
//if a key is pressed, we'll send it over to the Player class for processing //if a key is pressed, we'll process it
public void keyPressed(KeyEvent e) { public void keyPressed(KeyEvent e) {
// if the key is not being rebound right now, intercept it
if (!waitForKey) { if (!waitForKey) {
e = UtilityFunction.intercept(e, GameFrame.game.middlewareArray); e = UtilityFunction.intercept(e, GameFrame.game.middlewareArray);
} }
// if the key is being rebound, process the rebinding
if (waitForKey) { if (waitForKey) {
if (e.getKeyCode() != KeyEvent.VK_ENTER) { if (e.getKeyCode() != KeyEvent.VK_ENTER) {
lastKeyCode = e.getKeyCode(); lastKeyCode = e.getKeyCode();
waitForKey = false; waitForKey = false;
} }
} else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { // if the player presses ESC, return to the main menu
((CardLayout)gameFrame.getLayout()).show(gameFrame, "menu"); ((CardLayout)gameFrame.getLayout()).show(gameFrame, "menu");
} else if (e.getKeyCode() == KeyEvent.VK_ENTER) { } else if (e.getKeyCode() == KeyEvent.VK_ENTER) { // if the player presses ENTER, rebind the key that is the id of the currently selected textbox
// logic for changing keys starts here // logic for changing keys starts here
waitForKey = true; waitForKey = true;
} else { } else { // otherwise, move the currently selected textbox up or down
currentBox = UtilityFunction.processBox(e, currentBox, textBoxArray); currentBox = UtilityFunction.processBox(e, currentBox, textBoxArray);
} }
} }

View File

@ -1,20 +1,30 @@
// Eric Li, Charlie Zhao, ICS4U, Finished 6/20/2022
// Utility class to make interacting with sound files easier
import javax.sound.sampled.*; import javax.sound.sampled.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
public class Sound implements Serializable { public class Sound implements Serializable {
private AudioInputStream audioInputStream; public AudioInputStream audioInputStream;
private Clip clip; public Clip clip;
private File file; public File file;
public Sound(String filePath) throws UnsupportedAudioFileException, IOException, LineUnavailableException { public Sound(String filePath) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
file = new File(filePath); file = new File(filePath);
audioInputStream = AudioSystem.getAudioInputStream(file); audioInputStream = AudioSystem.getAudioInputStream(file);
clip = AudioSystem.getClip(); clip = AudioSystem.getClip();
clip.open(audioInputStream); clip.open(audioInputStream);
} }
// start playing sound
public void start(){ public void start(){
clip.setFramePosition(0); clip.setFramePosition(0);
clip.start(); clip.start();
} }
// while close() is not used because the same sounds are either constantly used or immediately dereferenced (and therefore collected by the GC)
// it is still good practice to enable closing sounds after finishing using them
public void close() {
clip.close();
}
} }

View File

@ -1,3 +1,7 @@
// Eric Li, Charlie Zhao, ICS4U, Finished 6/17/2022
// A wrapper that makes the Sound class serializable
// Please note that this is currently superseded by UtilityFunction.playSound, but is included for extensibility purposes
import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException; import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.*; import java.io.*;
@ -6,20 +10,6 @@ public class SoundWrapper implements Serializable {
transient public Sound sound; transient public Sound sound;
public String soundString; public String soundString;
public static Sound grass;
static {
try {
grass = new Sound("sound/grass.wav");
} catch (UnsupportedAudioFileException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (LineUnavailableException e) {
throw new RuntimeException(e);
}
}
// please note that not as many constructors were implemented as BufferedImage, as this class was created before most sounds were added; // please note that not as many constructors were implemented as BufferedImage, as this class was created before most sounds were added;
// as such, backwards compatibility was not needed // as such, backwards compatibility was not needed
public SoundWrapper(String soundLocation) throws UnsupportedAudioFileException, LineUnavailableException, IOException { public SoundWrapper(String soundLocation) throws UnsupportedAudioFileException, LineUnavailableException, IOException {
@ -31,25 +21,15 @@ public class SoundWrapper implements Serializable {
@Serial @Serial
private void writeObject(ObjectOutputStream out) throws IOException { private void writeObject(ObjectOutputStream out) throws IOException {
// write location of .wav file (soundString)
out.writeObject(soundString); out.writeObject(soundString);
} }
@Serial @Serial
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException, UnsupportedAudioFileException, LineUnavailableException { private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException, UnsupportedAudioFileException, LineUnavailableException {
// read .wav file located at soundString
Object o; Object o;
o = in.readObject(); o = in.readObject();
sound = new Sound((String)o); sound = new Sound((String)o);
} }
public static void playSound(String filePath) throws UnsupportedAudioFileException, LineUnavailableException, IOException {
Sound sound = new Sound(filePath);
if (sound == null) {
try {
sound = new Sound(filePath);
} catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) {
throw new RuntimeException(e);
}
}
sound.start();
}
} }

View File

@ -1,3 +1,6 @@
// Eric Li, Charlie Zhao, ICS4U, Finished 5/30/2022
// Exception that occurs when the sprite is not of the right dimensions
public class SpriteException extends Exception { public class SpriteException extends Exception {
public SpriteException() { public SpriteException() {
super("Tile sprites must have equal lengths and heights."); super("Tile sprites must have equal lengths and heights.");

View File

@ -54,7 +54,7 @@ public class StickyBomb extends GenericSprite implements Serializable {
// } // }
// } // }
// explode.start(); // explode.start();
SoundWrapper.playSound("sound/explode.wav"); UtilityFunction.playSound("sound/explode.wav");
double yDis = GameFrame.game.player.y+Player.PLAYER_HEIGHT/2-(y+(double)length/2); double yDis = GameFrame.game.player.y+Player.PLAYER_HEIGHT/2-(y+(double)length/2);
double xDis = GameFrame.game.player.x+Player.PLAYER_WIDTH/2-(realX+(double)length/2); double xDis = GameFrame.game.player.x+Player.PLAYER_WIDTH/2-(realX+(double)length/2);
double hypo = Math.sqrt(yDis*yDis+xDis*xDis); double hypo = Math.sqrt(yDis*yDis+xDis*xDis);

View File

@ -1,3 +1,7 @@
// Eric Li, Charlie Zhao, ICS4U, Finished 6/17/2022
// Creates a centered textbox with centered text
// The textbox can also have a filled background
import java.awt.*; import java.awt.*;
import java.io.Serializable; import java.io.Serializable;
@ -20,7 +24,7 @@ public class TextBox extends Rectangle implements Serializable {
this.id = id; this.id = id;
} }
// isHover is not needed for now and is also probably implemented badly // checks if a mouse is hovering over the textbox
public boolean isHover(int x, int y) { public boolean isHover(int x, int y) {
if (x >= newX && x <= newX + xWidth) { if (x >= newX && x <= newX + xWidth) {
return (y >= newY && y <= newY + yHeight); return (y >= newY && y <= newY + yHeight);
@ -28,15 +32,19 @@ public class TextBox extends Rectangle implements Serializable {
return false; return false;
} }
// draws the centered textbox
public void drawCenteredTextBox(Graphics g, String text, Color backgroundColor, Color textColor) { public void drawCenteredTextBox(Graphics g, String text, Color backgroundColor, Color textColor) {
// if the backgroundColor is not null, draw a rectangle filled with that color
if (backgroundColor != null) { if (backgroundColor != null) {
g.setColor(backgroundColor); g.setColor(backgroundColor);
g.fillRect(newX, newY, xWidth, yHeight); g.fillRect(newX, newY, xWidth, yHeight);
} }
// set text color and draw string
g.setColor(textColor); g.setColor(textColor);
drawCenteredString(g, y, newX, xWidth, text); drawCenteredString(g, y, newX, xWidth, text);
} }
// draws the centered string
public static void drawCenteredString(Graphics g, int y, int x, int xWidth, String text) { public static void drawCenteredString(Graphics g, int y, int x, int xWidth, String text) {
int newX, newY; int newX, newY;
// get font size // get font size
@ -49,7 +57,6 @@ public class TextBox extends Rectangle implements Serializable {
g.drawString(text, newX, newY); g.drawString(text, newX, newY);
} }
// TODO: make this good
public void draw(Graphics g, Color backgroundColor, Color textColor) { public void draw(Graphics g, Color backgroundColor, Color textColor) {
g.setFont(font); g.setFont(font);
drawCenteredTextBox(g, text, backgroundColor, textColor); drawCenteredTextBox(g, text, backgroundColor, textColor);

View File

@ -1,12 +1,21 @@
// Eric Li, Charlie Zhao, ICS4U, Finished 6/20/2022
// Utility functions to help with common tasks
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
// class is final because no objects will be made from it
public final class UtilityFunction { public final class UtilityFunction {
private UtilityFunction(){}
// intercept keystroke
public static KeyEvent intercept(KeyEvent e, ArrayList<Middleware> middlewareArray) { public static KeyEvent intercept(KeyEvent e, ArrayList<Middleware> middlewareArray) {
// iterate through each Middleware object in middlewareArray
for (Middleware m: middlewareArray) { for (Middleware m: middlewareArray) {
if (m.canIntercept(e)) { if (m.canIntercept(e)) {
// call interceptKey to intercept the key
e = m.interceptKey(e); e = m.interceptKey(e);
return e; return e;
} }
@ -14,6 +23,7 @@ public final class UtilityFunction {
return e; return e;
} }
// process VK_UP or VK_DOWN inputs to change the currently selected box
public static int processBox(KeyEvent e, int currentBox, ArrayList<TextBox> textBoxArray) { public static int processBox(KeyEvent e, int currentBox, ArrayList<TextBox> textBoxArray) {
if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_W) { if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_W) {
// if currentBox > 0, subtract one // if currentBox > 0, subtract one
@ -27,7 +37,14 @@ public final class UtilityFunction {
return currentBox; return currentBox;
} }
// randomly generate an integer from low to high
public static int randInt(int low, int high){ public static int randInt(int low, int high){
return (int)(Math.random()*(high-low+1))+low; return (int)(Math.random()*(high-low+1))+low;
} }
// start playing a sound that is located at filePath
public static void playSound(String filePath) throws UnsupportedAudioFileException, LineUnavailableException, IOException {
Sound sound = new Sound(filePath);
sound.start();
}
} }

View File

@ -1,3 +1,6 @@
// Eric Li, Charlie Zhao, ICS4U, Finished 6/17/2022
// Draws signs on the walls of levels
import java.awt.*; import java.awt.*;
import java.io.Serializable; import java.io.Serializable;
@ -8,8 +11,6 @@ public class WallSign extends TextBox implements Serializable {
this.newX = x; this.newX = x;
this.y = y; this.y = y;
} }
// TODO: flip
public void draw(Graphics g, Color textColor) { public void draw(Graphics g, Color textColor) {
int oldX = this.newX; int oldX = this.newX;
newX -= GameFrame.game.camera.x; newX -= GameFrame.game.camera.x;

3
tips and tricks.txt Normal file
View File

@ -0,0 +1,3 @@
Press P to skip to the next level.
Delete local/game_state if you bound the wrong controls and are not having a good time.
Use bombs sparingly.