Fix Your Timestep!

BlitzMax Forums/BlitzMax Programming/Fix Your Timestep!

I have been trying to convert this article to Bmax because I have never used Fixed Logic timing before.
Unfortunately, I'm finding some of the article incoherent so might be missing something. My code doesn't run very smoothly.
First, here's what I understand the example code to be doing with my own comments

    float t = 0.0f;    * Guess this is gametime but don't quite know what it's used for.
    const float dt = 0.01f; * Timestep in seconds? 

    float currentTime = 0.0f; * OK.
    float accumulator = 0.0f; * OK.

    State previous; * Guess in Bmax you have to keep the old positions.
    State current; * and this is the new position.

    while (!quit)
    {
         float newTime = time();  *Guess time() is millisecs()
         float deltaTime = newTime - currentTime; * OK with this..
*** Needed some clamp on deltatime here I assume.
         currentTime = newTime; * OK.

         accumulator += deltaTime;  * OK

         while (accumulator>=dt)
         {
              previousState = currentState;   * old_xpos=new_xpos etc?
              integrate(currentState, t, dt); * no idea 
what this really means but assumed you pass 
your update methods/functions the timestep value. 
Not sure what you use 't' for.
              t += dt;  * Keep a count of 't' but not sure why.
              accumulator -= dt;  * OK.
         }

         const float alpha = accumulator / dt;  * OK

         State state = currentState*alpha + previousState*(1.0f-alpha);   * OK.

         render(state);  *OK
    }

Here is the code I came up with :
SuperStrict
Type ttest1
	Global list:tlist=CreateList()
	Field x:Float = 0.0
	Field y:Float = 100.0
	Field xvel:Float=1.0
	Field previous_x:Float = 0.0
	
	Method New()
		ListAddLast list , Self
	End Method
	
	Function Draw(alpha:Float)
'standard draw without interpolation for comparison.
		For Local all:ttest1 = EachIn list
			DrawOval all.x , all.y , 8 , 8
		Next
	End Function
	
	Function update() 
		For Local all:ttest1=EachIn list
			all.previous_x=all.x	
			all.x=(all.x+all.xvel) Mod GraphicsWidth()
		Next
	End Function
	
End Type

Type ttest2
	Global list:tlist=CreateList()
	Field x:Float = 0.0
	Field y:Float = 200.0
	Field xvel:Float=1.0
	Field previous_x:Float = 0.0
	
	Method New()
		ListAddLast list , Self
	End Method
	
	Function Draw(alpha:Float)
'interpolated draw
		For Local all:ttest2 = EachIn list
			Local xpos:Float = (all.x * alpha) + (all.previous_x * (1.0 - alpha))
			DrawOval xpos , all.y , 8 , 8
		Next
	End Function
	
	Function update(timestep:Float)
		For Local all:ttest2=EachIn list
' saving previous x for interpolation.
			all.previous_x=all.x	
			all.x=(all.x+all.xvel) Mod GraphicsWidth()
		Next
	End Function
	
End Type

Graphics 800 , 600
Local cycletime:Float = 0.0
Const timestep:Float = 10.0   ' run 10ms timesteps?
Local accumulator:Float = 0.0
Local t1:ttest1 = New ttest1
Local t2:ttest2 = New ttest2
Local Current_Time:Float = MilliSecs()

While Not KeyHit(KEY_ESCAPE)
	Local newtime:Float = MilliSecs()
	Local deltatime:Float = newtime - current_time
'clamp deltatime.
	If deltatime > 300.0 deltatime = 300.0
	current_time = newtime
	accumulator:+ deltatime
	While accumulator >= timestep
		update_game(timestep)
		cycletime:+ timestep
		accumulator:- timestep
	Wend
'I have called it cycletime but this is 't' in the article.
'Anybody know what it's for? I zero it to stop it incrementing
'	cycletime=0.0
	Local alpha:Float = accumulator / timestep
	render(alpha)
Wend

Function render(alpha:Float)
	Cls
	ttest1.draw(alpha)
	ttest2.draw(alpha)
	Flip 0
End Function

Function update_game(timestep : Float)
	ttest1.update()
	ttest2.update(timestep)
End Function

Have I missed something?
I realise there are other timing methods available but this is purely to check how fixed rate logic behaves for myself.

copypaste and crippled a bit from my code (left out some specific things)

UpdateMain uses Game.intDeltaTime to calc movement and so on


'--- Variables for compensated timing
Const dt:Float=1.0/60.0
Global CurrTime:Int=0
Global Kompensator:Float=0.0
Global newTime:Int = 0
Global deltaTime:Float = 0.0
'-------------

Repeat
	If CurrTime = 0 Then CurrTime = MilliSecs()
	newTime   = MilliSecs()
	deltaTime = (newTime - CurrTime) / 1000.0
	CurrTime  = newTime
	Kompensator:+ deltaTime

	While(Kompensator >= dt)
		Game.intDeltaTime = (MilliSecs() - Game.IntTimer) / 1000.0
		Game.IntTimer = MilliSecs()            
		UpdateMain() 
		Kompensator :- dt
	Wend
	DrawMain()
Until AppTerminate() Or ExitGame = 1



So "previousState" is missing - no tweening

bye
MB

I'm not good at reading other people's code at the best of times, but your understanding of the article seems right to me, and I can't immediately spot anything wrong in your implementation. Have you tried using something more accurate than milliseconds to see if that makes the difference?

Thanks Michael/Gab,
I was going to try and use this for a high resolution timer.
However, checking Michael's code (which still wasn't too smooth for me although I might have implemented it wrong) led me to this
For some reason this seems to work much better when (if I have read it right) it should be less 'blended'.
SuperStrict
Type ttest1
	Global list:tlist=CreateList()
	Field x:Float = 0.0
	Field y:Float = 100.0
	Field xvel:Float=8.0
	Field pre_x:Float = 0.0
	Method New()
		ListAddLast list , Self
	End Method
	Function Draw(alpha:Float)
		For Local all:ttest1 = EachIn list
'			DrawOval all.x+(all.xvel*alpha),all.y,8,8
			DrawOval all.x,all.y,8,8
		Next
	End Function
	Function update() 
		For Local all:ttest1=EachIn list
			all.pre_x=all.x	
			all.x=(all.x+all.xvel) Mod GraphicsWidth()
		Next
	End Function
End Type
Type ttest2
	Global list:tlist=CreateList()
	Field x:Float = 0.0
	Field y:Float = 200.0
	Field xvel:Float=8.0
	Field pre_x:Float = 0.0
	Method New()
		ListAddLast list , Self
	End Method
	Function Draw(alpha:Float)
		For Local all:ttest2 = EachIn list
			DrawOval all.x+(all.xvel*alpha),all.y,8,8
		Next
	End Function
	Function update()
		For Local all:ttest2 = EachIn list
			all.pre_x=all.x	
			all.x=(all.x+all.xvel) Mod GraphicsWidth()
		Next
	End Function
End Type
Graphics 800 , 600
Local t1:ttest1 = New ttest1
Local t2:ttest2 = New ttest2
Const TICKS_PER_SECOND:Int = 25
Const SKIP_TICKS:Int = 1000 / TICKS_PER_SECOND
Const MAX_FRAMESKIP:Int = 5
Local NEXT_GAME_TICK:Double = MilliSecs()
Local LOOPS:Int
Local INTERPOLATION:Float
While Not KeyHit(KEY_ESCAPE)
	LOOPS = 0
	While (MilliSecs()>NEXT_GAME_TICK And loops < MAX_FRAMESKIP)
		update_game()
		next_game_tick:+ skip_ticks
		loops:+ 1
	Wend
	interpolation=Float(MilliSecs()+SKIP_TICKS-NEXT_GAME_TICK)/Float(skip_ticks)
	render(INTERPOLATION)
Wend
Function render(alpha:Float)
	Cls
	ttest1.draw(alpha)
	ttest2.draw(alpha)
	Flip 0
End Function
Function update_game()
	ttest1.update()
	ttest2.update()
End Function

Also found Beakers interval system which looks funky with things done by events although I've yet to find how to get the remainder for an interpolation.
If anybody else uses, or has checked, fixed logic timesteps then I'd be interested how your code differs.

tonyg,

my version is based on the article in your first post. in the example below two of the three rects are moved by fixed logic timesteps. all move using the same amount.

http://www.winblitz3d.co.uk/FixYourTimestep.zip

kev

Kev, does your code run smoothly?
Is this something you're using which works OK?
How does your code compare to the code I have posted?

TonyG: Hmm, yeah that first one isn't so hot but the last one works well on my system. I used that article to make my framework timer. It uses the fixed rate logic but without the interpolation. BUT what it does is run the fixed rate logic a certain number of times with Delta at 1, then the last loop is run with Delta at an appropriate fraction. Then my game logic used Delta for movements and timers etc and it works really well. Best of both worlds. I don't know if you've seen my framework in action but it's pretty smooth ;-)

To some degree I think all these methods use the 'remainder' whether they call it interpolation, blending or delta. I was just expecting more from the first method as it uses the extra information from the last step.
GA, I've seen the demos from your framework and it does look impressive. If I was interested in writing games I would probably consider getting it but as I simply mess about it's a bit of a waste to have somebody else do the 'messing' for me.
.
Anybody seen anything I've done wrong? Anybody explain what 't' is for in the Fix Your Timestep article?
Anybody got fully working source they're prepared to share so I can check what I'm missing?

I'd like to share my implementation of this but A) Since I'm still running my game engine in a window and only for the editors at the moment, I've no idea if it even works. and B) It's so tightly integrated within my game engine, it would be hard to rip out and even harder to read. When I get a little further along the line and I actually have the game doing something ( ANYTHING! ) then I'd be more than happy to share the implementation ( assuming it works. ) However, that's not likely to be for a while yet.

I haven't looked at Kev's so cannot comment on that.

The problem with Grey's implementation is that his timestep is not fixed, defeating the purpose of the article. In a simple game which doesn't use any physics, this won't matter, but if you do use any physics, it will matter, and it will explode your simulation just as the article describes. I haven't looked at Grey's implementation in any detail, but if you're not going to fix your timestep, I don't see the purpose in doing anything more complicated than delta time anyway. This may simply be shortsightedness on my part, however.

Since T is implemented in exactly the same way in each type of timing Glenn uses, it won't be having an impact on your results. As far as I can tell, and without digging through all my rather complicated code, I'm pretty sure I assumed that T was the total simulation time, as you have.

I'll try to find some time on Monday or Tuesday to have a proper look at your version and see if I can compare it to my own. A perfect game timing system is very necessary, so it would be good for all of us if we can all have a good understanding of it.

TonyG: Fair enough :-)

Gabriel: My code runs at a fixed rate of 200 FPS (obvisouly that's modifiable) and just uses Delta time for a bit of extra smoothing (the remainder). It's based on that article. Maybe you misunderstood the way it works, or I've misunderstood something ;-)

TonyG: Just looked at that link you posted, "The Game Loop", and I agree with why the first 3 are not great, but I also don't like the 4th one (it does have some merits of course). The 4th one uses render tweening and could result in objects being drawn within other objects before collisions are detected - this seems messy to me. Also if you are trying to make an object fall with gravity, the change in y velocity will only occur at 25FPS and the interpolation will NOT use gravity (unless you coded it that way I guess) in between - so I'm not sure it would look that good...but maybe it woudn't be that noticeable.

Probably my 200FPS fixed rate loop is overkill but it does look nice. I've also built in a threshold so that it won't go crazy on slow machines. It's probably not ideal either, I'm not sure there is an IDEAL game loop. Each has pros and cons and maybe it's down to developer preference e.g. do you want it to run on slow PCs, do you want it to look good on normal/fast PCs, how easy is the game to code etc.?

If a PC get's too slow, well maybe it's not suitable to run your game anyway...

tonyg,


does your code run smoothly?
Is this something you're using which works OK?



this is still to be tested. basicly its a wrapped version from the article, so it should function correctly.


How does your code compare to the code I have posted?


it does not, as before mine is a wrapped .cpp and your .bmx

if your intreasted in the code email me and i will send it your way.

*EDIT, this example now includes a simple example of firing a buttet that collides with 2 images. im intrested in the smoothness of this example.

http://www.winblitz3d.co.uk/FixYourTimestep.zip

kev

Gabriel: My code runs at a fixed rate of 200 FPS (obvisouly that's modifiable) and just uses Delta time for a bit of extra smoothing (the remainder). It's based on that article. Maybe you misunderstood the way it works, or I've misunderstood something ;-)


Well the article is not about fixing your frames per second, it's about fixing your timestep. The purpose is so that your physics updates are always fed the same timestep, because doing anything else results in broken, unpredictable physics. In your description above, you said you do a final timestep which is different from the rest, thus you're not using a fixed timestep at all.

So I'm not sure which of us has misunderstood either, but I thought you were describing a system of timing involving a variable timestep, because you do a small timestep instead of a tween.

The 4th one uses render tweening and could result in objects being drawn within other objects before collisions are detected - this seems messy to me.

No it couldn't. You cannot tween forwards, only backwards. So you're tweening back through a physics update you already did, and you didn't hit a collision in your physics update, then you won't hit one by not doing x% of that update.

Also if you are trying to make an object fall with gravity, the change in y velocity will only occur at 25FPS and the interpolation will NOT use gravity (unless you coded it that way I guess) in between - so I'm not sure it would look that good...but maybe it woudn't be that noticeable.


This, however, is quite correct. Your tweening is linear, and will not correspond perfectly with dynamics of whatever kind. I've engaged in long debates on this subject in IRC, and we never did reach complete agreement, but I feel that it won't be noticeable as long as you keep your FPS up. I'm not sure what you mean about only occurring at 25FPS, it will occur as often as you update your physics, but if you were updating your physics at 25FPS, then it might indeed be noticeable that your linear tweening was making things less than smooth. I wouldn't recommend going below 60.

@Kev, that is a lot smoother.
@GA, I understood you were using the delta only in the render phase (e.g. as a type of tween) but it you're using it in the update phase then I can see why you Gabe asked why you don't just use Deltatime.
Anyway, I replaced millisecs() with QueryPerformanceCounter()
which *might* have made the first code look a bit better (my eyes are fried by looking at sodding ovals move across a screen).
SuperStrict
Type ttest1
	Global list:tlist=CreateList()
	Field x:Float = 0.0
	Field y:Float = 100.0
	Field xvel:Float=1.0
	Field previous_x:Float = 0.0
	
	Method New()
		ListAddLast list , Self
	End Method
	
	Function Draw(alpha:Float)
'standard draw without interpolation for comparison.
		For Local all:ttest1 = EachIn list
			DrawOval all.x , all.y , 8 , 8
		Next
	End Function
	
	Function update() 
		For Local all:ttest1=EachIn list
			all.previous_x=all.x	
			all.x=(all.x+all.xvel) Mod GraphicsWidth()
		Next
	End Function
	
End Type

Type ttest2
	Global list:tlist=CreateList()
	Field x:Float = 0.0
	Field y:Float = 200.0
	Field xvel:Float = 0.5
	Field previous_x:Float = 0.0
	
	Method New()
		ListAddLast list , Self
	End Method
	
	Function Draw(alpha:Float)
'interpolated draw
		For Local all:ttest2 = EachIn list
			Local xpos:Float = (all.x * alpha) + (all.previous_x * (1.0 - alpha) )
			For Local loopy:Int = 0 To 200 Step 10
				DrawOval xpos , all.y + loopy, 8 , 8
			Next
		Next
	End Function
	
	Function update(timestep:Float)
		For Local all:ttest2=EachIn list
' saving previous x for interpolation.
			all.previous_x=all.x	
			all.x=(all.x+all.xvel) Mod GraphicsWidth()
		Next
	End Function
	
End Type

Graphics 800 , 600
TCodeTimer.Initialise()

Local timer:TCodeTimer = TCodeTimer.Create()


Local cycletime:Float = 0.0
Const timestep:Float = 5.0   ' run 10ms timesteps?
Local accumulator:Float = 0.0
Local t1:ttest1 = New ttest1
Local t2:ttest2 = New ttest2
timer.StartWatch()
Local Current_Time:Double = timer.watchtime()

While Not KeyHit(KEY_ESCAPE)
'	Local newtime:Float = MilliSecs()
	Local newtime:Double = timer.watchtime()
	Local deltatime:Float = newtime - current_time
'clamp deltatime.
	If deltatime > 50.0 deltatime = 50.0
	current_time = newtime
	accumulator:+ deltatime
	While accumulator >= timestep
		update_game(timestep)
		cycletime:+ timestep
		accumulator:- timestep
	Wend
'I have called it cycletime but this is 't' in the article.
'Anybody know what it's for? I zero it to stop it incrementing
'	cycletime=0.0
	Local alpha:Float = accumulator / timestep
	render(alpha)
Wend

Function render(alpha:Float)
	Cls
	ttest1.draw(alpha)
	ttest2.draw(alpha)
'               Flip -1 works nicely
	Flip 0
End Function

Function update_game(timestep : Float)
	ttest1.update()
	ttest2.update(timestep)
End Function
' classes

Extern "win32"
	Function QueryPerformanceCounter(count:Long Var)
	Function QueryPerformanceFrequency(freq:Long Var)
End Extern

Type TCodeTimer
	Field _start:Long
	Field _stop:Long
	Field _iterations:Int
	Field _totaltime:Double
	
	Method ResetTimer()
		_start 		= 0
		_stop 		= 0
		_iterations 	= 0
		_totaltime 	= 0
	End Method	
	
	Method StartTimer()
		QueryPerformanceCounter(_start)
	End Method
	
	
	Method StopTimer()
		QueryPerformanceCounter(_stop) 
		_totaltime :+ (Double(_stop - _start) / Double(_freq)) 
		_iterations :+ 1
	End Method
	
	
	''' Stopwatch functions
	Field _watchstart:Double
	
	Method StartWatch()
		self._watchstart = QueryCounter()
	End Method
	
	''' returns the number of milliseconds since calling StartWatch()
	Method WatchTime:Double()
		Return QueryCounter() - _watchstart
	End Method 
	
	''' Globals
	
	''' Initialize should be called before first use, to determine timer class overhead
	Global _overhead:Double	
	Global _freq:Long
	
	Function Create:TCodeTimer()
		Return New TCodeTimer
	End Function
	
	Function Initialise()
		QueryPerformanceFrequency(_freq)
		_freq :/ 1000
	End Function
	
	Function QueryCounter:Double()
		Local counter:Long
		QueryPerformanceCounter(counter)
		Return Double(counter) / _freq
	End Function 

End Type


Timing code *IS* bizarre. There must be lots of people happy with whatever method they used.
If there are pros/cons for different types of game then maybe there should be a 'suite' of timing solutions
e.g. set_timing_mode=FIXED_RATE
from simple 'waittimers' to those more difficult to implement.

<edit> Hold on. Other than (probably) a programming glitch causing some extra ovals to be drawn when x reaches graphicswidth() the code I just posted is smooth when I used Flip -1.
I was using Flip 0 to flip as quick as possible.

<edit2> AND it seems to be OK with normal millisecs() counter.

<edit3> Ignore the dodgy ovals drawn. It's simply when x loops t 0 so (x*alpha)+(prev_x*(1.0-alpha)) is not correct.

...until, of course, I try it on another PC and flip -1 causes terrible tearing. Back to the drawing board.

Gabriel: Ah yes I see now. My timestep is ixed for all but the last iteration which is based on delta as you have observed. The article actually says that the objects may be drawn in other objects but I see what you mean. As for 25FPS I was just referring to the timing that the article used.

TonyG: I'm not just using Delta because I wanted a high resolution game logic. Higher than the FPS, and one that remains constant so that collisions etc are always accurate. It really is the same method as fix your timestep except I'm not render tweening I'm using a variable Delta for the last game loop interation. I integrated sswifts sprite and physics code with my framework and that worked fine.

Just have to say a big thankyou to Tonyg - I've had my game on hold for ages thinking it would be impossible to plug in some kind of timing code...

First tried delta timing, but it broke the predictability of my platformer (jumping etc)... However your code worked from the outset with very little work to add it in... Thanks so much!!!!

One thing I noted was you use:

all.x=(all.x+all.xvel)

Wheras I used:

all.x=all.x+( ( all.x - all.pre_x ) * alpha )

Wondered if there was a reason for that (I realise it's functionally the same in your code... But for something more complex?)

Hey that's great news you finally got it sorted.

Got a question though ...

When I introduce random fluctuations in the draw time (simply using delay() ), the smoothness factor drops quite a bit. The logic still feels the same, but the interpolation feels like it's not quite where it should be. In other words, I'm running logic at 60Hz, so sometimes the redraw is occurring only 30 times / second when the delay is introduced, however it should potentially run pretty smooth even in 30fps... So I'm wondering...

Is there something broken with your implementation Tony? Or would it just need some kind of averaging-out or 'anti-jitter'? If so I'm not quite sure where in the code I would implement that.

Lol Grey, are you ever not in the forums? So do you have any idea what I'm talking about? You seem to be really good at this sort of thing...

I didn't do any more work on it but this might help

nm

Hmm... This is what I'm using, based on your second example.

	Const FPS:Int = 60
	Const FRAMELENGTH:Int = 1000 / FPS
	Const MAX_FRAMESKIP:Int = 5
	
	Local nextframetime:Double = MilliSecs()
	Local loops:Int
	Local tween:Float
	
	Repeat

		' THE BIG BIT
		
		loops = 0	
		
		Local ms:Double=MilliSecs()
		
		While (ms>nextframetime And loops < MAX_FRAMESKIP)

			update()
			nextframetime:+ FRAMELENGTH
			loops:+ 1
		Wend
		
		tween=Float(ms+FRAMELENGTH-nextframetime)/Float(FRAMELENGTH)

		Cls
		
		draw(tween)

                flip (1)

       Until 0


So I figure it happily flies when there's more draw() than update() called (the tween definitely works)...

But even when I force it to 30 fps draw() by using delay 17 (just over the length of one frame) it seems jerky. I know it's possible to be smooth in 30fps, so I'm wondering what could be causing it.

Hmm... The never-ending quest for smoothness continues.

BTW How do I post a codebox?

You use [] with the word CODE in it. You can also use QUOTE like I did here. Finish it off with another [] with /CODE or /QUOTE
[quote ]This is a quote[/quote ]
[code ]Some code.[/code ]


I added spaces between the text and the ]
otherwise it would be interpreted.

can you post some demo code of the jittering problem? Probably you just need some jitter correction code. Thing is if you start using timing code, it will never lopk as good as when you had none and locked to 60Hz BUT timing code is a neccessity for any pro game.

And yeah, I probably check the forum too often. I only check a couple of areas of interest tho so it's pretty quick.

Hehe Jake I was only teasing... I think it's great! You've helped me out plenty, and you probably learn a lot yourself.

Anyway, the code I posted above is taken exactly from my game. The thing I'm not sure about is how Tony came from this:

         float newTime = time();  
         float deltaTime = newTime - currentTime; 
         currentTime = newTime; * OK.

         accumulator += deltaTime;  * OK

         while (accumulator>=dt)
         {
              previousState = currentState; 
              integrate(currentState, t, dt); 
              t += dt; 
              accumulator -= dt;  * OK.
         }


to this:

		loops = 0	
		
		Local ms:Double=MilliSecs()
		
		While (ms>nextframetime And loops < MAX_FRAMESKIP)

			update()
			nextframetime:+ FRAMELENGTH
			loops:+ 1
		Wend



The original author seems to be doing something extra in the update() phase - some kind of integration? Or am I being stupid?

the original author was calling their update function Integrate, that's all. They were passing in more variables because it's a physics sim.

Does making FRAMELENGTH a float and doing 1000.0/FPS help?

@Sonic
The second post I made was with code taken from a different article from the first.
First code taken from here
Second code taken from here

So with the interest in my game on the forum, I thought I'd revive this thread to see if anyone can help me with my last stumbling block.

As I mentioned before I'm happily using the frameskip + tweening code at frame rates higher than the 60hz my logic runs at. However, when I introduce a delay of 17ms to ensure that it runs at under 60hz (and the nearest would be 30hz) it seems to jerk nastily. This is my code:


	Const FPS:Int = 60
	Const FRAMELENGTH:Int = 1000 / FPS
	Const MAX_FRAMESKIP:Int = 5
	
	Local nextframetime:Double = MilliSecs()
	Local loops:Int
	Local tween:Float

	Repeat

		' THE BIG BIT
		
		loops = 0	
		
		Local ms:Double=MilliSecs()
		
		While (ms>nextframetime And loops < MAX_FRAMESKIP)
			all.update()
			nextframetime:+ FRAMELENGTH
			loops:+ 1
		Wend
		
		tween=Float(ms+FRAMELENGTH-nextframetime)/Float(FRAMELENGTH)

		Cls
		
		all.draw(tween)

	Until 0	


Why do you want to limit it to 30FPS, it will look better at 60...?

No that's not it Grey - I'd like it to be runnable on machines that can only render at 30fps, hence introducing an artificial delay to test it.

Ah I see, OK got it. Hmm I'm not really an expert on this sort of tweening code. Hope you can sort it.

Does this code help any?

Sonic - Could you explain your main loop please? I've never seen a loop like that before? I can see the update being called as needed, but the tween value you pass to the draw routine confuses me, because the draw routine should just draw objects at their current position unless your draw routine is also moving objects based on the tween value passed? Thanks!

That's what tweening does, it's a drawing thing. It doesn't move them, it interpolates between current and past (or maybe future) positions based on the tween and draws it there. It complicates drawing routines though which is why I didn't bother with tweening.

I can see the update being called as needed, but the tween value you pass to the draw routine confuses me, because the draw routine should just draw objects at their current position unless your draw routine is also moving objects based on the tween value passed?

That's right. The tween value is between 0.0 and 1.0 and represents how much of the last update you *should* have done. You only ever update in full steps, so your objects get ahead of themelves. The tween value is used in the draw function to linear interpolate between the position, scale, orientation, color, etc before the last update and the value after the last update.

It complicates drawing routines though which is why I didn't bother with tweening.

a little but it's well worth it! I tweened all my Blitz3d stuff (using Marks code) and you could really tell the difference. My Invaders from Space looked 10 times better on a larger range of machines after I started tweening it. Most recommended. press the function keys in that demo to really see it in it's glory.

[edit] slowmo is a great tool, it allows you to see the animation defects that need to be fixed like with the little guy...

Yeah but I did logic tweening instead so same difference and I have a slow mo mode too, I use it ALL the time to check out the effects I'm programming.

oh I know... and you get slowmo with deltatime too. I just was mentioning it because I think the tweened slowmo looks smoother than any of the other methods.

Yeah actually the beauty is it doesn't complicate drawing at all. All that's needed is an additional 'draw_x= ( current_x * tween ) + ( new_x * (1.0 - tween) )' etc. All my graphics derive from a bass class anyway, so it only needed to change one routine, rather than add delta time to all logic.

Still... something is slightly broken!

I will have a look at that code, Tony, hopefully I can spot a difference.

I converted the whole Gaffer post about 4 months ago, including the RK4 (integrator function..etc) and it works silky smooth. If anyone is interested I can post it.

Here's part of it (non-RK4). Supply your own ball.png.

Press the Space Bar to see the Tween in action.

Part 1 (main.bmx):
'FRLRT

SuperStrict

Import "Vec3.bmx"

AppTitle$ = "Fixed Rate Logic with Render Tweening"

Graphics 800,600

Local newTime:Int = MilliSecs()
Local oldtime:Int = newTime
Local dt# = 1.0/100.00	'100 FPS logic
Local deltaTime#, accumulator#, alpha#, t#

Local ball:TBody = CreateBody()
ball.current.vPos.x = 400
ball.current.vPos.y = 500
ball.current.vVel.Set(500,-500,0)

SetBlend ALPHABLEND

Global imgBall:TImage = LoadImage("ball.png")
MidHandleImage imgBall

Global RenderTween:Int = False

While Not KeyHit(KEY_ESCAPE)
Cls

	newTime = MilliSecs()
	deltaTime# = (newTime - oldTime) * 0.001
	oldTime = newTime
	
	accumulator :+ deltaTime

	While accumulator >= dt
	
		'Apply some gravity
		ApplyLinearForce ball, 0, ball.fMass * 981.0, 0

		ball.Logic(dt)
		
		accumulator :- dt
		't :+ dt
	Wend

	alpha = accumulator / dt
	
	ball.Render(alpha)

	If KeyHit(KEY_SPACE) Then RenderTween = 1 - RenderTween
	DrawText "Press the SPACE BAR to turn on render tweening mode.",5,5
	DrawText "RenderTween: "+RenderTween,5,50

Flip 1     'Eliminates Tearing
Wend
End


Type TBody
	Field fMass:Float = 1.0
	Field current:TState = New TState
	Field previous:TState = New TState
	
	Method Logic(dt#)
		
		'Basic Linear Physics
		self.previous = self.current.copy()
		self.current.vAcc = self.current.vForces.DividedByScalar(self.fMass)
		self.current.vVel = self.current.vVel.Plus(self.current.vAcc.TimesScalar(dt))
		self.current.vPos = self.current.vPos.Plus(self.current.vVel.TimesScalar(dt))
		self.current.vForces.Zero()
		
		'Simple Collision
		If self.current.vPos.y <= 0 Or self.current.vPos.y >= 600
			self.current.vVel.y = -self.current.vVel.y
		EndIf
		If self.current.vPos.x <= 0 Or self.current.vPos.x >= 800
			self.current.vVel.x = -self.current.vVel.x
		EndIf

	End Method
	
	Method Render(alpha#)
		Local x#, y#
		If RenderTween
			x# = self.current.vPos.x * alpha + self.previous.vPos.x * (1.0 - alpha)
			y# = self.current.vPos.y * alpha + self.previous.vPos.y * (1.0 - alpha)
		Else
			x = self.current.vPos.x
			y = self.current.vPos.y
		EndIf
		DrawImage imgBall, x, y
	End Method

End Type

Function CreateBody:TBody()
	Return New TBody
End Function

Type TState
	Field vPos:TVec3 = Vec3()
	Field vVel:TVec3 = Vec3()
	Field vAcc:TVec3 = Vec3()
	Field vForces:TVec3 = Vec3()

	Field vAngAcc:TVec3 = Vec3()
	Field vAngVel:TVec3 = Vec3()
	Field vMoments:TVec3 = Vec3()

	Method Copy:TState()
		Local t:TState = New TState
		t.vPos = self.vPos.Copy()
		t.vVel = self.vVel.Copy()
		t.vAcc = self.vAcc.Copy()
		Return t
	End Method
End Type

Function ApplyLinearForce(body:TBody, xf#, yf#, zf#)
	Local vF:TVec3 = Vec3(xf, yf, zf)
	body.current.vForces = body.current.vForces.Plus( vF )
End Function


Part 2 (Vec3.bmx):

Rem
bbdoc:
EndRem
Type TVec3
	Field x:Float, y:Float, z:Float

	Method Copy:TVec3()
		Return Vec3(self.x, self.y, self.z)
	End Method

	Method CrossProduct:TVec3(v:TVec3)
		Local res:TVec3 = New TVec3
		res.x =  self.y * v.z  -  self.z * v.y
		res.y = -self.x * v.z  +  self.z * v.x
		res.z =  self.x * v.y  -  self.y * v.x
		Return res
	End Method

	Method DividedBy:TVec3(v:TVec3)
		Local res:TVec3 = New TVec3
		res.x = self.x / v.x
		res.y = self.y / v.y
		res.z = self.z / v.z
		Return res
	End Method

	Method DotProduct:Float(v:TVec3)
		Return self.x * v.x + self.y * v.y + self.z * v.z
	End Method

	Method DividedByScalar:TVec3(divisor#)
		Local res:TVec3 = New TVec3
		res.x = self.x / divisor
		res.y = self.y / divisor
		res.z = self.z / divisor
		Return res
	End Method
	
	Method Inverse:TVec3()
		Local v:TVec3 = New TVec3
		v.x = -self.x
		v.y = -self.y
		v.z = -self.z
		Return v
	End Method

	Method Magnitude:Float()
		Return Sqr(self.x * self.x + self.y * self.y + self.z * self.z)
	End Method
	
	Method Minus:TVec3(v:TVec3)
		Local res:TVec3 = New TVec3
		res.x = self.x - v.x
		res.y = self.y - v.y
		res.z = self.z - v.z
		Return res
	End Method
	
	Method Normalize()
		Local m# = self.Magnitude()
		self.x :/ m
		self.y :/ m
		self.z :/ m
	End Method

	Method Plus:TVec3(v:TVec3)
		Local res:TVec3 = New TVec3
		res.x = self.x + v.x
		res.y = self.y + v.y
		res.z = self.z + v.z
		Return res
	End Method

	Method Set(x#, y#, z#)
		self.x = x
		self.y = y
		self.z = z
	End Method
	
	Method Show(cap:String, xpos:Int, ypos:Int, vspc:Int = 12)
		DrawText cap+".x=" + self.x,xpos,ypos
		DrawText cap+".y=" + self.y,xpos,ypos + vspc * 1
		DrawText cap+".z=" + self.z,xpos,ypos + vspc * 2
	End Method

	Method Times:TVec3(v:TVec3)
		Local res:TVec3 = New TVec3
		res.x = self.x * v.x
		res.y = self.y * v.y
		res.z = self.z * v.z
		Return res
	End Method

	Method TimesScalar:TVec3(scalar#)
		Local res:TVec3 = New TVec3
		res.x = self.x * scalar
		res.y = self.y * scalar
		res.z = self.z * scalar
		Return res
	End Method

	Method Zero()
		self.x = 0
		self.y = 0
		self.z = 0
	End Method

End Type

Rem
bbdoc:
EndRem
Function Vec3:TVec3(x#=0, y#=0, z#=0)
	Local v:TVec3 = New TVec3
	v.x = x
	v.y = y
	v.z = z
	Return v
End Function


Hmmm, well I've double-checked your code Tony and it seems to behave identically to mine... probably because mine was taken from yours in the first place!

So I still have the problem of jerkiness when the graphics take more than one logic frame to redraw. I need to solve this to continue, but I'm at a loss.

Perhaps it's a problem with my introducing a Delay (17) artificially, but I don't have a slow machine to test on, and I can't think of any other way to force the redraw to run at half the logic rate.

[edit] Oh and Chroma - I can't really follow your code, even though it's well written! The main stuff seems to be the same, but could you explain the integration stuff to me?

There are posts which discuss the jerkiness and GA created a test to even it out.

Seems like the code tony posted only runs smooth on my machine if it renders at refresh (vsync) speed. If you add delays so it skips vsyncs, you get the normal jerkiness.

If your game misses a vsync or 2, once in a while, you will get jerkiness, that's 100% unavoidable. The trick is to "do something" to stablize the frame rate or rendering so it's not so obvious when it happens. Averaging the delta over a period of frames can help, but it doesn't 100% get rid of it. What I do is give an option to the end user to run the game at a slower render speed. Sometimes it's better to run at a stable slower frame rate compared to the render loop speed being chaotic.

Yep, that was the point of the post as I didn't understand why it wasn't smooth. Since then hrdnutz posted this with a code archive and GA posted this .
@JGOware, did you expand your code from this ?

option to the end user to run the game at a slower render speed.
How did you do that?

but I don't have a slow machine to test on, and I can't think of any other way to force the redraw to run at half the logic rate.

write a program that will take a lot of cpu time, allow control of that with a press of a button. now run that in the background when testing your app.

@Tonyg - Yes. I actually use two different timing options in my game engine. Dellta timing with averging or the more simplistic option you linked to. I choose which timing method I need based on the game type. For games that you pretty much need to run at full vsync all the time, I prefer the more simplistic timing method and if the end user can't run it full vsync, then they can turn some graphics options on/off render in lower resolutions, etc, which should allow it to run at 1 vsync.

@GA - Nothing techy, just using millisecs() to loop a bit at the end of the render loop until the requested frame rate has passed. The user can attempt to run the game at 20,30,50,60 if desired. Logic still happens at regular speed so you don't get slow down. But ofcourse objects will move greater distances at the slower frame rates.

Hmm, I'm stumped, running a heavy program in the background produces the same effect as introducing a delay (ie stuttering). Unless I can figure out how to smooth out logic at 60hz and a frame rate below at, I can't realistically continue.

I think you need to post the smallest demo which shows the problem.

As long as your internal logic is accurate, 99.99% of your end users won't care if the game jitters once in a while. Unless ofcourse your delay introduces some weird error in your logic handling? Otherwise, welcome to the world of PC coding, where the fix for the jitters is an endless quest. At some point you just have to think "that's good enough..." and move on.

JGOware: OK thx.

Yeah post a demo, my PC never stutters on anything so we'll be able to find out if it's your PC or your code.
However it could just look bad simply BECAUSE it's at 30 fps. If the stuttering is regular and looks crap it's just due to the update being less that the refresh rate (like Amiga games running at 25Hz on a 50Hz TV (in UK)). If it's irregular then it's another problem...

I just wanted to explain what the alpha(tween) variable is.

alpha = accumulator / dt

Alpha is the amount of interpolation between the previous frame and the current frame. It is a value between 0 and 1. 0 means that the value is fully set to the previous state. 1 means that the value is fully set to the current state. So a .5 would set the value halfway between the value of the previous state and the current state.

previous = 10
current = 20

alpha of 0 means that the value of 10 will be used.
alpha of 1 means that the value of 20 will be used.
alpha of 0.5 means that the value of 15 would be used.

A lot may already know this but for those that don't, it helps to be able to envision exactly what it's doing.