Skip to content

Instantly share code, notes, and snippets.

@iiAtlas
Created November 21, 2012 01:42
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save iiAtlas/4122531 to your computer and use it in GitHub Desktop.
Save iiAtlas/4122531 to your computer and use it in GitHub Desktop.
Simple Double-Buffered, Delta Based Canvas [Java]
package com.atlas.fps;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
public class AtlasFPSLimiter extends Canvas implements Runnable {
private JFrame frame;
private final int WIDTH = 640, HEIGHT = 480;
private int x = 20, y = 20, fps, tps;
private boolean running = false;
public AtlasFPSLimiter() {
frame = new JFrame("FPS: ~ TPS: ~");
frame.setSize(WIDTH, HEIGHT);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBackground(Color.black);
setForeground(Color.white);
frame.add(this);
frame.setVisible(true);
start();
}
private void start() {
if(!running) {
running = true;
new Thread(this).start();
}
}
private void stop() {
running = false;
}
private void preDraw() { //Method which prepares the screen for drawing
BufferStrategy bs = getBufferStrategy(); //Gets the buffer strategy our canvas is currently using
if(bs == null) { //True if our canvas has no buffer strategy (should only happen once when we first start the game)
createBufferStrategy(2); //Create a buffer strategy using two buffers (double buffer the canvas)
return; //Break out of the preDraw method instead of continuing on, this way we have to check again if bs == null instead of just assuming createBufferStrategy(2) worked
}
Graphics g = bs.getDrawGraphics(); //Get the graphics from our buffer strategy (which is connected to our canvas)
g.setColor(getBackground());
g.fillRect(0, 0, WIDTH, HEIGHT); //Fill the screen with the canvas' background color
g.setColor(getForeground());
draw(g); //Call our draw method, passing in the graphics object which we just got from our buffer strategy
g.dispose(); //Dispose of our graphics object because it is no longer needed, and unnecessarily taking up memory
bs.show(); //Show the buffer strategy, flip it if necessary (make back buffer the visible buffer and vice versa)
}
private void draw(Graphics g) {
g.fillRect(x, y, 25, 25);
}
private void tick(double delta) {
x += 1 * delta; //Multiply all movements by delta where the number before is your speed, this forces any movements to scale based on time passed since the last tick
y += 1 * delta;
}
@Override
public void run() {
int desiredTPS = 60; //Target ticks per second
long lastTime = System.currentTimeMillis(); //Time since we last looped (tick + draw), initialized here to the current time
long secondTime = lastTime + 1000; //Target time one second ahead of when we last updated fps/tps
double msPerTick = 1000 / desiredTPS; //Milliseconds expected in a single tick
int frames = 0, ticks = 0; //Used for counting frames and ticks while in between seconds, later used to set fps and tps
double delta = 0; //Represents the time passed since last tick
boolean needsRender = false; //True when the screen is dirty (when we have ticked)
while(running) {
long currentTime = System.currentTimeMillis(); //The time when we began our game loop
long timeDifference = currentTime - lastTime; //The difference in time since the last game loop (expressed as a negative value
lastTime = currentTime; //Reassign lastTime since we have used it, the last time is now the current time
delta += timeDifference / msPerTick; //A representation of the time passed, converted into a more manageable format
while(delta >= 1) { //Tick as many times as delta is greater then one
ticks++; //Increase temporary ticks variable, later used to set tps
tick(delta); //Call the tick method, pass it delta so it can alter any movement accordingly
delta--; //Decrease delta because we have used it
needsRender = true; //Informs us that the screen is dirty, must be rendered
}
if(needsRender) {
frames++; //Increase temporary frames variable, later used to set fps
preDraw(); //Draw the screen (or at least call the method which draws the screen)
}
if(System.currentTimeMillis() >= secondTime) { //True when the current time is equal to (or greater than) one second since we last updated fps/tps
fps = frames; //set fps to the frames variable which has been increasing over the last second
tps = ticks; //set tps to the frames variable which has been increasing over the last second
frames = 0; //reset frame count
ticks = 0; //reset tick count
frame.setTitle("FPS: " + fps + " TPS: " + tps);
secondTime = System.currentTimeMillis() + 1000; //Set the time which we must again update fps/tps (one second from the current time)
}
try { Thread.sleep(10); } catch(Exception e) { e.printStackTrace(); } //Attempt to sleep the thread for 10ms, not necessarry to run it nonstop even though our game will behave okay
}
}
public static void main(String[] args) {
new AtlasFPSLimiter();
}
}
@EduenSarceno
Copy link

EduenSarceno commented Apr 16, 2017

@iiAtlas I think it is necessary reset the flag needsRender to false after a iteration.

while (running) {
    ...
    while (delta >= 1) {
        ...
        needsRender = true;
    }
    if (needsRender) {
        ...
    }
    ...
    needsRender = false; //missing this
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment