package com.lunar; import com.lunar.input.InputListener; import com.lunar.input.MouseInput; import com.lunar.state.GameState; import com.lunar.window.FramePreferences; import javax.swing.JFrame; import javax.swing.WindowConstants; import java.awt.Color; import java.awt.Graphics; import java.awt.event.KeyListener; import java.awt.event.MouseListener; import java.awt.image.BufferStrategy; import java.util.ArrayList; import java.util.List; public class Game implements Runnable { private JFrame frame; private int width, height, fps; private Thread thread; private boolean running = true; private Graphics graphics; private List<GameState> stack; private int maxTPS = 0; // The maximum tick rate private int maxFPS = 0; // The maximum frame rate private boolean showFPS = false; /** * Initialize the project. * * @param title The string on the window's title bar. * @param width Width of the window * @param height Height of the window * @param tickRate Determines how fast the game loop is. */ public Game(String title, int width, int height, int tickRate) { this.width = width; this.height = height; stack = new ArrayList<>(); this.maxTPS = tickRate; frame = new JFrame(title); frame.setSize(width, height); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); frame.setResizable(false); frame.setFocusable(true); frame.addKeyListener(new InputListener()); frame.addMouseListener(new MouseInput()); } /** * Initialize the project. * * @param title The string on the window's title bar. * @param width Width of the window * @param state A game state (if you have that) * @param height Height of the window * @param tickRate Determines how fast the game loop is. */ public Game(String title, int width, int height, GameState state, int tickRate) { this(title, width, height, tickRate); addToStack(state); } /** * Initialize the game. * * @param title The string on the window's title bar. * @param width Width of the window * @param height Height of the window * @param pref The window preferences. * @param tickRate Determines how fast the game loop is. */ public Game(String title, int width, int height, FramePreferences pref, int tickRate) { this.width = width; this.height = height; stack = new ArrayList<>(); maxTPS = tickRate; frame = new JFrame(title); frame.setSize(width, height); frame.setDefaultCloseOperation(pref.getCloseOperation()); frame.setLocationRelativeTo(pref.getRelativeLocation()); frame.setResizable(pref.isResizable()); frame.setFocusable(pref.isFocusable()); frame.addKeyListener(new InputListener()); frame.addMouseListener(new MouseInput()); } /** * Initialize the game. * * @param title The string on the window's title bar. * @param width Width of the window * @param state A game state (if you have that) * @param height Height of the window * @param pref The window preferences. * @param tickRate Determines how fast the game loop is. */ public Game(String title, int width, int height, FramePreferences pref, GameState state, int tickRate) { this(title, width, height, pref, tickRate); stack = new ArrayList<>(); addToStack(state); } /** * Start the game thread. */ public synchronized void start() { frame.setVisible(true); frame.createBufferStrategy(3); onStart(); running = true; thread = new Thread(this); thread.start(); } /** * Stop the game thread. */ public synchronized void stop() { running = false; try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } /** * The game-loop, handles drawing, updating, etc. */ @Override public void run() { long lastRunTime = System.nanoTime(); double TICK_RATE = 1000000000 / maxTPS; double tickDelta = 0; int frames = 0; long frameTime = System.currentTimeMillis(); while (running) { long now = System.nanoTime(); long tickTime = now - lastRunTime; long timeStart = System.currentTimeMillis(); lastRunTime = now; tickDelta += tickTime / TICK_RATE; while (tickDelta >= 1) { onTick(); tickDelta--; } if (frames < maxFPS || maxFPS == 0) { onDraw(); frames++; } if (System.currentTimeMillis() - frameTime >= 1000) { frameTime += 1000; fps = frames; frames = 0; } // TODO: Fix later(implement a new game loop), currently locks FPS to whatever the TPS is. long time = ((long) TICK_RATE - (System.currentTimeMillis() - timeStart)) / (long) 1e6; try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } stop(); } /** * When the game starts. */ private void onStart() { // call onStart method to each GameStack. stack.forEach(GameState::onStart); } /** * Draw all game objects. */ private void onDraw() { BufferStrategy frameStrategy = frame.getBufferStrategy(); if (frameStrategy == null) { // create the buffer strategy, its null. frame.createBufferStrategy(3); frameStrategy = frame.getBufferStrategy(); } // clear and get our graphics object. graphics = frameStrategy.getDrawGraphics(); graphics.clearRect(0, 0, width, height); if (showFPS) { graphics.setColor(Color.red); graphics.drawString(Integer.toString(fps) + " fps", 50, 50); } // update stack. stack.forEach(state -> state.onDraw(graphics)); graphics.dispose(); frameStrategy.show(); } /** * Update all game objects. */ private void onTick() { stack.forEach(GameState::onTick); } /** * Gets the width of the Game's window. * * @return the width */ public int getWidth() { return width; } /** * Gets the height of the Game's window. * * @return the height */ public int getHeight() { return height; } /** * Shows or hides the current FPS. * Mainly for debugging purposes. */ public void setFPSVisibility(boolean showFPS) { this.showFPS = showFPS; } /** * Limits the maximum FPS. * Set to 0 for unlimited FPS. */ public void setMaxFPS(int frames) { this.maxFPS = frames; } /** * Gets the FPS of the Game. * * @return the frames per second count, * which is how many times the game renders in a given second. */ public int getFPS() { return fps; } /** * @return the graphics object. */ public Graphics getGraphics() { return graphics; } /** * Add a state to the stack. */ public void addToStack(GameState state) { stack.add(state); stack.sort((state1, state2) -> Integer.compare(state2.getPriority(), state1.getPriority())); } /** * Remove the state from the stack. * * @param state the state that should be removed from the game stack. */ public void removeFromStack(GameState state) { stack.remove(state); } /** * Clear the stack. */ public void clearStack() { stack.clear(); } /** * Add a key listener */ public void addKeyListener(KeyListener listener) { frame.addKeyListener(listener); } /** * Clear all key listeners. NOTE: this method will also remove the default listener. */ public void clearKeyListeners() { KeyListener[] listeners = frame.getKeyListeners(); for (KeyListener listener : listeners) { removeKeyListener(listener); } } /** * Remove the given key listener */ public void removeKeyListener(KeyListener listener) { frame.removeKeyListener(listener); } /** * Add a mouse listener */ public void addMouseListener(MouseListener listener) { frame.addMouseListener(listener); } /** * Clear all mouse listeners. NOTE: this method will also remove the default listener. */ public void clearMouseListeners() { MouseListener[] listeners = frame.getMouseListeners(); for (MouseListener listener : listeners) { removeMouseListener(listener); } } /** * Remove the given mouse listener */ public void removeMouseListener(MouseListener listener) { frame.removeMouseListener(listener); } }