diff --git a/src/CameraPanel.java b/src/CameraPanel.java new file mode 100644 index 0000000..4fad7ad --- /dev/null +++ b/src/CameraPanel.java @@ -0,0 +1,8 @@ +import javax.swing.*; + +public class CameraPanel extends JPanel { + public Camera camera = new Camera(0); + + public CameraPanel() {} + +} diff --git a/src/GameFrame.java b/src/GameFrame.java index 79c414d..8fefc50 100644 --- a/src/GameFrame.java +++ b/src/GameFrame.java @@ -5,6 +5,7 @@ Runs the constructor in GamePanel class */ import java.awt.*; import java.io.IOException; +import java.util.Set; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; import javax.swing.*; @@ -13,15 +14,18 @@ public class GameFrame extends JFrame{ MenuPanel menu; GamePanel game; - JPanel main; + SettingPanel settings; + CameraPanel main; public GameFrame(){ try { - main = new JPanel(); + main = new CameraPanel(); main.setLayout(new CardLayout()); menu = new MenuPanel(main); game = new GamePanel(main); //run GamePanel constructor + settings = new SettingPanel(main); main.add(menu, "menu"); + main.add(settings, "settings"); main.add(game, "game"); } catch (IOException | SpriteException | UnsupportedAudioFileException | LineUnavailableException e) { // TODO: handle IO errors gracefully diff --git a/src/GamePanel.java b/src/GamePanel.java index 5224c5b..e6f7476 100644 --- a/src/GamePanel.java +++ b/src/GamePanel.java @@ -45,6 +45,7 @@ public class GamePanel extends JPanel implements Runnable, KeyListener{ //public static ArrayListmap = new ArrayList(); public static Tile[][]map = new Tile[300][18]; + public static ArrayList middlewareArray = new ArrayList(); public static ArrayListparticleTiles = new ArrayList(); public static ArrayListenemy = new ArrayList(); @@ -300,6 +301,13 @@ public class GamePanel extends JPanel implements Runnable, KeyListener{ //if a key is pressed, we'll send it over to the Player class for processing public void keyPressed(KeyEvent e){ + for (Middleware m: middlewareArray) { + if (m.canIntercept(e)) { + e = m.interceptKey(e); + System.out.println("intercept"); + break; + } + } if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { isPaused = !isPaused; } else { @@ -309,6 +317,12 @@ public class GamePanel extends JPanel implements Runnable, KeyListener{ //if a key is released, we'll send it over to the Player class for processing public void keyReleased(KeyEvent e){ + for (Middleware m: middlewareArray) { + if (m.canIntercept(e)) { + e = m.interceptKey(e); + break; + } + } player.keyReleased(e); if(e.getKeyChar() == 'p'){ LevelManager.nextLevel(); diff --git a/src/MenuPanel.java b/src/MenuPanel.java index e52d05b..fbd8375 100644 --- a/src/MenuPanel.java +++ b/src/MenuPanel.java @@ -23,9 +23,9 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ public static final int GAME_HEIGHT = 630; public final static int TOTAL_BOXES = 2; - public JPanel gameFrame; + public CameraPanel gameFrame; - public static Camera camera = new Camera(0); + public static Camera camera; public Thread gameThread; public Image image; public Graphics graphics; @@ -40,12 +40,13 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ // image imports begin here public BufferedImage backgroundImage = GamePanel.getImage("img/backgrounds/pointyMountains.png"); - public MenuPanel(JPanel gameFrame) throws IOException, SpriteException, UnsupportedAudioFileException, LineUnavailableException { + public MenuPanel(CameraPanel gameFrame) throws IOException, SpriteException, UnsupportedAudioFileException, LineUnavailableException { this.gameFrame = gameFrame; + camera = gameFrame.camera; title = new TextBox(100, 400, 100, GAME_WIDTH, standardFont, "Detroit", null); enter = new TextBox(300, 600, 100, GAME_WIDTH, standardFont, "Start Game", "game"); - settings = new TextBox(400, 600, 100, GAME_WIDTH, standardFont, "Settings", "menu"); + settings = new TextBox(400, 600, 100, GAME_WIDTH, standardFont, "Settings", "settings"); textBoxArray.add(enter); textBoxArray.add(settings); @@ -149,8 +150,6 @@ public class MenuPanel extends JPanel implements Runnable, KeyListener{ // logic for different screens starts here CardLayout cardLayout = (CardLayout) gameFrame.getLayout(); cardLayout.show(gameFrame, textBoxArray.get(currentBox).id); - } else if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_W) { - currentBox = (currentBox + 1) % textBoxArray.size(); } else if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_W) { // if currentBox > 0, subtract one // else, set to TOTAL_BOXES-1 diff --git a/src/Middleware.java b/src/Middleware.java new file mode 100644 index 0000000..23b0eaa --- /dev/null +++ b/src/Middleware.java @@ -0,0 +1,56 @@ +import com.sun.jdi.request.DuplicateRequestException; + +import java.awt.event.KeyEvent; +import java.util.ArrayList; + +public class Middleware { + + public static ArrayList allOldCode = new ArrayList(); + public static ArrayList allNewCode = new ArrayList(); + private final int oldCode; + private final int newCode; + public boolean isDestroyed = false; + + Middleware(int oldCode, int newCode) { + // if (!canCreate(oldCode, newCode)) { + // TODO: replace with more appropriate exception + // throw new DuplicateRequestException(); + // } + allOldCode.add(oldCode); + allNewCode.add(newCode); + this.oldCode = oldCode; + this.newCode = newCode; + } + + public boolean canIntercept(KeyEvent e) { + return e.getKeyCode() == newCode && !isDestroyed; + } + + public KeyEvent interceptKey(KeyEvent e) { + e.setKeyCode(oldCode); + return e; + } + + public void destroy() { + allOldCode.remove(oldCode); + allNewCode.remove(newCode); + isDestroyed = true; + } + + public static boolean canCreate(int oldCode, int newCode) { + return (!allOldCode.contains(oldCode) && !allNewCode.contains(newCode)); + } + + @Override + public boolean equals(Object o) { + try { + Middleware m = (Middleware)o; + // duck typing equals check + // if it has the same oldCode, assume it is the same; this is because oldCodes should be unique + // also makes some corner cases easier + return this.oldCode == m.oldCode; + } catch (ClassCastException e) { + return false; + } + } +} diff --git a/src/Player.java b/src/Player.java index 13050f6..9e13f47 100644 --- a/src/Player.java +++ b/src/Player.java @@ -42,37 +42,32 @@ public class Player extends GenericSprite { // moves paddle when key is pressed public void keyPressed(KeyEvent e) { - if(e.getKeyChar() == 'd'){ + if(e.getKeyCode() == KeyEvent.VK_D){ rightPressed = true; - } - if(e.getKeyChar() == 'a'){ + if(e.getKeyCode() == KeyEvent.VK_A){ leftPressed = true; - } - if(e.getKeyChar() == 'w'){ + if(e.getKeyCode() == KeyEvent.VK_W){ upPressed = true; - } - if(e.getKeyChar() == 's'){ + if(e.getKeyCode() == KeyEvent.VK_S){ downPressed = true; - } - } // stops moving paddle when key is released public void keyReleased(KeyEvent e) { - if(e.getKeyChar() == 'd'){ + if(e.getKeyCode() == KeyEvent.VK_D){ rightPressed = false; } - if(e.getKeyChar() == 'a'){ + if(e.getKeyCode() == KeyEvent.VK_A){ leftPressed = false; } - if(e.getKeyChar() == 'w'){ + if(e.getKeyCode() == KeyEvent.VK_W){ upPressed = false; } - if(e.getKeyChar() == 's'){ + if(e.getKeyCode() == KeyEvent.VK_S){ downPressed = false; } } diff --git a/src/SettingPanel.java b/src/SettingPanel.java new file mode 100644 index 0000000..511274a --- /dev/null +++ b/src/SettingPanel.java @@ -0,0 +1,205 @@ +/* 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 + +Implements KeyListener interface to listen for keyboard input + +Implements Runnable interface to use "threading" - let the game do two things at once + +*/ + +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.ArrayList; + +public class SettingPanel extends JPanel implements Runnable, KeyListener{ + + //dimensions of window + public static final int GAME_WIDTH = 1225; + public static final int GAME_HEIGHT = 630; + + public CameraPanel gameFrame; + + public static Camera camera; + public Thread gameThread; + public Image image; + public Graphics graphics; + public BackgroundImage background; + public TextBox title; + public TextBox up, down, left, right; + public ArrayList textBoxArray = new ArrayList(); + public Font standardFont = new Font(Font.MONOSPACED, Font.BOLD, 60); + public Font smallFont = new Font(Font.MONOSPACED, Font.PLAIN, 40); + public int playerFrame, enemyFrame; + public boolean waitForKey, notFirstWait; + public int lastKeyCode = -1; + public int currentBox = 0; + public PauseMenu pauseMenu; + + // image imports begin here + public BufferedImage backgroundImage = GamePanel.getImage("img/backgrounds/pointyMountains.png"); + + public SettingPanel(CameraPanel gameFrame) throws IOException, SpriteException, UnsupportedAudioFileException, LineUnavailableException { + this.gameFrame = gameFrame; + camera = gameFrame.camera; + + 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)); + down = new TextBox(350, 600, 50, GAME_WIDTH, smallFont, "Down", Integer.toString(KeyEvent.VK_A)); + left = new TextBox(400, 600, 50, GAME_WIDTH, smallFont, "Left", Integer.toString(KeyEvent.VK_S)); + right = new TextBox(450, 600, 50, GAME_WIDTH, smallFont, "Right", Integer.toString(KeyEvent.VK_D)); + textBoxArray.add(up); + textBoxArray.add(down); + textBoxArray.add(left); + textBoxArray.add(right); + + background = new BackgroundImage(0, 0, backgroundImage, GAME_WIDTH, GAME_HEIGHT, 10, camera); + pauseMenu = new PauseMenu(GAME_HEIGHT/2, 0, 400, 400, GAME_WIDTH, smallFont, "Enter your key"); + this.setFocusable(true); //make everything in this class appear on the screen + this.addKeyListener(this); //start listening for keyboard input + // request focus when the CardLayout selects this game + this.addComponentListener(new ComponentAdapter() { + + @Override + public void componentShown(ComponentEvent cEvt) { + Component src = (Component) cEvt.getSource(); + src.requestFocusInWindow(); + } + + }); + //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 + addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + if (hoverCheck(e)) { + keyPressed(new KeyEvent(new Component() { + }, 0, 0, 0, KeyEvent.VK_ENTER)); + } + } + }); + addMouseMotionListener(new MouseMotionAdapter() { + public void mouseMoved(MouseEvent e) { + hoverCheck(e); + } + }); + this.setPreferredSize(new Dimension(GAME_WIDTH, GAME_HEIGHT)); + + //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.start(); + } + + //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 + public void paint(Graphics g){ + //we are using "double buffering here" - if we draw images directly onto the screen, it takes time and the human eye can actually notice flashes of lag as each pixel on the screen is drawn one at a time. Instead, we are going to draw images OFF the screen, then simply move the image on screen as needed. + image = createImage(GAME_WIDTH, GAME_HEIGHT); //draw off screen + graphics = image.getGraphics(); + draw(graphics, playerFrame, enemyFrame);//update the positions of everything on the screen + g.drawImage(image, 0, 0, this); //move the image on the screen + + } + + //call the draw methods in each class to update positions as things move + public void draw(Graphics g, int playerFrame, int enemyFrame){ + background.draw(g); + title.draw(g,null, Color.black); + for (TextBox t: textBoxArray) { + t.draw(g, null, Color.cyan); + } + textBoxArray.get(currentBox).draw(g, Color.gray, Color.blue); + + if (waitForKey) { + g.setColor(new Color(255, 255, 255, 100)); + g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); + pauseMenu.draw(g, Color.white, Color.black); + } + } + + //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 + public void move(){ + } + + //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(){ + //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(); + double amountOfTicks = 60; + double ns = 1000000000/amountOfTicks; + double delta = 0; + long now; + + while(true){ //this is the infinite game loop + now = System.nanoTime(); + delta = delta + (now-lastTime)/ns; + lastTime = now; + + //only update screen if enough time has passed + if(delta >= 1){ + changeKeyBind(); + move(); + camera.x += 10; + repaint(); + delta--; + } + } + } + + public boolean hoverCheck(MouseEvent e) { + for (TextBox t: textBoxArray) { + if (t.isHover(e.getX(), e.getY())) { + currentBox = textBoxArray.indexOf(t); + return true; + } + } + return false; + } + + public void changeKeyBind() { + if (lastKeyCode != -1) { + System.out.println(lastKeyCode); + // newCode is -1 as it does not matter + GamePanel.middlewareArray.remove(new Middleware(Integer.parseInt(textBoxArray.get(currentBox).id), -1)); + GamePanel.middlewareArray.add(new Middleware(Integer.parseInt(textBoxArray.get(currentBox).id), lastKeyCode)); + System.out.println(GamePanel.middlewareArray.get(0)); + // lastKeyCode is set to -1 to prevent endless execution + lastKeyCode = -1; + } + } + + //if a key is pressed, we'll send it over to the Player class for processing + public void keyPressed(KeyEvent e) { + if (waitForKey) { + lastKeyCode = e.getKeyCode(); + waitForKey = false; + } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + ((CardLayout)gameFrame.getLayout()).show(gameFrame, "menu"); + } else if (e.getKeyCode() == KeyEvent.VK_ENTER) { + // logic for changing keys starts here + waitForKey = true; + } else if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_W) { + // if currentBox > 0, subtract one + // else, set to TOTAL_BOXES-1 + currentBox = currentBox > 0 ? currentBox - 1:textBoxArray.size() - 1; + } else if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_S) { + // if currentBox > total box amount - 1, set to 0 + // else, set to TOTAL_BOXES-1 + currentBox = currentBox < textBoxArray.size() - 1 ? currentBox + 1:0; + } + } + + //if a key is released, we'll send it over to the Player class for processing + public void keyReleased(KeyEvent e){ + + } + + //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){ + + } + +} \ No newline at end of file