All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kauridev.lunarbase.Game Maven / Gradle / Ivy

Go to download

Lunar Base is a simple game framework written in Java. It is written using an entity-component-system architecture.

The newest version!
/*
 * This file is part of the lunar-base package.
 *
 * Copyright (c) 2014 Eric Fritz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package com.kauridev.lunarbase;

/**
 * A basic game utility that provides timing mechanisms including an update/render loop. The loop
 * can be either fixed or variable timestep (fixed timestep by default). The type of step determines
 * how often {@link #update(double)} and {@link #render(double)} will be called and affects how you
 * need to represent time-based procedures such as movement and animation.
 * 

* When using a variable timestep game loop you should express rates - such as the distance a sprite * moves - in game units per millisecond (ms). The amount a sprite moves in any given update can * then be calculated as the product of the rate of the sprite and the elapsed time. Using this * approach to calculate the distance the sprite moved ensures that the sprite will move * consistently if the speed of the game or computer varies. * * @author Eric Fritz */ abstract public class Game { /** * The number of slow updates before {@link #isRunningSlowly} is set to true. */ private final int slowUpdateThreshold = 20; /** * Whether the game loop has started. */ private boolean isStarted = false; /** * Whether the game loop is running. */ private boolean isRunning = false; /** * Whether the game loop has started. */ private boolean isRunningSlowly = false; /** * Whether the game loop runs in fixed or variable timestep. */ private boolean isFixedTimeStep = true; /** * The number of millisecond calls to update should be padded if isFixedTimeStep is true. */ private double targetElapsedTime = 1000.0 / 60; /** * Whether update should be called if the game is inactive. */ private boolean alwaysUpdate = true; /** * Whether render should be called if the game is inactive. */ private boolean alwaysRender = true; /** * Whether render should be skipped during the next tick. */ private boolean isRenderSuppressed = false; /** * Whether update has been called at least once. */ private boolean hasCalledUpdate = false; /** * Whether render has been called at least once. */ private boolean hasCalledRender = false; /** * The relative time (in milliseconds) of when the game loop started. */ private long startTime; /** * The relative time (in nanoseconds) of the last call to update. */ private long lastUpdate; /** * The relative time (in nanoseconds) of the last call to render. */ private long lastRender; /** * The number of consecutive updates that took longer than targetElapsedTime. */ private int slowUpdates; /** * Initializes the game and begins running the game loop. * * @throws IllegalStateException If the game has already been started. */ final public void start() { if (isStarted) { throw new IllegalStateException(); } run(); } /** * Stops the game loop. * * @throws IllegalStateException If the game is not running. */ final public void stop() { if (!isRunning) { throw new IllegalStateException(); } isRunning = false; } /** * Runs the game. */ private void run() { isStarted = true; isRunning = true; startTime = System.currentTimeMillis(); init(); while (isRunning) { tick(); } exit(); } /** * Calls {@link #update(double)} (if applicable) and {@link #render(double)} (if applicable). */ private void tick() { // If the loop is running in fixed timestep and at least one update has already been // called, wait until targetElapsedTime has passed before calling the next update. if (isFixedTimeStep && hasCalledUpdate) { double elapsed = getTimeSinceLastUpdate(); if (elapsed < targetElapsedTime) { try { Thread.sleep((long) (targetElapsedTime - elapsed)); } catch (InterruptedException e) { e.printStackTrace(); } } } // Call update. if ((isActive() || alwaysUpdate)) { // // TODO - if inactive, should the elapsed time reset? double elapsed = getTimeSinceLastUpdate(); lastUpdate = System.nanoTime(); update(elapsed); hasCalledUpdate = true; if (!isRunning) { return; } } // Determine if the game loop is running slowly. Maintain a counter for the number of // consecutive updates that took longer than targetElapsedTime. If this value becomes // greater than slowUpdateThreshold, the loop is running consistently slower than the // target elapsed time. isRunningSlowly = false; if (isFixedTimeStep && getTimeSinceLastUpdate() > targetElapsedTime) { if (slowUpdates++ >= slowUpdateThreshold) { isRunningSlowly = true; } } else { slowUpdates = 0; } // Call render if the game loop is not running slowly and the last call to update did // not suppress rendering for this tick. if ((isActive() || alwaysRender) && !isRenderSuppressed && !isRunningSlowly) { // // TODO - if inactive, suppressed, or skipped, should the elapsed time reset? double elapsed = getTimeSinceLastRender(); lastRender = System.nanoTime(); render(elapsed); hasCalledRender = true; } isRenderSuppressed = false; } /** * Returns the total elapsed time (in milliseconds) that the game loop has been running. This * also includes the time it took to initialize the game, not just the time since the first * update. * * @return The total elapsed time (in milliseconds) that the game loop has been running. */ final public long getTotalElapsedTime() { return System.currentTimeMillis() - startTime; } /** * Returns the time elapsed (in fractional milliseconds) since the last call to * {@link #update(double)}. If update has not yet been called since the start of the * game loop, this method returns 0.0. * * @return The time elapsed (in fractional milliseconds) since the last call to update. */ final public double getTimeSinceLastUpdate() { if (!hasCalledUpdate) { return 0; } return (System.nanoTime() - lastUpdate) / 1000000.0; } /** * Returns the time elapsed (in fractional milliseconds) since the last call to * {@link #render(double)}. If render has not yet been called since the start of the * game loop, this method returns 0.0. * * @return The time elapsed (in fractional milliseconds) since the last call to render. */ final public double getTimeSinceLastRender() { if (!hasCalledRender) { return 0; } return (System.nanoTime() - lastRender) / 1000000.0; } /** * Returns true if the game is running, false otherwise. * * @return true if the game is running, false otherwise. */ final public boolean isRunning() { return isRunning; } /** * Returns true if the game loop is taking too long, false otherwise. *

* During fixed timestep, if calls to {@link #update(double)} are taking consistently longer * than the target elapsed time to complete, the game loop can be considered to be running too * slowly and should do something to "catch up". * * @return true if the game loop is taking too long, false otherwise. * * @see #setTargetElapsedTime(double) * @see #setTargetUpdatesPerSecond(int) */ final public boolean isRunningSlowly() { return isRunningSlowly; } /** * Sets the game loop as either fixed timestep or variable timestep. *

* If isFixedTimeStep is true, the game loop will run in fixed timestep. The * game loop will call {@link #update(double)} after the target elapsed time has passed since * the last call to update and then attempt to call {@link #render(double)} if the game * loop is not running too slowly. *

* If isFixedTimeStep is false, the game loop will run in variable timestep. * The game loop will call update and render continuously. * * @param isFixedTimeStep true for fixed timestep, false for variable timestep. * * @see #isRunningSlowly() * @see #setTargetElapsedTime(double) * @see #setTargetUpdatesPerSecond(int) */ final public void setFixedTimeStep(boolean isFixedTimeStep) { this.isFixedTimeStep = isFixedTimeStep; } /** * Sets the target number of calls to {@link #update(double)} per second when the game loop runs * in fixed timestep. * * @param numUpdates The target number of calls to update per second. * * @throws IllegalArgumentException If targetElapsedTime is not greater than zero. * @see #setFixedTimeStep(boolean) * @see #setTargetElapsedTime(double) */ final public void setTargetUpdatesPerSecond(int numUpdates) { if (targetElapsedTime <= 0) { throw new IllegalArgumentException(); } setTargetElapsedTime(1000.0 / numUpdates); } /** * Sets the target time between calls to {@link #update(double)} in milliseconds when the game * loop runs in fixed timestep. * * @param targetElapsedTime The target time between calls to update in milliseconds. * * @throws IllegalArgumentException If targetElapsedTime is not greater than zero. * @see #setFixedTimeStep(boolean) * @see #setTargetUpdatesPerSecond(int) */ final public void setTargetElapsedTime(double targetElapsedTime) { if (targetElapsedTime <= 0) { throw new IllegalArgumentException(); } this.targetElapsedTime = targetElapsedTime; } /** * Prevents calls to {@link #render(double)} until after the next call to * {@link #update(double)}. */ final public void suppressRender() { isRenderSuppressed = true; } /** * Returns true if the game is active, false otherwise. *

* The base implementation always returns true. Override this method to return * false when, for example, the window is minimized or does not have focus. * * @return true if the game is active, false otherwise. */ public boolean isActive() { return true; } /** * Sets whether {@link #update(double)} should be called when the game is not active. * * @param alwaysUpdate true if update should be called when the game is not active. * * @see #isActive() */ final public void setAlwaysUpdate(boolean alwaysUpdate) { this.alwaysUpdate = alwaysUpdate; } /** * Sets whether {@link #render(double)} should be called when the game is not active. * * @param alwaysRender true if render should be called when the game is not active. * * @see #isActive() */ final public void setAlwaysRender(boolean alwaysRender) { this.alwaysRender = alwaysRender; } /** * Initializes the game. */ abstract public void init(); /** * Called after the game has stopped updating. This is where allocated resources should be * closed or released. */ abstract public void exit(); /** * Called when the game has determined that game logic needs to be processed. This might include * the management of the game state, the processing of user input, or the updating of simulation * data. Override this method with game-specific logic. * * @param elapsed The number of milliseconds elapsed since the last call to update. */ abstract public void update(double elapsed); /** * Called when the game determines it is time to render a frame. Override this method with * game-specific rendering code. * * @param elapsed The number of milliseconds elapsed since the last call to render. */ abstract public void render(double elapsed); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy