diff options
-rw-r--r-- | src/subconscious/Game.java | 33 | ||||
-rw-r--r-- | src/subconscious/graphics/BattleScene.java | 4 | ||||
-rw-r--r-- | src/subconscious/graphics/GameWindow.java | 147 | ||||
-rw-r--r-- | src/subconscious/graphics/MapEditorScene.java | 2 | ||||
-rw-r--r-- | src/subconscious/graphics/MapScene.java | 16 | ||||
-rw-r--r-- | src/subconscious/graphics/MenuScene.java | 70 | ||||
-rw-r--r-- | src/subconscious/graphics/Palette.java | 1 | ||||
-rw-r--r-- | src/subconscious/graphics/PauseScene.java | 42 | ||||
-rw-r--r-- | src/subconscious/graphics/Scene.java | 97 |
9 files changed, 275 insertions, 137 deletions
diff --git a/src/subconscious/Game.java b/src/subconscious/Game.java index 8e51a32..a6d42be 100644 --- a/src/subconscious/Game.java +++ b/src/subconscious/Game.java @@ -14,15 +14,17 @@ public class Game { public enum State { // main menu MENU, - // state in which the player is awake (real world) - REALITY, - // state in which the player is dreaming (imaginary world) - DREAM, + // where actors can move freely + WORLD, + // where actors are constrained in the tiles + BATTLE, // intermediate states PAUSE, CLOSING, } private State state = State.MENU; + private State lastState; + private final Lock stateLock = new ReentrantLock(); private Condition stateChanged; @@ -49,7 +51,8 @@ public class Game { } public void start() { - this.setState(State.DREAM); + // TODO: change + this.setState(State.BATTLE); } public void update() { @@ -82,6 +85,26 @@ public class Game { return this.state; } + public void togglePause() { + if (this.isPaused()) + this.resume(); + else + this.pause(); + } + + public void pause() { + this.lastState = this.state; + this.setState(State.PAUSE); + } + + public void resume() { + this.setState(lastState); + } + + public boolean isPaused() { + return this.state == State.PAUSE; + } + /* methods to manage map and map loading */ public Map getMap() { return currentMap; diff --git a/src/subconscious/graphics/BattleScene.java b/src/subconscious/graphics/BattleScene.java index 0252dec..6cc38c9 100644 --- a/src/subconscious/graphics/BattleScene.java +++ b/src/subconscious/graphics/BattleScene.java @@ -56,8 +56,8 @@ public class BattleScene extends MapScene implements ActionListener { private int realX = 0; private int realY = 0; - public BattleScene(Game g) { - super(g); + public BattleScene(Game g, String uniqueName) { + super(g, uniqueName); // 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 b0f48fd..da69d40 100644 --- a/src/subconscious/graphics/GameWindow.java +++ b/src/subconscious/graphics/GameWindow.java @@ -2,8 +2,10 @@ package subconscious.graphics; import subconscious.Game; +import java.util.Stack; +import java.util.AbstractMap.SimpleEntry; + import java.awt.Dimension; -import java.awt.GridLayout; import java.awt.CardLayout; import java.awt.BorderLayout; @@ -11,24 +13,24 @@ import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JButton; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.event.WindowListener; +import java.awt.event.WindowEvent; /* 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 class GameWindow extends JFrame implements WindowListener { public static final Dimension WINDOW_SIZE = new Dimension(600, 400); - public final String MENU_CARD = "menu"; - public final String SCENE_CARD = "scene"; - private JPanel root; + // reference to the current scene which is NOT on the stack private Scene scene = null; private Thread sceneThread = null; + // stack of loaded scenes in the background that are paused + private Stack<SimpleEntry<Scene, Thread>> loadedScenes = new Stack<>(); private Game game; @@ -44,8 +46,11 @@ public class GameWindow extends JFrame implements ActionListener { this.setPreferredSize(WINDOW_SIZE); this.setLocationRelativeTo(null); + this.addWindowListener(this); + // set up components - this.buildUi(); + // set up a cardlayout + this.root = new JPanel(new CardLayout()); this.add(this.root, BorderLayout.CENTER); this.pack(); @@ -55,47 +60,33 @@ public class GameWindow extends JFrame implements ActionListener { this.loop(); } - 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)); - - JButton editor = new JButton("Editor"); - editor.setActionCommand("btn-editor"); - editor.addActionListener(this); - - JButton battle = new JButton("Battle"); - battle.setActionCommand("btn-battle"); - battle.addActionListener(this); - - JButton exit = new JButton("Exit"); - exit.setActionCommand("btn-exit"); - exit.addActionListener(this); - - menu.add(editor); - menu.add(battle); - menu.add(exit); - - // add cards - this.root.add(menu, MENU_CARD); - } - // ovserver of this.game private void loop() { + // load the first scene + this.loadScene(new MenuScene(this.game, "mainmenu")); + while (this.game.isRunning()) { Game.State newState = this.game.waitStateChange(); switch (newState) { case MENU: - this.loadMenu(); + // unload all scenes to get the first one => menu + this.unloadAllScenes(); break; - case DREAM: - this.loadScene(this.scene); + // TODO: rewrite the following part + case BATTLE: + this.loadScene(new BattleScene(this.game, "battle1")); + break; + + case PAUSE: + this.loadScene(new PauseScene(this.game, "pause")); + try { + this.sceneThread.join(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + this.unloadScene(); break; } @@ -106,29 +97,40 @@ public class GameWindow extends JFrame implements ActionListener { 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 a scene is loaded push it onto the loadedScenes stack if (this.scene != null){ - this.unloadScene(); + this.scene.pauseThread(); + this.loadedScenes.push(new SimpleEntry<Scene, Thread>(scene, sceneThread)); } // build thread this.scene = scene; // add to UI - this.root.add(this.scene, SCENE_CARD); - ((CardLayout) this.root.getLayout()).show(this.root, SCENE_CARD); + this.root.add(this.scene, this.scene.UNIQUE_NAME); + ((CardLayout) this.root.getLayout()).show(this.root, this.scene.UNIQUE_NAME); // start scene this.sceneThread = new Thread(this.scene); // for debugging - this.sceneThread.setName("Scene rendering Thread"); + this.sceneThread.setName("Scene [" + scene.UNIQUE_NAME + "]"); this.sceneThread.start(); this.scene.updateCanvasSize(this.getSize()); } + private void unloadAllScenes() { + unloadScenes(this.loadedScenes.size() -1); + } + + private void unloadScenes(int count) { + for (int i = 0; i < count; i++) { + this.unloadScene(); + } + } + private void unloadScene() { // close old scene this.scene.stop(); @@ -142,44 +144,41 @@ public class GameWindow extends JFrame implements ActionListener { // remove card from UI this.root.remove(scene); - this.scene = null; - this.sceneThread = null; - } + // TODO: this should probably be an error + if (this.loadedScenes.empty()) { + this.scene = null; + this.sceneThread = null; + return; + } - // TODO: menu should become a Scene - private void loadMenu() { - if (this.scene != null) - this.scene.pause(); + // load the last scene + SimpleEntry<Scene, Thread> sceneEntry = this.loadedScenes.pop(); + this.scene = sceneEntry.getKey(); + this.sceneThread = sceneEntry.getValue(); - ((CardLayout) this.root.getLayout()).show(this.root, MENU_CARD); + ((CardLayout) this.root.getLayout()).show(this.root, this.scene.UNIQUE_NAME); + this.scene.resumeThread(); } public void quit() { + this.unloadAllScenes(); this.setVisible(false); - - if (this.scene != null) { - this.unloadScene(); - } - this.dispose(); } - // Action Listener for menu - @Override - public void actionPerformed(ActionEvent e) { - if (e.getActionCommand().startsWith("btn")) { - if (e.getActionCommand().equals("btn-exit")) { - this.game.quit(); - return; - } - - if (e.getActionCommand().equals("btn-editor")) { - this.loadScene(new MapEditorScene()); - } else if (e.getActionCommand().equals("btn-battle")) { - this.loadScene(new BattleScene(this.game)); - this.game.start(); - } + /* window listener */ + @Override public void windowActivated(WindowEvent e) {} + @Override public void windowClosed(WindowEvent e) {} + @Override public void windowClosing(WindowEvent e) { + this.quit(); + } - } + @Override + public void windowDeactivated(WindowEvent e) { + this.game.pause(); } + + @Override public void windowDeiconified(WindowEvent e) {} + @Override public void windowIconified(WindowEvent e) {} + @Override public void windowOpened(WindowEvent e) {} } diff --git a/src/subconscious/graphics/MapEditorScene.java b/src/subconscious/graphics/MapEditorScene.java index edf9323..0b6d8a8 100644 --- a/src/subconscious/graphics/MapEditorScene.java +++ b/src/subconscious/graphics/MapEditorScene.java @@ -46,7 +46,7 @@ public class MapEditorScene extends MapScene implements ActionListener { private AffineTransform tx = new AffineTransform(); public MapEditorScene() { - super(); + super(null, "map-editor"); this.setLayout(new BorderLayout()); JPanel bottomPanel = new JPanel(); diff --git a/src/subconscious/graphics/MapScene.java b/src/subconscious/graphics/MapScene.java index a16fcf3..87a6012 100644 --- a/src/subconscious/graphics/MapScene.java +++ b/src/subconscious/graphics/MapScene.java @@ -26,8 +26,6 @@ import java.awt.geom.NoninvertibleTransformException; */ @SuppressWarnings("serial") public abstract class MapScene extends Scene { - - protected Game game; // TODO: remove // @Deprecated @@ -52,8 +50,8 @@ public abstract class MapScene extends Scene { protected double zoom = 1; - public MapScene(Game g) { - super(g); + public MapScene(Game g, String uniqueName) { + super(g, uniqueName); this.game = g; this.map = this.game.getMap(); @@ -66,12 +64,6 @@ public abstract class MapScene extends Scene { this.tileSize = this.shorterCanvasLenght / 10; } - - // This constructor is used ONLY for a MapEditorScene which does not use a - // game object, and when the MapEditor will be removed this constructor - // will also be deleted - @Deprecated - public MapScene() { } protected void renderTiles(Graphics2D g) { for (Tile tile : this.game.getMap().getGrid()) { @@ -248,6 +240,10 @@ public abstract class MapScene extends Scene { case KeyEvent.VK_RIGHT: this.pan.x -= 10; break; + + case KeyEvent.VK_ESCAPE: + this.game.pause(); + break; } } diff --git a/src/subconscious/graphics/MenuScene.java b/src/subconscious/graphics/MenuScene.java new file mode 100644 index 0000000..9c57ca6 --- /dev/null +++ b/src/subconscious/graphics/MenuScene.java @@ -0,0 +1,70 @@ +package subconscious.graphics; + +import subconscious.Game; + +import java.awt.Graphics2D; +import java.awt.GridLayout; +import java.awt.BorderLayout; +import java.awt.Dimension; + +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JButton; + + +@SuppressWarnings("serial") +public class MenuScene extends Scene implements ActionListener { + public MenuScene(Game g, String uniqueName) { + super(g, uniqueName); + + this.game = g; + + // build Menu card + this.setLayout(new GridLayout(3, 1)); + + JButton editor = new JButton("Editor"); + editor.setActionCommand("btn-editor"); + editor.addActionListener(this); + + JButton battle = new JButton("Battle"); + battle.setActionCommand("btn-battle"); + battle.addActionListener(this); + + JButton exit = new JButton("Exit"); + exit.setActionCommand("btn-exit"); + exit.addActionListener(this); + + this.add(editor); + this.add(battle); + this.add(exit); + } + + @Override + public void build() {} + @Override + public void update(long deltaNanoTime) {} + @Override + public void render(Graphics2D g) {} + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().startsWith("btn")) { + if (e.getActionCommand().equals("btn-exit")) { + this.game.quit(); + this.stop(); + return; + } + + if (e.getActionCommand().equals("btn-editor")) { + // TODO: fix editor + // this.loadScene(new MapEditorScene()); + } else if (e.getActionCommand().equals("btn-battle")) { + this.game.start(); + } + + } + } +}
\ No newline at end of file diff --git a/src/subconscious/graphics/Palette.java b/src/subconscious/graphics/Palette.java index 75937a0..50e8372 100644 --- a/src/subconscious/graphics/Palette.java +++ b/src/subconscious/graphics/Palette.java @@ -4,6 +4,7 @@ import java.awt.Color; public class Palette { public final static Color BLACK = new Color(29, 31, 33); + public final static Color BLACK_T = new Color(29, 31, 33, 100); public final static Color WHITE = new Color(197, 200, 198); public final static Color WHITE_T = new Color(197, 200, 198, 200); public final static Color RED = new Color(165, 66, 66); diff --git a/src/subconscious/graphics/PauseScene.java b/src/subconscious/graphics/PauseScene.java new file mode 100644 index 0000000..6365514 --- /dev/null +++ b/src/subconscious/graphics/PauseScene.java @@ -0,0 +1,42 @@ +package subconscious.graphics; + +import subconscious.Game; + +import java.awt.Graphics2D; +import java.awt.BorderLayout; + +import java.awt.event.KeyEvent; + + +@SuppressWarnings("serial") +public class PauseScene extends Scene { + public PauseScene(Game g, String uniqueName) { + super(g, uniqueName); + + // TODO: build on upper level + this.add(this.canvas, BorderLayout.CENTER); + } + + protected void build() { + + } + + protected void render(Graphics2D g) { + g.setColor(Palette.BLACK); + g.fillRect(0, 0, this.canvasSize.width, this.canvasSize.height); + } + + protected void update(long deltaNanoTime) { + } + + + @Override + public void keyReleased(KeyEvent e) { + super.keyReleased(e); + + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + this.game.resume(); + this.stop(); + } + } +}
\ No newline at end of file diff --git a/src/subconscious/graphics/Scene.java b/src/subconscious/graphics/Scene.java index bbc98eb..4132ded 100644 --- a/src/subconscious/graphics/Scene.java +++ b/src/subconscious/graphics/Scene.java @@ -35,14 +35,18 @@ import javax.swing.JPanel; public abstract class Scene extends JPanel implements Runnable, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener, ComponentListener { - + + // this is required to manage multiple scenes + public final String UNIQUE_NAME; + protected final long DESIRED_FPS = 60; protected final long DESIRED_DELTA_LOOP = (1000*1000*1000)/DESIRED_FPS; - protected Game game; + // Game is never be cached in the local thread + protected volatile Game game; - protected boolean running; - protected boolean paused; + protected volatile boolean running; + protected volatile boolean threadPaused; protected Object pauseLock; protected BufferStrategy buffer; @@ -52,12 +56,14 @@ public abstract class Scene extends JPanel // TODO: make accessible from user settings protected int guiSize = 2; - protected Scene() { - this.game = null; - + public Scene(Game game, String uniqueName) { + + this.UNIQUE_NAME = uniqueName; + this.game = game; + // thread control this.running = false; - this.paused = false; + this.threadPaused = false; this.pauseLock = new Object(); // watch for resize @@ -75,22 +81,17 @@ public abstract class Scene extends JPanel this.canvas.addMouseMotionListener(this); this.canvas.addMouseWheelListener(this); + // TODO: solve canvas + // this.add(this.canvas, BorderLayout.CENTER); this.build(); } - protected Scene(Game g) { - // call default contructor - this(); - - this.game = g; - } - /* abstract methods */ // runs when the the scene thread starts protected abstract void build(); // runs on each tick - protected abstract void render(Graphics2D g); protected abstract void update(long deltaNanoTime); + protected abstract void render(Graphics2D g); public void run() { long beginLoopTime; @@ -99,28 +100,22 @@ public abstract class Scene extends JPanel long lastUpdateTime; long deltaLoop; + this.running = true; + this.threadPaused = false; + + // wait for the canvas to be ready + // this is required otherwise createBufferStrategy() + // throws an IllegalStateException + while (!this.canvas.isDisplayable() && running); + // initialize canvas buffer this.canvas.createBufferStrategy(2); this.buffer = this.canvas.getBufferStrategy(); this.canvas.requestFocus(); - this.running = true; - this.paused = false; - while (running) { beginLoopTime = System.nanoTime(); - synchronized (this.pauseLock) { - if (this.paused) { - try { - this.pauseLock.wait(); - } catch (InterruptedException ex) { - ex.printStackTrace(); - break; - } - } - } - do { do { Graphics2D g = (Graphics2D) this.buffer.getDrawGraphics(); @@ -131,7 +126,13 @@ public abstract class Scene extends JPanel // repeat if the drawing buffer contents were lost } while (this.buffer.contentsLost()); - this.buffer.show(); + try { + this.buffer.show(); + } catch (IllegalStateException ex) { + // this happens when the scene is hidden or the frame is disposed + // for example then the thread is stopped, and so this exception + // can be ignored + } lastUpdateTime = currentUpdateTime; currentUpdateTime = System.nanoTime(); @@ -151,25 +152,35 @@ public abstract class Scene extends JPanel } } + + synchronized (this.pauseLock) { + if (this.threadPaused) { + try { + this.pauseLock.wait(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + } } } - public synchronized void stop() { - this.running = false; - + /* game pause controls */ + public void stop() { // stop thread even if paused - this.resume(); + this.resumeThread(); + this.running = false; } - public synchronized void pause() { - this.paused = true; + public void pauseThread() { + this.threadPaused = true; } - public synchronized void resume() { - if (!paused) + public void resumeThread() { + if (!this.threadPaused) return; - this.paused = false; + this.threadPaused = false; synchronized (this.pauseLock) { this.pauseLock.notifyAll(); @@ -192,12 +203,8 @@ public abstract class Scene extends JPanel /* key listener */ @Override public void keyTyped(KeyEvent e) {} @Override public void keyPressed(KeyEvent e) {} + @Override public void keyReleased(KeyEvent e) {} - @Override - public void keyReleased(KeyEvent e) { - // TODO: Escape pauses the game - } - /* mouse listener */ @Override public void mouseClicked(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) {} |