From 235262919e0863215c2f58a4ab3b977f59442d05 Mon Sep 17 00:00:00 2001 From: Nao Pross Date: Tue, 20 Nov 2018 17:07:35 +0100 Subject: Add Game class, fix GameWindow's scene loading / management The new Game class contains the state of the game and the main logic to manage maps, actors and scores. --- src/subconscious/Game.java | 66 +++++++++++++++ src/subconscious/Subconscious.java | 4 +- src/subconscious/graphics/BattleScene.java | 5 +- src/subconscious/graphics/GameWindow.java | 125 ++++++++++++++++++++--------- src/subconscious/graphics/MapScene.java | 10 ++- src/subconscious/graphics/Scene.java | 80 +++++++++++++----- 6 files changed, 229 insertions(+), 61 deletions(-) create mode 100644 src/subconscious/Game.java (limited to 'src') diff --git a/src/subconscious/Game.java b/src/subconscious/Game.java new file mode 100644 index 0000000..dc7d109 --- /dev/null +++ b/src/subconscious/Game.java @@ -0,0 +1,66 @@ +package subconscious; + +import java.util.ArrayList; + + +/* Game + * Contains informations about the current state of the game used in an + * Obervable Object pattern. The Game Loop is managed in the graphics. + */ +public class Game { + public enum State { + MENU, PAUSE, LOADING, + // state in which the player is awake (real world) + REALITY, + // state in which the player is dreaming (imaginary world) + DREAM + } + + private State state; + private boolean stateChanged; + + private boolean running; + private boolean gameOver; + + private ArrayList actors; + private ArrayList maps; + + public Game() { + this.setState(State.MENU); + + this.running = true; + this.gameOver = false; + + this.actors = new ArrayList<>(); + this.maps = new ArrayList<>(); + } + + public void start() { + this.setState(State.DREAM); + } + + /* methods to manage the state */ + public void setState(State state) { + if (this.state == state) + return; + + this.stateChanged = true; + this.state = state; + } + + public State getState() { + return this.state; + } + + public void waitStateChange() { + while (!this.stateChanged); + this.stateChanged = false; + } + + /* accessors */ + public boolean isRunning() { return running; } + public void quit() { this.running = false; } + + public boolean isGameOver() { return gameOver; } + public void gameOver() { this.gameOver = true; } +} \ No newline at end of file diff --git a/src/subconscious/Subconscious.java b/src/subconscious/Subconscious.java index 384393a..88d9f70 100644 --- a/src/subconscious/Subconscious.java +++ b/src/subconscious/Subconscious.java @@ -4,6 +4,8 @@ import subconscious.graphics.GameWindow; public class Subconscious { public static void main(String[] args) { - GameWindow w = new GameWindow(); + // TODO: in the future this will be loaded from a save file + Game g = new Game(); + GameWindow w = new GameWindow(g); } } \ No newline at end of file diff --git a/src/subconscious/graphics/BattleScene.java b/src/subconscious/graphics/BattleScene.java index 6d41c2d..1b5e990 100644 --- a/src/subconscious/graphics/BattleScene.java +++ b/src/subconscious/graphics/BattleScene.java @@ -1,5 +1,6 @@ package subconscious.graphics; +import subconscious.Game; import subconscious.Actor; import subconscious.MapLoader; import subconscious.Tile; @@ -50,8 +51,8 @@ public class BattleScene extends MapScene { private int realX = 0; private int realY = 0; - public BattleScene() { - super(); + public BattleScene(Game g) { + super(g); // TODO: this should be handled in MapScene MapLoader mapLoader = new MapLoader("../testmap.json"); diff --git a/src/subconscious/graphics/GameWindow.java b/src/subconscious/graphics/GameWindow.java index f7459fd..ce99df5 100644 --- a/src/subconscious/graphics/GameWindow.java +++ b/src/subconscious/graphics/GameWindow.java @@ -1,5 +1,7 @@ package subconscious.graphics; +import subconscious.Game; + import java.awt.Dimension; import java.awt.GridLayout; import java.awt.CardLayout; @@ -12,6 +14,11 @@ import javax.swing.JButton; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; + +/* GameWindow + * This class manages the graphical part of the game, such as loading and + * unloading scenes, and the window itself + */ @SuppressWarnings("serial") public class GameWindow extends JFrame implements ActionListener { public static final Dimension WINDOW_SIZE = new Dimension(600, 400); @@ -24,26 +31,36 @@ public class GameWindow extends JFrame implements ActionListener { private Scene scene = null; private Thread sceneThread = null; + private Game game; + // TODO: remove map editor, start directly on Battle mode - public GameWindow() { + public GameWindow(Game g) { super("Subconscious"); + this.game = g; + + // set up JFrame this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(WINDOW_SIZE); this.setPreferredSize(WINDOW_SIZE); this.setLocationRelativeTo(null); - this.initComponents(); + // set up components + this.buildUi(); this.add(this.root, BorderLayout.CENTER); this.pack(); this.setVisible(true); + + // start Window Loop + this.loop(); } - private void initComponents() { + private void buildUi() { // set up a cardlayout this.root = new JPanel(new CardLayout()); + // TODO: menu should be a Scene // build Menu card JPanel menu = new JPanel(); menu.setLayout(new GridLayout(3, 1)); @@ -68,43 +85,82 @@ public class GameWindow extends JFrame implements ActionListener { this.root.add(menu, MENU_CARD); } - public void showScene(Scene scene) { - // the current scene is already open - if (scene == this.scene) { - ((CardLayout)this.root.getLayout()).show(this.root, SCENE_CARD); - scene.updateCanvasSize(); - scene.resume(); + // ovserver of this.game + private void loop() { + while (this.game.isRunning()) { + this.game.waitStateChange(); - return; - } + switch (this.game.getState()) { + case MENU: + this.loadMenu(); + break; - // if there is an old scene - if (this.scene != null) { - // close old scene - this.scene.stop(); - try { - this.sceneThread.join(); - } catch (InterruptedException ex) { - ex.printStackTrace(); + case DREAM: + this.loadScene(this.scene); + break; } + + } + } + + private void loadScene(Scene scene) { + // check to not reload the same scene + // TODO: this may not be necessary, ex reaload scene? + if (this.scene == scene) + return; + + if (this.scene != null){ + this.unloadScene(); } - // build new thread + // build thread this.scene = scene; this.sceneThread = new Thread(this.scene); + // for debugging + this.sceneThread.setName("Scene rendering Thread"); - // add to layout - this.root.add(scene, SCENE_CARD); - ((CardLayout)this.root.getLayout()).show(this.root, SCENE_CARD); + // add to UI + this.root.add(this.scene, SCENE_CARD); + ((CardLayout) this.root.getLayout()).show(this.root, SCENE_CARD); + // start scene this.sceneThread.start(); - scene.updateCanvasSize(); - scene.resume(); + this.scene.updateCanvasSize(this.getSize()); } - public void showMenu() { - this.scene.pause(); - ((CardLayout)this.root.getLayout()).show(this, MENU_CARD); + private void unloadScene() { + // close old scene + this.scene.stop(); + try { + // wait for thread to die + this.sceneThread.join(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + + // remove card from UI + this.root.remove(scene); + + this.scene = null; + this.sceneThread = null; + } + + // TODO: menu should become a Scene + private void loadMenu() { + if (this.scene != null) + this.scene.pause(); + + ((CardLayout) this.root.getLayout()).show(this.root, MENU_CARD); + } + + public void quit() { + this.setVisible(false); + + if (this.scene != null) { + this.unloadScene(); + } + + this.dispose(); } // Action Listener for menu @@ -112,22 +168,17 @@ public class GameWindow extends JFrame implements ActionListener { public void actionPerformed(ActionEvent e) { if (e.getActionCommand().startsWith("btn")) { if (e.getActionCommand().equals("btn-exit")) { - this.setVisible(false); - this.dispose(); + this.quit(); return; } - Scene scene = null; - if (e.getActionCommand().equals("btn-editor")) { - scene = new MapEditorScene(); + this.loadScene(new MapEditorScene()); } else if (e.getActionCommand().equals("btn-battle")) { - scene = new BattleScene(); + this.loadScene(new BattleScene(this.game)); + this.game.start(); } - if (scene != null) { - showScene(scene); - } } } } diff --git a/src/subconscious/graphics/MapScene.java b/src/subconscious/graphics/MapScene.java index 6978e1f..0fade19 100644 --- a/src/subconscious/graphics/MapScene.java +++ b/src/subconscious/graphics/MapScene.java @@ -1,5 +1,6 @@ package subconscious.graphics; +import subconscious.Game; import subconscious.Map; import subconscious.Tile; import subconscious.Actor; @@ -32,7 +33,9 @@ import java.io.FileReader; import java.io.BufferedReader; import java.io.IOException; - +/* MapScene + * Specialization of Scene to render a Map. + */ @SuppressWarnings("serial") public abstract class MapScene extends Scene implements ActionListener { protected Map map; @@ -54,6 +57,11 @@ public abstract class MapScene extends Scene implements ActionListener { protected AffineTransform tx = new AffineTransform(); + + public MapScene(Game g) { + super(g); + } + public MapScene() { super(); } diff --git a/src/subconscious/graphics/Scene.java b/src/subconscious/graphics/Scene.java index c49246b..0f678ef 100644 --- a/src/subconscious/graphics/Scene.java +++ b/src/subconscious/graphics/Scene.java @@ -1,5 +1,7 @@ package subconscious.graphics; +import subconscious.Game; + import java.awt.Canvas; import java.awt.Graphics2D; import java.awt.Dimension; @@ -20,6 +22,15 @@ import javax.swing.JPanel; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; + +/* Scene + * Abstract model class for all scenes of the game. Each scene is in a different + * thread and renders with a double buffered. As threads, scenes can be paused + * from rendering. + * + * It is also responsable for all inputs, which are transferred to the Game + * object which controls the internal state of the game. + */ @SuppressWarnings("serial") public abstract class Scene extends JPanel implements Runnable, KeyListener, MouseListener, MouseMotionListener, @@ -28,6 +39,8 @@ public abstract class Scene extends JPanel protected final long DESIRED_FPS = 60; protected final long DESIRED_DELTA_LOOP = (1000*1000*1000)/DESIRED_FPS; + protected Game game; + protected boolean running; protected boolean paused; protected Object pauseLock; @@ -40,9 +53,17 @@ public abstract class Scene extends JPanel protected int guiSize = 2; protected Scene() { + this.game = null; + + // thread control + this.running = false; + this.paused = false; + this.pauseLock = new Object(); + // watch for resize this.addComponentListener(this); + // set up canvas this.canvasSize = GameWindow.WINDOW_SIZE; this.canvas = new Canvas(); @@ -55,6 +76,13 @@ public abstract class Scene extends JPanel this.canvas.addMouseWheelListener(this); } + protected Scene(Game g) { + // call default contructor + this(); + + this.game = g; + } + public void run() { long beginLoopTime; long endLoopTime; @@ -69,7 +97,6 @@ public abstract class Scene extends JPanel this.running = true; this.paused = false; - pauseLock = new Object(); while (running) { beginLoopTime = System.nanoTime(); @@ -121,45 +148,58 @@ public abstract class Scene extends JPanel public synchronized void resume() { if (!paused) return; - + this.paused = false; - this.pauseLock.notifyAll(); + + synchronized (this.pauseLock) { + this.pauseLock.notifyAll(); + } } + // automagically set the canvas size to the parent's size + // WARNING: does not always work + // TODO: why doesn't this always work? public synchronized void updateCanvasSize() { this.canvasSize = this.getParent().getSize(); } - public synchronized void setCanvasSize(Dimension newSize) { + // this one always works + public synchronized void updateCanvasSize(Dimension newSize) { this.canvasSize = newSize; } + /* abstract methods */ protected abstract void render(); protected abstract void update(long deltaNanoTime); + /* key listener */ + @Override public void keyTyped(KeyEvent e) {} + @Override public void keyPressed(KeyEvent e) {} + @Override - public void keyTyped(KeyEvent e) {} - public void keyPressed(KeyEvent e) {} - public void keyReleased(KeyEvent e) {} - - @Override - public void mouseClicked(MouseEvent e) {} - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} - public void mousePressed(MouseEvent e) {} - public void mouseReleased(MouseEvent e) {} - public void mouseDragged(MouseEvent e) {} - public void mouseMoved(MouseEvent e) {} - public void mouseWheelMoved(MouseWheelEvent e) {} + public void keyReleased(KeyEvent e) { + // TODO: Escape pauses the game + } + /* mouse listener */ + @Override public void mouseClicked(MouseEvent e) {} + @Override public void mouseEntered(MouseEvent e) {} + @Override public void mouseExited(MouseEvent e) {} + @Override public void mousePressed(MouseEvent e) {} + @Override public void mouseReleased(MouseEvent e) {} + @Override public void mouseDragged(MouseEvent e) {} + @Override public void mouseMoved(MouseEvent e) {} + @Override public void mouseWheelMoved(MouseWheelEvent e) {} + + /* component listener */ @Override public void componentResized(ComponentEvent e) { this.updateCanvasSize(); } - public void componentMoved(ComponentEvent e) {} - public void componentShown(ComponentEvent e) {} - public void componentHidden(ComponentEvent e) {} + @Override public void componentMoved(ComponentEvent e) {} + @Override public void componentShown(ComponentEvent e) {} + @Override public void componentHidden(ComponentEvent e) {} } -- cgit v1.2.1