I am writing a game loop, I found the code in the example below here. I have also looked at other ways to do a game loop, such as from this article. I couldn't get any of those ones working though. So I kept with the one from the first link.
What I would like to know:
Is the way I wrote my game loop a good way to do this?
Any suggestions?
Should I be using Thread.sleep(); in my game loop?
Here is my current code:
public void run(){
long lastLoopTime = System.nanoTime();
final int TARGET_FPS = 60;
final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
long lastFpsTime = 0;
while(true){
long now = System.nanoTime();
long updateLength = now - lastLoopTime;
lastLoopTime = now;
double delta = updateLength / ((double)OPTIMAL_TIME);
lastFpsTime += updateLength;
if(lastFpsTime >= 1000000000){
lastFpsTime = 0;
}
this.updateGame(delta);
this.repaint();
try{
Room.gameTime = (lastLoopTime - System.nanoTime() + OPTIMAL_TIME) / 1000000;
System.out.println(Room.gameTime);
Thread.sleep(Room.gameTime);
}catch(Exception e){
}
}
Eventually you'll want to move to something like LWJGL, but let me stress, keep doing what you're doing here for now. It will teach you fundamentals.
Good job on your loop. Looks nice, let me offer a few pointers:
Repaint will not render the screen immediately. It tells the RepaintManager to render when its ready. Use invalidate paintImmediately instead. paintImmediately will block execution until the component has been redrawn so you can measure rendering time.
Thread.sleep typically has a few milliseconds drift. You should be using it to keep your loop from using too much CPU, but make sure you understand if you sleep 10 milliseconds you might sleep 5 milliseconds or you might sleep 20.
Lastly:
double delta = updateLength / ((double)OPTIMAL_TIME);
If updateLength is less than OPTIMAL_TIME, don't call update. In other words, if delta is less than one, don't update. This tutorial explains why better than I ever could.
Overall, it is a good loop, but there are a few missing aspects to what I have found in experience to be the best loop.
You will eventually want to move to LWJGL or some other java game API, but for now, learn the basics of how game-loops work, and what best suits your needs.
Firstly, in answer to one of your points, no. You will do better staying away from
Thread.sleep()
this can stray from the real amount of time you set it to sleep.
e.g. if you set it to sleep for 10 milliseconds, it could sleep the program for 5 to 20 milliseconds.
The second problem I cam immediately see is that you do not have any way to stop the game-loop for a custom stop() method. Try
boolean running = true;
while (running) {
// Your Code Here //
}
Thirdly, you may want to consider changing how you use your delta variable. The way in the code below may be a better use and construction for you.
This is an example of my game-loop that I use in my programs:
#Override
public void run() {
long initialTime = System.nanoTime();
final double timeU = 1000000000 / UPS;
final double timeF = 1000000000 / FPS;
double deltaU = 0, deltaF = 0;
int frames = 0, ticks = 0;
long timer = System.currentTimeMillis();
while (running) {
long currentTime = System.nanoTime();
deltaU += (currentTime - initialTime) / timeU;
deltaF += (currentTime - initialTime) / timeF;
initialTime = currentTime;
if (deltaU >= 1) {
getInput();
update();
ticks++;
deltaU--;
}
if (deltaF >= 1) {
render();
frames++;
deltaF--;
}
if (System.currentTimeMillis() - timer > 1000) {
if (RENDER_TIME) {
System.out.println(String.format("UPS: %s, FPS: %s", ticks, frames));
}
frames = 0;
ticks = 0;
timer += 1000;
}
}
}
the simplest way to refresh repainting could be like this:
public class GameLoop extends JPanel {
private final BufferedImage back_buffer;
bool state = true;
public void init() {
while (state) {
updatePlayer();
delay(5);
}
}
public void delay(int time) {
try {
Thread.sleep(time);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
repaint();
}
and the repaint function is who update all of the graphics
#Override
public void paint(Graphics g) {
super.paint(g);
grapicDoble.setColor(Color.white);
grapicDoble.fillRect(0, 0, 500, 500);
game.reset();
g.drawImage(back_buffer, 0, 0, this);
}
Related
I'm fairly new to Swing, and I'm attempting to make a simple game in which, at its current stage, you move a box around with w-a-s-d and it faces the mouse cursor. In terms of those functions, I'm having no issues.
Unfortunately, where I am having issues is with the application stuttering when few actions are being performed.
For example, if I am moving the box around but the cursor is still, the game starts stuttering badly. However, if I wiggle the cursor, the game runs fine.
After some research, it appears that the issue is related to Swing's Component.repaint(). After implementing my understanding of this, though, the stuttering issues are unchanged.
The way I implemented it was by having my abstract base GameObject class extend Component, with all objects displayed in the game being descendants of that class.
In the Game class, the main class of the application, the run function is responsible for updating the objects in the game.
long lastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis();
int frames = 0;
while (running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1) {
tick();
delta--;
}
if (running) {
render();
}
frames++;
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("FPS: " + frames);
frames = 0;
}
}
stop();
}
The tick() and render() functions call the handler, which handles all of the objects.
private void tick() {
handler.tick();
}
The Handler class, complete with my additions of repainting the objects. I added repaint after calling both the tick and render functions of the object, but neither had any major effect.
public class Handler {
LinkedList<GameObject> objects = new LinkedList<GameObject>();
public void tick() {
for (int i = 0; i < objects.size(); i++) {
GameObject object = objects.get(i);
object.tick();
object.repaint();
}
}
public void render(Graphics g) {
for (int i = 0; i < objects.size(); i++) {
GameObject object = objects.get(i);
object.render(g);
object.repaint();
}
}
}
So while these changes didn't seem to help the stuttering in my program, something else did - a print statement in the run function, printing a simple string.
I can't for the life of me think how that could possibly be affecting anything, and it certainly isn't a permanent fix, so any advice on the situation would be greatly appreciated
I know there are a lot of problems but perhaps by freezing another task to achieve the nearest result possible or by using a parallel thread?
Here is my code:
public void draw(Canvas canvas) {
DrawButtons(canvas);
DrawPercise(canvas);
DrawLines(canvas);
}
private void DrawButtons(Canvas canvas) {
canvas.drawBitmap(Button, 50, 0, null);
}
private void DrawPercise(Canvas canvas) {
if (System.nanoTime() >= AllowedTimeinNano) {
// Save time again for Next if
//if 50000000 nanoseconds passed do it again
AllowedTimeinNano = (long) (System.nanoTime() + (20000000000f / 400));
DoTask();
}
}
private void DrawLines(Canvas canvas) {
for (float i = 40; i < 800; i += 40) {
canvas.drawLine(0, i, 800, i, TablePaint);
}
}
The problem is if my task takes too long or the target device has bad performance, then the timing becomes incorrect, and the whole point of the app is based on this timing. I know this may be impossible but could you give some tips?
I thought I would answer this question to clairify what I said in the comment.
Thread:
public class GameThread extends Thread {
private int FPS = 60;
private double averageFPS;
private SurfaceHolder surfaceHolder;
private Clicker gamePanel;
private boolean running;
public static Canvas canvas;
public GameThread(SurfaceHolder surfaceHolder, Clicker gamePanel)
{
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#Override
public void run()
{
long startTime;
long timeMillis;
long waitTime;
long totalTime = 0;
int frameCount =0;
long targetTime = 1000/FPS;
while(running) {
startTime = System.nanoTime();
canvas = null;
//try locking the canvas for pixel editing
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
this.gamePanel.tick();
this.gamePanel.draw(canvas);
}
} catch (Exception e) {
}
finally{
if(canvas!=null)
{
try {
surfaceHolder.unlockCanvasAndPost(canvas);
}
catch(Exception e){e.printStackTrace();}
}
}
timeMillis = (System.nanoTime() - startTime) / 1000000;
waitTime = targetTime-timeMillis;
try{
sleep(waitTime);
}catch(Exception e){}
totalTime += System.nanoTime()-startTime;
frameCount++;
if(frameCount == FPS)
{
averageFPS = 1000/((totalTime/frameCount)/1000000);
frameCount =0;
totalTime = 0;
System.out.println(averageFPS);
}
}
}
public void setRunning(boolean b)
{
running=b;
}
}
I got it when I took a tutorial when I started up, and I have only used this. Before you can use it there are some things you have to do:
Have a class that extends SurfaceView implements SurfaceHolder.Callback
Replace 'Clicker' with the name of your class.
For FPS calculation:
milliseconds in 1 second / FPS = how often in milliseconds it will update
1000 / 60 = 16.666666
Which means it updates every 0.01666666 seconds
How do I use it?
Simple. (You have to find the propper places to place them yourself)
Creating it:
if(gt == null) {
gt = new GameThread(getHolder(), this);
gt.setRunning(true);
gt.start();
}
Stopping it:
if(gt != null) {
try {
gt.setRunning(false);
gt.join();
gt = null;
} catch (InterruptedException e) {
Toast.makeText(c, "An error occured when stopping the thread.", Toast.LENGTH_LONG)
.show();
}
}
In my experience, 60 FPS is the best FPS to take to secure that this will work on all devices. All though, there are some exceptions.
Normal phones today have 2GB of ram or more. For an instance, S7 has 4 gigabytes of ram. However, if there is 512 MB of ram, I'm not sure as of performance. But there are very few devices left in the world with 512 MB RAM. There are some budget phones, but there are not a lot of those with only 512 MB of RAM unless you would select the really old versions.
So, by using the thread above, you will have a game thread that updates contantly and will never stop. You do not have to worry performance wise, because there are mostly new devices running for real potential customers.
Additionally, this thread works in a very simple way:
FPS is the max FPS and will therefore not go higher, so lower end devices will go for as high as possible up to 60, while newer will stay steady at 60.
All though I have experienced with my phone that it sometimes go up to 62, but it isn't really a problem because it is only two FPS too much.
Remember:
The more actions that happen in methods touched by the gamethread, the more strain it is on the system and on the app. Any game can reach 2000 fps if there is no limit and nothing happens. While loops are fast!
So I am making a simple game with spites. But im having some issues with the tick/render loop.
I need it to run at 30 ticks a second. But the fps needs to be as fast as it can.
The issue I am having is the while loop doesn't run
lastTime += msPerTick;
So my output looks like this. No adding. No loop. No render.
x:0.375 y:0.03333333333333333
x:0.375 y:0.03333333333333333
x:0.375 y:0.03333333333333333
x:0.375 y:0.03333333333333333
x:0.375 y:0.03333333333333333
x:0.375 y:0.03333333333333333
x:0.375 y:0.03333333333333333
x:0.375 y:0.03333333333333333
Here is a snippet of the code. Let me know if you need more.
public static final int TICKS_PER_SECOND = 30;
private static final int MAX_TICKS_PER_FRAME = 10;
private boolean running;
private Thread thread;
private int tickCount;
private int frames;
private boolean paused;
public void run() {
init();
float lastTime = (System.nanoTime() / 1000000) / 1000.0f;
running = true;
double msPerTick = 1.0 / TICKS_PER_SECOND;
while (running) {
synchronized (this) {
float now = (System.nanoTime() / 1000000) / 1000.0f;
int frameTicks = 0;
while (now - lastTime > msPerTick) {
System.out.println("x:" + (now - lastTime) + " y:" + msPerTick);
if (!paused && frameTicks++ < MAX_TICKS_PER_FRAME)
tick();
lastTime += msPerTick; //<-- Issue here
}
if (!paused) {
render((now - lastTime) / msPerTick);
}
}
//Thread.yield(); No need for yield lets use sleep.
try {
Thread.sleep(paused ? 500 : 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Because you're not adding 2 doubles. If you change the type of lastTime to double the code will work fine.
float f = (float) 0.2;
double d = 2;
f += d => 2.0
d += f => 2.2
I think there is an algorithm problem here which happens if your render action takes less than 1/30 second.
When you start the outer loop executes multiple times for 1/30 seconds without entering the inner loop. At the 1/30 second mark it enters the inner loop exactly once and then spends another 1/30 seconds looping in the outer loop.
If the render takes more than 1/30 seconds then you should not have the problem.
You cannot rely on the Thread.yield() to do much of anything.
I'm trying to render the positions of multiple fighters onscreen. The relevant code is as follows:
public void run() {
double ns = 1000000000.0 / tps;
double delta = 0;
int frames = 0;
int updates = 0;
long lastTime = System.nanoTime();
long timer = System.currentTimeMillis();
while (running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while(delta >= 1) {
update();
updates++;
delta--;
}
frame.getContentPane().repaint();
frames++;
if(System.currentTimeMillis() - timer >= 1000) {
timer += 1000;
frame.setTitle(title + " | " + updates + " ups, " + frames + " fps");
frames = 0;
updates = 0;
}
}
stop();
}
private void update() {
if (Math.random() < .1) {
Fighter newFighter = new Fighter();
fighterList.add(newFighter);
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
setBackground(Color.BLUE);
System.out.println(fighterList.size());
for (int i = 0; i<fighters; i++) {
System.out.println("Attempted");
g.setColor(Color.GREEN);
g.drawRect((int) fighterList.get(i).xPos,
(int) fighterList.get(i).yPos,
fighterList.get(i).radius,
fighterList.get(i).radius);
System.out.println("Rendered");
}
}
public static void main(String[] args) {
Game game = new Game();
game.frame.setContentPane(new Game());
game.frame.setResizable(false);
game.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.frame.setLocationRelativeTo(null);
game.frame.setVisible(true);
game.start();
}
Issue is, nothing is being drawn to the screen. Additionally, running System.out.println(fighterList.size()); gives different outputs based on where it's run - when run inside paintComponent it always returns zero, wheras when run inside update it returns the proper amount. Is this an issue with scope, or is there something else I'm missing?
Most likely a synchronization issue. Your paintComponent() method is always called from the EDT (Event Dispatch Thread) while your run() method runs in its own separate thread. This is where update() gets called which adds new Fighters to the list.
You need proper synchronization so both (or all) threads will see the same consistent data.
Also since your model (data) may be modified during a repaint, you should also "clone" the model to avoid inconsistent model being painted. Or if you don't want to clone it, synchronize access to the model so it can't get modified while it is painted.
I've just exported my Eclipse project to a runnable jar file. I have no lag issues when I run it in Eclipse, but have extreme lag when running it in the jar. (Running MacOSX 10.9.4)
According to my FPS counter, I'm getting over 900 frames a second, and the game is actually running, but nothing is actually being rendered for a while.
Not sure what code to show because there is a lot of it, as it is an almost completed game. But let me know what code you might need.
Here's my game loop:
public void run() {
long start = System.nanoTime();
final double numUpdates = 30.0;
double ns = 1000000000 / numUpdates;
double delta = 0;
int updates = 0;
int frames = 0;
long timer = System.currentTimeMillis();
while(running) {
long current = System.nanoTime();
delta += (current - start) / ns;
start = current;
if(delta >= 1) {
update();
updates++;
delta--;
}
render();
frames++;
if(System.currentTimeMillis() - timer > 1000) {
timer = System.currentTimeMillis();
Window.frame.setTitle("Rage Mage UPS: " + updates + ", FPS: " + frames);
updates = 0;
frames = 0;
}
}
}
render() method:
private void render() {
statusHandler.render(g); // statusHandler is a class that handles the current state of the game
Graphics g2 = getGraphics();
g2.drawImage(image, 0, 0, WIDTH * SCALE, HEIGHT * SCALE, null);
}
I don't think the game loop is the problem though, because I was getting the same issues before with a different loop, and then I changed to this loop, and nothing has changed.
Thanks!