|
superflat
|
 |
« on: June 21, 2009, 02:53:11 AM » |
|
I find this a tricky but interesting topic in coding. If anyone would be kind enough to share their timing code and talk a little about what it does, I think it could be really helpful for not just me buy others learning to code.
Mine's quite a mess, so I don't think it's worth sharing. Quite often I've done no timing code at all!
I'm mainly interested in fixed-logic step code, as I make mostly 2d games and want them to be deterministic, but I'm keen to find out ways of combining this with tweening etc.
Thanks!
|
|
|
|
|
Logged
|
|
|
|
|
BorisTheBrave
|
 |
« Reply #1 on: June 21, 2009, 04:15:57 AM » |
|
By timing, you mean handling the rate at which your game runs, I take it? Flash AS3, from some of my general library code. Variable step - locked to display update (onEnterFrame event) var newTime:Number = getTimer()/1000; var dt:Number = (newTime - _realTime); _realTime = newTime; dt = alterDt(dt); step(dt); alterDt is a function for editing the flow of time. Usually it's just the identity, but playing with it allows pausing and bullet time. It also includes a cap so that a step is never larger than 0.1s, even if the framerate has dropped that far. You can also replace it with a constant function to get fixed steps, so I think this is basically as generic as it gets. But maybe other posters will show me something I'm missing.
|
|
|
|
|
Logged
|
|
|
|
|
William Laub
|
 |
« Reply #2 on: June 21, 2009, 05:41:22 AM » |
|
#define FRAMERATE 16 ... long timeStart = 0; //Starting Time in milliseconds long timeEnd = 0; //Ending Time in milliseconds ... timeStart = SDL_GetTicks(); ... timeEnd = SDL_GetTicks(); if(timeEnd-timeStart < FRAMERATE) SDL_Delay(FRAMERATE - (timeEnd-timeStart));
|
|
|
|
|
Logged
|
|
|
|
|
increpare
Guest
|
 |
« Reply #3 on: June 21, 2009, 07:32:36 AM » |
|
Seems to be getting off to a better start than when I tried to do one, heh. here're the haskelified SDL timer functions I'm using in a project at the moment (I wrote out unidiomatic C-like timer code and my collaborator sexed them up): sdlTicks = SDL.getTicks -- Rename for brevity
{- Timer API -} data Timer = Stopped | Started Word32 | Paused Word32
startTimer, stopTimer, pauseTimer, unpauseTimer :: Timer -> IO Timer
{- Timer API implementation -} startTimer _ = (Started <$> sdlTicks)
stopTimer _ = return Stopped
pauseTimer (Started s) = Paused <$> subtract s <$> sdlTicks pauseTimer t = return t
unpauseTimer (Paused p) = Started <$> subtract p <$> sdlTicks unpauseTimer t = return t
getTicks :: Timer -> IO Word32 getTicks (Started s) = subtract s <$> sdlTicks getTicks (Paused p) = return p getTicks Stopped = return 0
[the main timer loop just looks like gottenticks <- getTicks curtimer if gottenticks < (div 1000 framesPerSecond) then do delay (div 1000 framesPerSecond - gottenticks) else return()
and isn't too special]
|
|
|
|
|
Logged
|
|
|
|
|
Average Software
|
 |
« Reply #4 on: June 21, 2009, 08:28:37 AM » |
|
-- The main game loop. separate (Main_Loop) function Main return Boolean is -- The potential next state. Next_State: Logic_Access := State; -- The current time. Now: Time := Clock; -- The amount of time that has passed since -- the last frame. Interval: Duration := Now - Last_Time; begin -- Increase the time delta. Tickables.Time_Delta := Tickables.Time_Delta + Float(Interval); -- Update the old time. Last_Time := Now;
-- Advance the frame timer. Timer := Timer - Interval;
-- If it has expired... if Timer <= 0.0 then -- Reset it. Timer := Frame_Rate; else -- Otherwise, it is not time to process the frame yet. return True; end if;
-- Draw the screen. Renderer.Draw; -- Process the logic. State.Process(Next_State); -- Reset the time delta. Tickables.Time_Delta := 0.0;
-- If the logic state has changed... if Next_State /= State then -- Release the old state. Free(State); -- Copy the new state. State := Next_State; end if;
-- If the new state is null, the logic -- has requested a quit. if State = null then -- Destroy the main window. Main_Window.Destroy;
return False; end if;
return True;
exception when Error: others => Main_Window.Error_Box(Exception_Name(Error) & ": " & Exception_Message(Error)); Main_Window.Destroy;
return False; end Main; This is sort of interesting because I ran into the problem that if I let the game loop just run as fast as it can, it seems to kill the Gtk event system. My timing is therefore locked to a max of 120 FPS (that's what the Timer variable is tracking) but it still employs a delta system to allow it to run slower if it has to. It's sort of a hybrid.
|
|
|
|
|
Logged
|
|
|
|
|
mcc
|
 |
« Reply #5 on: June 21, 2009, 08:41:21 AM » |
|
Hm... well: while ( 1 ) { { sdlTime = SDL_GetTicks(); int timesince = sdlTime-lastSdlTime; if (timesince > TPF) { display(); lastSdlTime = sdlTime; } else if (timesince < TPF-1) { msleep(TPF-1-timesince); } }
// (HANDLE SDL EVENTS) }
TPF is somewhere defined as (1000/FPS). ... I actually agonize over this code. One important but nonobvious thing for Jasper to note here is the presence of the msleep() (SDL_Delay() in Gold Cray's very similar code). If you don't do something like this, your game will take up 100% CPU, screwing up laptop battery life and annoying people who run the game in the background. Two problems I see with my code here: - I do no kind of dynamic framerate; TPF never changes and so can't adjust to processor load, vsync/screen refresh constraints, etc. - I've never been able to decide if waiting the full time between framedraws before processing events is a good idea, since some events might for example trigger a sound effect. I originally had it set up to repeatedly sleep in 1 msec intervals until it was time to display a frame, but this didn't seem to actually make any noticeable difference so I eventually removed it. Currently I do intentionally wake up an msec or so early and just spin in place until it's time to draw, but I'm not completely sure this makes a difference either... maybe I'm overthinking this.
|
|
|
|
« Last Edit: June 21, 2009, 08:44:52 AM by mcc »
|
Logged
|
|
|
|
|
Martin 2BAM
|
 |
« Reply #6 on: June 21, 2009, 09:21:22 AM » |
|
In SFML is as easy as it can get  sf::RenderWindow wnd(/*...startup params...*/); wnd.SetFramerateLimit(60); //optional: Set FPS for fixed frame steps
while(wnd.IsOpened()) { float elapsed=wnd.GetFrameTime(); //returns the number of seconds since //the last frame, with decimals. };
BUTThis doesn't contemplate pausing. I must agree with BorisTheBrave post, as he has made a really good synopsis. Fixed frame times can help you with collision detection, as they asure you that nothing will do the "pass thru" thing, as long as you apply velocity limits. Also, this setting will allow a proper non-real-fps dependent gameplay. Non fixed frame times can make everything look smoother in computers that can tolerate it, and choppyer in slower computers (keeping propper timing). Also they are good for audio synchronization on slower pcs. Some collision & response engines, as Box2D, need a moreless fixed frame rate, so if you plan on using it or some other engine... read ahead to avoid wasting your time. Nevertheless I do recommend to prepare everything for non-fixed frame steps (i.e. Take the frame delta time as a variable/parameter to any physics processing, timed events, etc.). If afterwards you want a fixed timestep, you can easily call all this functions with a constant number (like 1sec/FPS), and it will also work. Regards -Martín 
|
|
|
|
|
Logged
|
|
|
|
|
Draknek
|
 |
« Reply #7 on: June 21, 2009, 10:46:10 AM » |
|
My current approach: const int step = 20; // ms const int MAX_UPDATES = 5;
int previousTime = SDL_GetTicks(); int unused = step;
// Update/render loop. while (true) { int i = 0; while (unused >= step && i < MAX_UPDATES) { i++; // Check for input. Input::checkEvents();
// Update the scene. update(); unused -= step; }
// Draw the scene. draw(); int now = SDL_GetTicks(); int delay = step - unused - (now - previousTime); if (delay > 0) { SDL_Delay(delay); } now = SDL_GetTicks(); unused += now - previousTime; previousTime = now; } The variable unused counts the amount of time that hasn't been simulated yet. Every time update is called, unused is decremented by the length of a frame. MAX_UPDATES limits the number of times you can call update before you draw the frame. But if update is likely to take longer than draw, it should probably just be set to 5.
|
|
|
|
|
Logged
|
|
|
|
|
Overkill
|
 |
« Reply #8 on: June 21, 2009, 10:49:25 AM » |
|
My timer code: const int TIMER_FAST_MULTIPLIER = 4; const int TIMER_SLOW_DIVISOR = 4; const int TIMER_MAX_FRAME_GAP_DEFAULT = 5; enum TimerSpeed { TimerSpeedNormal, TimerSpeedFastForward, TimerSpeedSlowMotion };
Timer::Timer() { reset(); }
void Timer::reset() { speed = TimerSpeedNormal; maxFrameGap = TIMER_MAX_FRAME_GAP_DEFAULT; time = SDL_GetTicks(); tickLast = time; secondLast = tickLast; gap = 0; fps = 0; updateCount = 0; }
void Timer::processInput(Input& fastForward, Input& slowMotion) { if(fastForward.isPressed()) { speed = TimerSpeedFastForward; } else if(slowMotion.isPressed()) { speed = TimerSpeedSlowMotion; } else { speed = TimerSpeedNormal; } }
void Timer::update() { time = SDL_GetTicks(); updateCount++; if(time - secondLast > 999) { fps = updateCount; updateCount = 0; secondLast = time; } if(time - tickLast > 0) { switch(speed) { case TimerSpeedFastForward: gap = time / (10 / TIMER_FAST_MULTIPLIER) - tickLast / (10 / TIMER_FAST_MULTIPLIER); break; case TimerSpeedNormal: gap = time / 10 - tickLast / 10; break; case TimerSpeedSlowMotion: gap = time / (10 * TIMER_SLOW_DIVISOR) - tickLast / (10 * TIMER_SLOW_DIVISOR); break; } if(speed != TimerSpeedFastForward) { gap = min(maxFrameGap, gap); } tickLast = time; } else { gap = 0; } }
Then my gameloop (simplified greatly for instructional purposes): while(!done) { // Draw to the screen Render(); // Update the timer information. timer.processInput(key[KEY_TILDE], key[KEY_1]); timer.update(); // Step through each frame of game logic, until we catch up. // Each update thing does exactly one tick of game logic. // And my timer resolves in centiseconds. for(i = 0; i < timer.gap; i++) { Update(); } }
|
|
|
|
« Last Edit: June 21, 2009, 11:00:31 AM by Overkill »
|
Logged
|
|
|
|
|
ஒழுக்கின்மை
|
 |
« Reply #9 on: June 21, 2009, 11:36:11 AM » |
|
game maker doesn't really use timing code, it has in-built systems for that like alarms, but this comes close: global.rotation = rotate(room_speed, 1); global.rotation_square = sqr(global.rotation); global.rotation_cube = power(global.rotation, 3);
global.rotation_slow = rotate(room_speed*2, 2); global.rotation_slower = rotate(room_speed*5, 3); global.rotation_slowest = rotate(room_speed*10, 4); global.rotation_superslow = rotate(room_speed*30, 5); global.rotation_millenium = rotate(room_speed*60, 6); global.rotation_era = rotate(room_speed*120, 7); global.rotation_aeon = rotate(room_speed*300, 8);
global.rotation_fast = rotate(room_speed/2, 9) global.rotation_faster = rotate(room_speed/3, 10); global.rotation_fastest = rotate(room_speed/5, 11); global.rotation_superfast = rotate(room_speed/10, 12);
the rotate() function is this: if (going_up[argument1]) then rotation[argument1] += 1/argument0; else rotation[argument1] -= 1/argument0; if (rotation[argument1] >= 1) { going_up[argument1] = NO; rotation[argument1] = 1; } if (rotation[argument1] <= 0) { going_up[argument1] = YES; rotation[argument1] = 0; } return(rotation[argument1]); global.rotation_up += 1/40; if (global.rotation_up > 1) global.rotation_up = 0; global.rotation_down = 1 - global.rotation_up;
global.rotation_slowup += 1/270; if (global.rotation_slowup > 1) global.rotation_slowup = 0; global.rotation_slowdown = 1 - global.rotation_slowup;
global.rotation_pathup += 1/112; if (global.rotation_pathup > 1) global.rotation_pathup = 0; global.rotation_pathdown = 1 - global.rotation_pathup;
global.rotation_fastup += 1/20; if (global.rotation_fastup > 1) global.rotation_fastup = 0; global.rotation_fastdown = 1 - global.rotation_fastup; global.rotation_sin = abs(sin(global.steps/60)); global.rotation_cos = abs(cos(global.steps/60)); global.rotation_tan = minmax(0, 1, abs(tan(global.steps/60)));
global.rotation_invsin = 1 - global.rotation_sin; global.rotation_invcos = 1 - global.rotation_cos; global.rotation_invtan = 1 - global.rotation_tan; i then use these globals for various timing things
|
|
|
|
|
Logged
|
|
|
|
|
yesfish
Guest
|
 |
« Reply #10 on: June 21, 2009, 04:07:39 PM » |
|
Check this article out: http://dewitters.koonsolo.com/gameloop.htmlIt has a range of game loops which I've found very useful. My code is basically the one before last but with Bullet SDK's stepSimulation stuck with another fixed step. Since bullet interpolates with motionstates for me there's no need to manually deal with it. while(loops < frameskip) { if(accumulator >= dt) { accumulator -= dt; dynamicsWorld->stepSimulation(fps, 10); } if(lacc >= ldt) { lacc -= ldt; // messagepump and logic here. } }
|
|
|
|
|
Logged
|
|
|
|
|
Anthony Flack
|
 |
« Reply #11 on: June 21, 2009, 05:38:18 PM » |
|
I always found delta time such a pain to use.
With most of the stuff I've done (2d games) rendering is far more time-consuming than logic, so I tend to just run the logic at a higher rate and render a frame when appropriate.
|
|
|
|
|
Logged
|
|
|
|
|
|
|
Klaim
|
 |
« Reply #13 on: June 22, 2009, 12:52:56 AM » |
|
I use an unusual mix. The rendering systems (audio & graphics) and the input systems runs as fast as they can (like in the http://dewitters.koonsolo.com/gameloop.html article). The game state is totally managed in another thread where I have a loop that is timed to 30fps. The game state changes are sent via network - locally. The View system (all that is perception of the game) will receive the messages as a client and display the representation of the game state - as fast as possible and with graphical and audio effects. The thing that makes it work is that there is no physics in my game. It's like if a chess game was plying on one thread and the "client" or "view" thread was getting informations from it and displays it (and send user actions). That's easier than if the game elements had some kind of physics. To get time, I use the most precise time provider available in my code, that is the Ogre::Timer class (you can get the sources from ogre3d.org - one implementation per platform). Additionnally I use some systems I wrote to manage different "clocks" that can have different time flow factors. The code is open sourced and available starting there.
|
|
|
|
|
Logged
|
|
|
|
|