jasonjohnson
JGO n00b  Posts: 8
|
 |
«
on:
2009-02-24 16:24:58 » |
|
I've been having a bit of trouble coming up with a decent game loop (or maybe this is decent, I just feel it could be better). Taken directly from the game I'm working on, my loop is below: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public void loop() { Date startTime = new Date(); Date previousRepaint = startTime; Date currentTime = startTime; float delta; float redrawsPerSecond = 1000/maxFps; while(!gameOver) { currentTime = new Date(); delta = currentTime.getTime()-previousRepaint.getTime(); update(delta); repaint(); previousRepaint = currentTime; if(delta <= redrawsPerSecond) { try { Thread.sleep(10); } catch(Exception e) { } } } } |
I see this as a sort of passive limitation (using Thread.sleep() instead of limiting the redraws conditionally beforehand). One thing to note is when I leverage the delta properly, I can keep CPU usage down to between 15-20% with very smooth animation. I'm really looking for any sort of advice on this. Is there generally a better way to go about the game loop? Is this code fine, but maybe a few tweaks here and there?
|
|
|
|
h3ckboy
JGO Kernel      Posts: 1645 Medals: 4
|
 |
«
Reply #1 on:
2009-02-24 16:35:23 » |
|
are you using any libraries. cause if so. you can limit the updates in Slick. acn probably do someting similar in LWJGL and JOGL.
|
|
|
|
|
jasonjohnson
JGO n00b  Posts: 8
|
 |
«
Reply #2 on:
2009-02-24 16:46:09 » |
|
I'm not currently using any libraries. I'm literally just casting the Graphics object that is sent to the paint() methods I'm overriding up to a Graphics2D object and turning on anti-aliasing with the proper RenderingHints.
I will eventually switch to something a bit more powerful (and more appropriate) like Slick, LWJGL or JOGL for more complex games, but I wanted to get a good grasp of the underlying 2D rendering capabilities.
Any thoughts on the game loop itself, though?
|
|
|
|
Games published by our own members! Go get 'em!
|
|
Wildern
Full Member   Posts: 140
|
 |
«
Reply #3 on:
2009-02-24 19:15:50 » |
|
Your loop will be jerky depending on what is going on between/during frame renders. If cpu usage stays constant, your updates will be smooth, but get a cpu usage spike and you get a jerk. I would recommend having a variable that holds your desired frames/second and using that in combination with your delta to calculate how long you need to sleep. This is pretty much what I use in my game loop. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| private static final int DELAYS_PER_YIELD = 1000000000; private static final int MAX_FRAME_SKIPS = 5; private static final long NANO_IN_MILLI = 1000000L; int fps = 60; long period = 1000L / (long)fps; boolean running = true;
public void start() { new Thread() { public void run() { long overSleepTime = 0L; int noDelays = 0; long excess = 0L; Thread.currentThread().setPriority(10); long beforeTime = System.nanoTime(); long lastRunTime = beforeTime;
while (running) { long delta = System.nanoTime() - lastRunTime; lastRunTime = System.nanoTime(); nextFrame(delta / NANO_IN_MILLI);
render();
long afterTime = System.nanoTime(); long timeDiff = afterTime - beforeTime; long sleepTime = period - timeDiff - overSleepTime; if (sleepTime > 0L) { try { Thread.sleep(sleepTime / NANO_IN_MILLI, (int)(sleepTime % NANO_IN_MILLI)); } catch (InterruptedException interruptedexception) { } overSleepTime = System.nanoTime() - afterTime - sleepTime; } else { excess -= sleepTime; overSleepTime = 0L; if (++noDelays >= DELAYS_PER_YIELD) { Thread.yield(); noDelays = 0; } } beforeTime = System.nanoTime(); int skips; for (skips = 0; excess > period && skips < MAX_FRAME_SKIPS; skips++) { excess -= period; nextFrame(period / NANO_IN_MILLI); } framesSkipped += skips; } System.exit(0); } }.start(); } |
|
|
|
|
|
jasonjohnson
JGO n00b  Posts: 8
|
 |
«
Reply #4 on:
2009-02-25 19:53:30 » |
|
Looks like a decent upgrade from my trivial game loop  I'll dissect this and see where I can get in terms of performance. Thanks for sharing your loop. Anyone else have a game loop they'd be willing to share? Preferably not based on Slick or any other game library?
|
|
|
|
zammbi
JGO Strike Force    Posts: 963 Medals: 9
|
 |
«
Reply #5 on:
2009-02-27 07:27:45 » |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| private static final int DELAYS_PER_YIELD = 1000000000; private static final int MAX_FRAME_SKIPS = 5; private static final long NANO_IN_MILLI = 1000000L; int fps = 60; long period = 1000L / (long)fps; boolean running = true;
public void start() { new Thread() { public void run() { long overSleepTime = 0L; int noDelays = 0; long excess = 0L; Thread.currentThread().setPriority(10); long beforeTime = System.nanoTime(); long lastRunTime = beforeTime;
while (running) { long delta = System.nanoTime() - lastRunTime; lastRunTime = System.nanoTime(); nextFrame(delta / NANO_IN_MILLI);
render();
long afterTime = System.nanoTime(); long timeDiff = afterTime - beforeTime; long sleepTime = period - timeDiff - overSleepTime; if (sleepTime > 0L) { try { Thread.sleep(sleepTime / NANO_IN_MILLI, (int)(sleepTime % NANO_IN_MILLI)); } catch (InterruptedException interruptedexception) { } overSleepTime = System.nanoTime() - afterTime - sleepTime; } else { excess -= sleepTime; overSleepTime = 0L; if (++noDelays >= DELAYS_PER_YIELD) { Thread.yield(); noDelays = 0; } } beforeTime = System.nanoTime(); int skips; for (skips = 0; excess > period && skips < MAX_FRAME_SKIPS; skips++) { excess -= period; nextFrame(period / NANO_IN_MILLI); } framesSkipped += skips; } System.exit(0); } }.start(); } |
I think the fps is broken, I tried setting it to very low but still keep on sucking up my cpu and rendering fast as possible. You also in your example "framesSkipped += skips" does nothing.
|
|
|
|
Mr_Light
JGO Strike Force    Posts: 893
shiny.
|
 |
«
Reply #6 on:
2009-02-27 10:12:05 » |
|
boolean running - should be volatile
it also seems like an odd place to put System.exit(0)
You also might want to reset interrupt status - though it shouldn't cause problems in this specific context, as you also rely on the running boolean and the contents of the loop should not require much time to execute.
Thread.currentThread().setPriority(10); seems random you might want to make it relative to the calling thread or allow one to set it by exposing it with their value.
|
It's harder to read code than to write it. - it's even harder to write readable code.
The gospel of brother Riven: "The guarantee that all bugs are in *your* code is worth gold." Amen brother a-m-e-n.
|
|
|
Wildern
Full Member   Posts: 140
|
 |
«
Reply #7 on:
2009-02-27 10:26:01 » |
|
I think the fps is broken, I tried setting it to very low but still keep on sucking up my cpu and rendering fast as possible.
You also in your example "framesSkipped += skips" does nothing.
60fps uses less than 10% cpu on my machine (Dual Core 2.6GHz with 3GB RAM). Lowering fps also lowers cpu used. The frameSkipped is left over from stats collection, I pulled out most of the stats collection code to simplify the example and I just missed that bit. boolean running - should be volatile
it also seems like an odd place to put System.exit(0)
You also might want to reset interrupt status - though it shouldn't cause problems in this specific context, as you also rely on the running boolean and the contents of the loop should not require much time to execute.
Thread.currentThread().setPriority(10); seems random you might want to make it relative to the calling thread or allow one to set it by exposing it with their value.
Thanks for the input. Where would you recommend putting the exit? When the user ends my game I set running to false which stops the loop. If I don't call exit, the application stays open.
|
|
|
|
|
damaxxed
JGO n00b  Posts: 11
I ♥ Prototyping
|
 |
«
Reply #8 on:
2009-03-05 18:09:07 » |
|
1 2 3
| Date startTime = new Date(); Date previousRepaint = startTime; Date currentTime = startTime; |
Why are you using Date? Isn't a long for each timestamp enough? Use 1
| long startTime = System.getCurrentMillis(); |
1 2 3
| if(delta <= redrawsPerSecond) { try { Thread.sleep(10); } catch(Exception e) { } } |
I dislike Thread.sleep() because it's very unprecise on my Windows XP machine.. I prefer solutions like this: 1 2 3 4 5 6 7 8 9 10 11 12
| final long LOOPTIME = 1000/60; long nextLoop;
while(true) { nextLoop = System.currentTimeMillis() + LOOPTIME;
while(System.currentTimeMillis() < nextLoop) { Thread.yield(); } } |
|
|
|
|
|
pjt33
JGO Strike Force    Posts: 913 Medals: 17
|
 |
«
Reply #9 on:
2009-03-07 13:01:20 » |
|
I dislike Thread.sleep() because it's very unprecise on my Windows XP machine.. I prefer solutions like this:
That maxes the CPU on my Linux box. I think in general nanoSleep is the best solution, assuming availability of Java 1.5.
|
|
|
|
|
Games published by our own members! Go get 'em!
|
|
damaxxed
JGO n00b  Posts: 11
I ♥ Prototyping
|
 |
«
Reply #10 on:
2009-03-09 03:39:08 » |
|
That maxes the CPU on my Linux box. I think in general nanoSleep is the best solution, assuming availability of Java 1.5.
Yes, it maxes the CPU, but other Threads are able to do their work even with that. Using Thread.sleep(long ms, long ns) isn't accurate, too, afaik.
|
|
|
|
|
pjt33
JGO Strike Force    Posts: 913 Medals: 17
|
 |
«
Reply #11 on:
2009-03-09 05:00:10 » |
|
Yes, it maxes the CPU, but other Threads are able to do their work even with that.
Maybe. I found that one of the games in this year's Java4k competition was almost unplayable until its author changed from yield to sleep, at which point it started actually detecting and handling all of my key presses.
|
|
|
|
|
Gudradain
Sr. Member   Posts: 371 Medals: 8
|
 |
«
Reply #12 on:
2009-03-11 12:37:28 » |
|
Everyone seems to use a game loop with thread.sleep(). Except me  What I did is basically create a repeating timer that call repaint() when it ends and I use paint() as my game loop... If the fps is faster than the Timer it will wait for the timer, if it's slower then the next repaint() will just have to wait. Anything is wrong with that 
|
|
|
|
|
Eli Delventhal
« League of Dukes » JGO Kernel      Posts: 3574 Medals: 44
Game Engineer
|
 |
«
Reply #13 on:
2009-03-12 13:29:11 » |
|
I don't even understand exactly what you're saying, but using the Timer class is acceptable. The main problem with it is that you can't directly control it like you can with the sleeping. So, if you're getting slower or faster FPS you can't really adjust it accordingly. For simple games I think it works fine, but I wouldn't use it for anything that's complex.
|
See my work:OTC Software<br /> Currently Working On:Secret project... I edit JGO in production, because I simply don't waste time writing bugs
|
|
|
Gudradain
Sr. Member   Posts: 371 Medals: 8
|
 |
«
Reply #14 on:
2009-03-12 21:48:50 » |
|
I don't even understand exactly what you're saying, but using the Timer class is acceptable. The main problem with it is that you can't directly control it like you can with the sleeping. So, if you're getting slower or faster FPS you can't really adjust it accordingly. For simple games I think it works fine, but I wouldn't use it for anything that's complex.
After reading it I got scare that I took a wrong way so I did some test to see how I could control the game speed so it match with the FPS. I'm still using a timer and I was able to change the game speed accordingly to the FPS. Here is a sample of my code. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| public class GameFrame extends JFrame{ public static int TIME_DELAY = 10; GamePanel gPanel; Timer timer = new Timer(TIME_DELAY, TimerListener); class TimerListener implements ActionListener{ public void actionPerformed(ActionEvent e){ gPanel.repaint(); } } }
public class GamePanel extens JPanel{ GameFrame gameFrame; protected void paintComponent(Graphics g) { long time0 = System.nanotime(); long time1 = System.nanotime(); int t10 = (int)(time1-time0)/1000000; int timeDelay = setTimeDelay(t10); gameFrame.TIME_DELAY = timeDelay + 2; gameFrame.timer.setDelay(timeDelay); } private int setTimeDelay(int time){ listTimeDelay.addFirst(time); if(listTimeDelay.size() > 10){ listTimeDelay.removeLast(); } int retour = 0; for(int i=0; i<listTimeDelay.size(); i++){ retour += listTimeDelay.get(i); } if(listTimeDelay.size() > 0){ retour = retour/listTimeDelay.size(); } if(retour < 13){ retour = 13; }else if(retour > 30){ retour = 30; } return retour; } } |
Anyone think it would make a problem later? Because if I have to change it I guess it's better now than later.
|
|
|
|
|
Eli Delventhal
« League of Dukes » JGO Kernel      Posts: 3574 Medals: 44
Game Engineer
|
 |
«
Reply #15 on:
2009-03-13 12:44:02 » |
|
That will always be a frame behind, because you're setting the delay for the next frame based on the current frame's fps, and you don't appear to be using a delta variable of any kind. It won't really be very noticeable, but I still don't recommend using Timer.
|
See my work:OTC Software<br /> Currently Working On:Secret project... I edit JGO in production, because I simply don't waste time writing bugs
|
|
|
Gudradain
Sr. Member   Posts: 371 Medals: 8
|
 |
«
Reply #16 on:
2009-04-04 15:19:18 » |
|
Yay I finally made a good game loop (at least I think I do). Any places for improvement? Check it out : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public class GameLoop implements Runnable{ long desiredFPS = 60; long desiredDeltaLoop = (1000*1000*1000)/desiredFPS; long beginLoopTime; long endLoopTime; long currentPhysicTime; long lastPhysicTime; boolean running = true; public void run(){ loopInitialization(); while(running){ beginLoopTime = System.nanoTime(); executeDrawing(); lastPhysicTime = currentPhysicTime; currentPhysicTime = System.nanoTime(); executePhysic(currentPhysicTime - lastPhysicTime); executeLogic(); endLoopTime = System.nanoTime(); adjustSpeed(); } } private void adjustSpeed(){ long deltaLoop = endLoopTime - beginLoopTime; if(deltaLoop > desiredDeltaLoop){ }else{ try{ Thread.sleep((desiredDeltaLoop - deltaLoop)/(1000*1000)); }catch(InterruptedException e){ } } } private void loopInitialization(){ currentPhysicTime = System.nanoTime(); } private void executeDrawing(){ } private void executePhysic(long deltaPhysic){ } private void executeLogic(){ } |
|
|
|
|
|
appel
JGO Wizard     Posts: 1477 Medals: 23
I always win!
|
 |
«
Reply #17 on:
2009-04-06 09:50:12 » |
|
You'll be putting the garbage collector to overdrive, and hence reducing the framerate, if you try to do something like:
while(true) { ... MyObject myObj = new MyObject(); ... }
|
|
|
|
Gudradain
Sr. Member   Posts: 371 Medals: 8
|
 |
«
Reply #18 on:
2009-04-06 13:40:28 » |
|
Hmmm. I don't really get what you are telling me. What object did I create in the loop? I can see that I create 2 or 3 long variable is it what you are talking about?
|
|
|
|
|
appel
JGO Wizard     Posts: 1477 Medals: 23
I always win!
|
 |
«
Reply #19 on:
2009-04-06 15:02:31 » |
|
Hmmm. I don't really get what you are telling me. What object did I create in the loop? I can see that I create 2 or 3 long variable is it what you are talking about?
I was referring to the original post's "currentTime = new Date();"
|
|
|
|
whome
Sr. Member   Posts: 267
Carte Noir Java
|
 |
«
Reply #20 on:
2009-05-06 17:26:44 » |
|
Yay I finally made a good game loop (at least I think I do). Any places for improvement?
I took your mainloop and made a testrun, its very simple left-to-right animation. I have tried everything(?) to make it smooth as silk in a windowed Java application still not eating CPU100%. I think its close to impossible or I just can't. I can cleary see occasional jumps and small tearing. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
| import java.util.*;
import java.awt.Color; import java.awt.Frame; import java.awt.Graphics; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.geom.Rectangle2D; import java.awt.image.BufferStrategy;
import java.awt.DisplayMode; public class GameLoop1 implements KeyListener { Frame mainFrame;
private static final long NANO_IN_MILLI = 1000000L; long desiredFPS = 60; long desiredDeltaLoop = (1000*1000*1000)/desiredFPS;
long beginLoopTime; long endLoopTime; long currentPhysicTime; long lastPhysicTime;
long fps; long frameCounter; long lastFpsTime; Rectangle2D rect; public GameLoop1(Map<String,String> args, GraphicsDevice device) { try { GraphicsConfiguration gc = device.getDefaultConfiguration(); mainFrame = new Frame(gc); mainFrame.setUndecorated(true); mainFrame.setIgnoreRepaint(true); mainFrame.setVisible(true); mainFrame.setSize(640, 480); mainFrame.setLocation(100,100); mainFrame.createBufferStrategy(2); mainFrame.addKeyListener(this);
if ("true".equalsIgnoreCase(args.get("fullscreen"))) { device.setFullScreenWindow(mainFrame); device.setDisplayMode(new DisplayMode(640, 480, 8, DisplayMode.REFRESH_RATE_UNKNOWN)); } BufferStrategy bufferStrategy = mainFrame.getBufferStrategy(); rect = new Rectangle2D.Float(0,100,64,64); currentPhysicTime = System.nanoTime(); lastFpsTime = currentPhysicTime;
while(true) { beginLoopTime = System.nanoTime();
Graphics g = bufferStrategy.getDrawGraphics(); drawScreen(g); g.dispose();
if( !bufferStrategy.contentsLost() ) bufferStrategy.show(); lastPhysicTime = currentPhysicTime; currentPhysicTime = System.nanoTime(); updateWorld(currentPhysicTime - lastPhysicTime); calculateFramesPerSecond(); endLoopTime = System.nanoTime(); adjustSpeed(); } } catch (Exception ex) { ex.printStackTrace(); } finally { device.setFullScreenWindow(null); } } private void adjustSpeed() { long deltaLoop = endLoopTime - beginLoopTime; if(deltaLoop > desiredDeltaLoop) { System.out.println("Late, do not sleep"); } else { try { Thread.sleep((desiredDeltaLoop - deltaLoop) / NANO_IN_MILLI); } catch (InterruptedException ex) { } } }
private void updateWorld(long elapsedTime) { double xMov = (140f/(NANO_IN_MILLI*1000)) * elapsedTime; rect.setRect(rect.getX() + xMov, 100, 64, 64); if( rect.getX() > mainFrame.getWidth() ) rect.setRect(-rect.getWidth(), 100, 64, 64); } private void drawScreen(Graphics g) { g.setColor(Color.BLACK); g.fillRect(0, 0, mainFrame.getWidth(), mainFrame.getHeight()); g.setColor(Color.WHITE); g.drawString("FPS: " + fps, 0, 17); g.setColor(Color.RED); g.fillRect((int)rect.getX(), (int)rect.getY(), (int)rect.getWidth(), (int)rect.getHeight()); } private void calculateFramesPerSecond() { if( currentPhysicTime - lastFpsTime >= NANO_IN_MILLI*1000 ) { fps = frameCounter; frameCounter = 0; lastFpsTime = currentPhysicTime; } frameCounter++; } public void keyPressed(KeyEvent e) { if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) { System.exit(0); } } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { }
public static void main(String[] args) { try { Map<String,String> mapArgs = parseArguments(args);
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice device = env.getDefaultScreenDevice(); new GameLoop1(mapArgs, device); } catch (Exception ex) { ex.printStackTrace(); } }
private static Map<String,String> parseArguments(String[] args) { Map<String,String> mapArgs = new HashMap<String,String>();
for(int idx=0; idx < args.length; idx++) { String val = args[idx]; int delimIdx = val.indexOf('='); if (delimIdx < 0) { mapArgs.put(val, null); } else if (delimIdx == 0) { mapArgs.put("", val.substring(1)); } else { mapArgs.put( val.substring(0, delimIdx).trim(), val.substring(delimIdx+1) ); } } return mapArgs; }
}
|
edit: another issue, my FPS counter clearly does not work. target fps is 60 but my debug prints +90fps all the time.
|
|
|
|
|
Gudradain
Sr. Member   Posts: 371 Medals: 8
|
 |
«
Reply #21 on:
2009-05-10 00:25:16 » |
|
Hello Whome. Thx for making this testrun. To tell the truth when I post it I was hoping someone find a flaw in my design so I could improve it. (It is nearly my first gameloop). I tried you testrun but I couldn't reproduce the jump unless I go to like 2000 fps (that eating nearly all my CPU). But, I see some tearing. I not sure to fully understand what cause tearing in a game but I find this with some research. http://www.gamedev.net/community/forums/topic.asp?topic_id=372033. Looks like you can't remove tearing unless you go fullscreen mode and enable vsync. btw, what is you debug? Your fps counter print around 61 fps for me but you said it doesn't work.
|
|
|
|
|
whome
Sr. Member   Posts: 267
Carte Noir Java
|
 |
«
Reply #22 on:
2009-05-12 15:24:29 » |
|
Upper-left corner FPS counter was my debug value. When I posted my example code it printed run +90 fps but now I run it again. Its a steady 60-63 fps value. Go figure what was wrong last time, strange. So we come to a conclusion fps calc works after all :-) I know and have done few native D3D apps they run fine windowed vsync lock. Its so nice a windowed non-tearing rendering loop, triple buffered flips and low cpu% usage. Here is an another game loop borrowed from a forum post. Loop animation does not rely on a delta time but each updateWorld() call is one animation frame to be simulated. Slower machines drop renderFPS but loop tries to keep up to a updateFPS. I think it is a bit more consistent fps, but tearing is still visible. About "Toolkit.getDefaultToolkit().sync()" method I think we can forget it. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
| import java.util.*;
import java.awt.Color; import java.awt.Frame; import java.awt.Graphics; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.geom.Rectangle2D; import java.awt.image.BufferStrategy;
import java.awt.DisplayMode; public class GameLoop2 implements KeyListener { Frame mainFrame;
private static final long NANO_IN_MILLI = 1000000L;
private static final int NO_DELAYS_PER_YIELD = 16;
private static int MAX_RENDER_SKIPS = 5;
private static int TARGET_FPS = 60; private long gameStartTime; private long curRenderTime; private long rendersSkipped = 0L; private long period; long fps; long frameCounter; long lastFpsTime; Rectangle2D rect; public GameLoop2(Map<String,String> args, GraphicsDevice device) { try { if (args.containsKey("fps")) TARGET_FPS = Integer.parseInt(args.get("fps"));
GraphicsConfiguration gc = device.getDefaultConfiguration(); mainFrame = new Frame(gc); mainFrame.setUndecorated(true); mainFrame.setIgnoreRepaint(true); mainFrame.setVisible(true); mainFrame.setSize(640, 480); mainFrame.setLocation(100,100); mainFrame.createBufferStrategy(2); mainFrame.addKeyListener(this);
if ("true".equalsIgnoreCase(args.get("fullscreen"))) { device.setFullScreenWindow(mainFrame); device.setDisplayMode(new DisplayMode(640, 480, 8, DisplayMode.REFRESH_RATE_UNKNOWN)); } final boolean VSYNC = "true".equalsIgnoreCase(args.get("vsync"));
BufferStrategy bufferStrategy = mainFrame.getBufferStrategy(); rect = new Rectangle2D.Float(0,100,64,64);
long beforeTime, afterTime, timeDiff, sleepTime; long overSleepTime = 0L; int noDelays = 0; long excess = 0L; gameStartTime = System.nanoTime(); beforeTime = gameStartTime; period = (1000L*NANO_IN_MILLI)/TARGET_FPS; System.out.println("FPS: " + TARGET_FPS + ", vsync=" + VSYNC); System.out.println("FPS period: " + period);
while(true) { updateWorld(0);
Graphics g = bufferStrategy.getDrawGraphics(); drawScreen(g); g.dispose();
if (VSYNC) Toolkit.getDefaultToolkit().sync();
if( !bufferStrategy.contentsLost() ) bufferStrategy.show();
afterTime = System.nanoTime(); curRenderTime = afterTime; calculateFramesPerSecond();
timeDiff = afterTime - beforeTime; sleepTime = (period-timeDiff) - overSleepTime; if (sleepTime > 0) { try { Thread.sleep(sleepTime/NANO_IN_MILLI); } catch(InterruptedException ex){} overSleepTime = (System.nanoTime()-afterTime) - sleepTime; } else { System.out.println("Rendering too slow"); excess -= sleepTime; overSleepTime = 0L; if (++noDelays >= NO_DELAYS_PER_YIELD) { Thread.yield(); noDelays = 0; } } beforeTime = System.nanoTime(); int skips = 0; while((excess > period) && (skips < MAX_RENDER_SKIPS)) { System.out.println("Skip renderFPS, run updateFPS"); excess -= period; updateWorld(0); skips++; } rendersSkipped += skips; } } catch (Exception ex) { ex.printStackTrace(); } finally { device.setFullScreenWindow(null); } }
private void updateWorld(long elapsedTime) { double xMov = 140f / (TARGET_FPS); rect.setRect(rect.getX()+xMov, 100, 64, 64); if( rect.getX() > mainFrame.getWidth() ) rect.setRect(-rect.getWidth(), 100, 64, 64); } private void drawScreen(Graphics g) { g.setColor(Color.BLACK); g.fillRect(0, 0, mainFrame.getWidth(), mainFrame.getHeight()); g.setColor(Color.WHITE); g.drawString("FPS: " + fps, 0, 17); g.setColor(Color.RED); g.fillRect((int)rect.getX(), (int)rect.getY(), (int)rect.getWidth(), (int)rect.getHeight()); } private void calculateFramesPerSecond() { if( curRenderTime - lastFpsTime >= NANO_IN_MILLI*1000 ) { fps = frameCounter; frameCounter = 0; lastFpsTime = curRenderTime; } frameCounter++; } public void keyPressed(KeyEvent e) { if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) { System.exit(0); } } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { }
public static void main(String[] args) { try { Map<String,String> mapArgs = parseArguments(args);
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice device = env.getDefaultScreenDevice(); new GameLoop2(mapArgs, device); } catch (Exception ex) { ex.printStackTrace(); } }
private static Map<String,String> parseArguments(String[] args) { Map<String,String> mapArgs = new HashMap<String,String>();
for(int idx=0; idx < args.length; idx++) { String val = args[idx]; int delimIdx = val.indexOf('='); if (delimIdx < 0) { mapArgs.put(val, null); } else if (delimIdx == 0) { mapArgs.put("", val.substring(1)); } else { mapArgs.put( val.substring(0, delimIdx).trim(), val.substring(delimIdx+1) ); } } return mapArgs; }
}
|
|
|
|
|
|
Darrin
Jr. Member   Posts: 92 Medals: 1
|
 |
«
Reply #23 on:
2009-05-12 17:04:04 » |
|
Hiya, I highly recommend looking at Kevin Glass's tutorial. I too use Java2D. Here my loop, set up to run in an applet. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public void run() {
long loopTime = System.currentTimeMillis(); FPS fps = new FPS();
while(bRunning) { long timeLapse = System.currentTimeMillis() - loopTime; loopTime = System.currentTimeMillis(); fps.update();
Graphics2D g2d = (Graphics2D) bufferStrategy.getDrawGraphics();
g2d.setColor(Color.black); g2d.fillRect(0, 0, CANVAS_X, CANVAS_Y);
if (state == GameState.SPLASH){ splash.draw(g2d, highScore); lives = NUMBER_OF_LIVES; }
g2d.drawString("fps: "+fps.getFPS(), 5, 30); drawAndSleep(g2d, loopTime); } }
private void drawAndSleep(Graphics2D g2d, long loopTime){
g2d.dispose();
if(!bufferStrategy.contentsLost()) bufferStrategy.show();
long sleep = loopTime+31-System.currentTimeMillis(); try { Thread.sleep(sleep); } catch (Exception e) {}
} |
|
|
|
|
Gudradain
Sr. Member   Posts: 371 Medals: 8
|
 |
«
Reply #24 on:
2009-05-12 20:43:25 » |
|
Ok I took the 2 examples that you gave me and I remove all the things not related to timing so we could see the differences easily. The one of Whome 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| private static final long NANO_IN_MILLI = 1000000L; private static int MAX_RENDER_SKIPS = 5; private static int TARGET_FPS = 60; boolean running = true; long period = (1000L*NANO_IN_MILLI)/TARGET_FPS; public void run(){
long beforeTime = System.nanoTime(); long afterTime, timeDiff, sleepTime; long excess = 0L;
while(running){ update(); render(); afterTime = System.nanoTime(); timeDiff = afterTime - beforeTime; sleepTime = (period-timeDiff) - excess; if (sleepTime > 0) { try { Thread.sleep(sleepTime/NANO_IN_MILLI); } catch(InterruptedException ex){} excess = (System.nanoTime()-afterTime) - sleepTime; } else { System.out.println("Rendering too slow"); excess -= sleepTime; }
beforeTime = System.nanoTime();
int skips = 0; while((excess > period) && (skips < MAX_RENDER_SKIPS)) { System.out.println("Skip renderFPS, run updateFPS"); excess -= period; update(); skips++; } } } private void render(){ } private void update(){ } |
And the one of Darrin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class ExampleLoopNew { boolean running = true; public void run(){ long loopBeginTime = System.currentTimeMillis(); long loopTimeLapse; while(running){ loopTimeLapse = System.currentTimeMillis() - loopBeginTime; loopBeginTime = System.currentTimeMillis(); render(); long sleep = (loopBeginTime+31) - System.currentTimeMillis(); try { Thread.sleep(sleep); } catch (Exception e) {} } } private void render(){ }
} |
and now mine (with somes modification) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public class ExampleLoopMine { long desiredFPS = 60; long desiredDeltaLoop = (1000*1000*1000)/desiredFPS; boolean running = true; public void run(){ long beginLoopTime; long endLoopTime; long currentUpdateTime = System.nanoTime(); long lastUpdateTime; long deltaLoop; while(running){ beginLoopTime = System.nanoTime(); render(); lastUpdateTime = currentUpdateTime; currentUpdateTime = System.nanoTime(); update(currentUpdateTime - lastUpdateTime); endLoopTime = System.nanoTime(); deltaLoop = endLoopTime - beginLoopTime; if(deltaLoop > desiredDeltaLoop){ }else{ try{ Thread.sleep((desiredDeltaLoop - deltaLoop)/(1000*1000)); }catch(InterruptedException e){ } } } } private void render(){ } private void update(long deltaTime){ }
} |
Anyone can tell what make one better than the other?
|
|
|
|
|
|
|
skinny boy
Jr. Member   Posts: 85
|
 |
«
Reply #26 on:
2009-09-15 16:13:20 » |
|
a lot of familiar code,, finally the studying started to pay off
(though i should stop and start writing some code, otherwise what is the point??)
(hint, Andrew Davison, Killer Game Programming in Java, Chapter 2 )
(not really sure about the chapter)
|
|
|
|
|
|