Fixed Steps and Tweening

BlitzMax Forums/BlitzMax Programming/Fixed Steps and Tweening

HrdNutz(Posted 2007) [#1]
Made a reply to another post, and thought maybe this could be useful to some who won't get to look there. Example code of fixed logic steps running at low frequency and motion interpolation to make it smooth.

Enjoy if you find this useful :P

Graphics(640, 480, 32)

Global UPDATE_FREQUENCY = 10 ' times per second
Global update_time = 1000 / UPDATE_FREQUENCY
Global t , dt , execution_time = 0

' boucing ball
Global X# , oldX#
Global dirX# = 1

t = MilliSecs()
While Not KeyDown(KEY_ESCAPE)
	dt = MilliSecs() - t
	t = MilliSecs()

	execution_time:+ dt
 
	' fixed interval update loop    		
        While execution_time >= update_time		
		Update()
		execution_time:- update_time
	Wend
	
	' calculate the remainder for motion interpolation
	Local et# = execution_time
	Local ut# = update_time
	Local tween# = et / ut
	
	Render(tween)
Wend

Function Update()
	' time independent speed
	Local Speed# = 150.0 / (1000.0 / Float(update_time)) ' 150.0 pixels per second
	
	' record the old position for tweening
	oldX = X
	
	' move the ball
	X:+ (Speed * dirX)
	
	' reverse directions if ball is out of screen bounds
	If X < 0 Or X > 640 Then dirX = - dirX
End Function

Function Render(tween#)
	Cls
	' interpolate between old and actual positions
	Local tx# = X * tween + OldX * (1.0 - tween)	
		
	' draw bouncing ball with interpolated values
	DrawOval tx - 16 , 50 , 32 , 32
	
	' draw second bouncing ball WITHOUT tweening
	DrawOval X - 16 , 200 , 32 , 32
	
	Flip(1)
End Function
	



CGV(Posted 2007) [#2]
Impressive! The movement was super smooth for as long as I ran it.

I felt sorry for the bottom circle though :)


Gabriel(Posted 2007) [#3]
Very nice stuff. I've been involved in a few IRC debates on this subject, and I still firmly believe this technique is as good as anything else for a simple game and better than anything else for a complex game ( ie: any kind of physics. ) For whatever reason, it is not widely known that even simple integration ( ie: any of the physics engines ) will become unreliable if you don't pass a constant delta, and this is the only way I know to keep the updates silky smooth and still get your constant delta.

It's very good of you to share it, and I hope people latch onto it because it's one of the most useful pieces of code they'll find if they do.


Amon(Posted 2007) [#4]
I'll try and intergrate in to my code.

Thanks for posting. :)


HrdNutz(Posted 2007) [#5]
Thanks guys,

And this is the only true way to go :P I know some people use 1KHz update loop frequencies to make their stuff smooth, and to me that's 'the devil' - forcing so many updates per second to achieve smooth results bottlenecks your logic exponentially. You can also forget having complete and exactly perfect reproducible behaviour without having exact fixed steps (replays anyone?). Low frequency updates and interpolation give you best of both worlds :P


The r0nin(Posted 2007) [#6]
Why does this part work?
	' interpolate between old and actual positions
	Local tx# = X * tween + OldX * (1.0 - tween)

Why would you need the old position? Wouldn't X = X + (speed*tween) accomplish the same thing? I know I'm missing something...


HrdNutz(Posted 2007) [#7]
The idea behind this is that if you have multiple frames rendering per single update (actual FPS is greater than the frequency of your logic) then you don't simply render same thing many times, you interpolate between last position and the current one, since current position is not updating again for some time longer. You know where the current position is alerady, but time wise, you haven't reached that point yet. This is the only way to do it - you can't just alter your current position using tween value, because your current position should not change until the next update tick, and you would need to predict where the object will be, instead of knowing for sure where it is and where it was :P

Hope that helps.
Dima

edit: if your FPS is equal or lower than the update frequency, you have nothing to worry about, and you will get more updates per rendered frame, basically keeping the Tween value at 1. It just works :P for actualy games using this approach, many things need to be interpolated, such as; positions, scales, rotations, color changes, etc...

Try setting the UPDATE_FREQUENCY to 1 for better clarity :P


Grey Alien(Posted 2007) [#8]
yeah nice bit of code. It's basically this: http://www.gaffer.org/game-physics/fix-your-timestep/

To convert my entire game to this would be a problem at this stage but I may try it in the future. However, interpolating absolutely everything when drawing can also be a headache, especially for objects that might curve or take unusual paths (unless you have precalculated the path). Furthermore, without clever collision detection, if you use a low frequency update you may end up drawing some sprites inside solid objects before the collision is detected OR miss the collision entirely. So for me it introduces some other issues that a fast update (the devil ;-)) doesn't have. Swings and proverbials...


HrdNutz(Posted 2007) [#9]
I wouldn't say that 200hz is 'the devil' :P But 1Khz definitely is. It is true that interpolating required ahead planning, but it's not really all that bad if you plan ahead. I have a special type/class that just stores values to be interpolated, like position, rotation, scale, color. Then I attach that type to game objects, so they all share it. It's not really that difficult to tween most positions, even if you have curvy moves, linear interpolation usually takes care of it. Also super low frequencies are not the best idea either, but it's easier to understand it from examples. I personally find 50Hz a suitable update rate for 2D, 15Hz for network code. it is true, depending on your game you have to choose proper frequency, but without tweening the values at render time, I am usually unable to achieve perfectly smooth results. (and keeping logic at fixed intervals for replays/physics/network)


tonyg(Posted 2007) [#10]
HrdNutz good information but where was you when I was REALLY struggling with fixed timestep? :)


degac(Posted 2007) [#11]
Many thanks!


TaskMaster(Posted 2007) [#12]
If you take the sample and set the update frequency to 1 or 2, the oval is able to defeat its bounds. IT goes off the screen on the left and right for some time before reversing direction.

Is this a flaw in the concept?

Aren't you suppose to be one dt behind with your tweening and not 1 dt ahead?


TaskMaster(Posted 2007) [#13]
False alarm on my part...

After taking a closer look, it appears that the sample is going to let the ball go out of bounds by 150/Update Frequency. So, it is not a fault of the algorithm and just the way the program works.

I had just grabbed the sample and started changing the update frequency and didn't pay any more mind to it than that.


HrdNutz(Posted 2007) [#14]
Not exaclty a flaw, and yes you're supposed to be 1 step behind with tweening, as it is in the circle example; the update position is ahead of the tweened position.

Reason circle gets out of bounds is because it overshoots the bounds in a single update, and i don't include any code in clipping the circle to the edges.

When reversing direction, add stuff like; if x < 0 then x = 0, if x > 640 then x = 640 - but this is somewhat inaccurate speed wise, unless you bounce the ball back an appropriate distance.

edit: O.O you were ahead of me, while i was typing ..


Grey Alien(Posted 2007) [#15]
Does being a step behind translate to any noticeable lag to the player.


HrdNutz(Posted 2007) [#16]
You're not really behind, update wise you're on time, just filling in stuff in between.


TaskMaster(Posted 2007) [#17]
Here is the sample with X and Y directions. You can use the arrow keys to change the direction. No diagonals.

What I notice is, with the code to get the user input in the update function, there is some delay before it happens. I am not sure what that means. But I guess if it were running at a frequency of 60 then it wouldn't be noticeable.

edit: small change to offset the white ball.
Graphics(640, 480, 32)

Global UPDATE_FREQUENCY = 10 ' times per second
Global update_time = 1000 / UPDATE_FREQUENCY
Global t , dt , execution_time = 0

' boucing ball
Global x# , oldX#
Global y#, oldY#
Global dirX# = 1
Global dirY# = 0

t = MilliSecs()
While Not KeyDown(KEY_ESCAPE)
	dt = MilliSecs() - t
	t = MilliSecs()

	execution_time:+ dt
 
	' fixed interval update loop    		
 While execution_time >= update_time		
		Update()
		execution_time:- update_time
	Wend
	
	' calculate the remainder for motion interpolation
	Local et# = execution_time
	Local ut# = update_time
	Local tween# = et / ut
	
	Render(tween)
Wend

Function Update()
	' time independent speed
	If KeyHit(KEY_LEFT)
		dirX=-1
		dirY=0
	End If
	If KeyHit(KEY_RIGHT)
		dirX=1
		dirY=0
	End If
	If KeyHit(KEY_UP)
		dirY=-1
		dirX=0
	End If
	If KeyHit(KEY_DOWN)
		dirY=1
		dirX=0
	End If
	
	Local Speed# = 150.0 / (1000.0 / Float(update_time)) ' 150.0 pixels per second
	
	' record the old position for tweening
	oldX = x
	oldY = y
	
	' move the ball
	x:+ (Speed * dirX)
	y:+ (Speed * dirY)
	
	' reverse directions if ball is out of screen bounds
	If x < 0 dirX=1
	If x > 640 dirX = -1
	If y < 0 dirY=1
	If y > 480 dirY = -1
End Function

Function Render(tween#)
	Cls
	' interpolate between old and actual positions
	Local tx# = x * tween + OldX * (1.0 - tween)	
	Local ty# = y * tween + oldY * (1.0 - tween)	
		
	' draw second bouncing ball WITHOUT tweening
	SetColor(255,255,255)
	DrawOval x - 16 , y - 16 + 50, 32 , 32
	
	' draw bouncing ball with interpolated values
	SetColor (255,0,0)
	DrawOval tx - 16 , ty - 16 , 32 , 32
	
	
	Flip
End Function



HrdNutz(Posted 2007) [#18]
Very nice O.O :D

yeah, you can't expect user input to be sensitive at 10hz - althought thre are ways :P like recording input states independent of the update loop, and saving them for use inside the low freq update. 60hz you will not notice anything though, so not much to worry about.


Grey Alien(Posted 2007) [#19]
Hmm, I just tested out the code. Looked smooth in full-screen but not in windowed mode. That's becasue Flip is being used with the default flags of -1, which is no good. Try Flip 1 instead.

Also here's an example made with my timing code. It's just as smooth on my PC, how about yours?

http://www.greyaliengames.com/misc/Ovaltest.zip (613k)


tonyg(Posted 2007) [#20]
HrdNutz code and the code posted by TaskMaster looks smooth for me in both full and windowed. What am I missing?


Grey Alien(Posted 2007) [#21]
Well try changing Flip to Flip 1 and see if it looks any smoother. With Flip, it smoothish but with some mini jerkiness, nothing serious. With Flip 1 it's dead smooth.


tonyg(Posted 2007) [#22]
Windowed mode and Flip 1 is *less* smooth on my system.
Windowed mode and Flip couldn't be any smoother


Grey Alien(Posted 2007) [#23]
weird. Exactly the opposite of my system. Did you run my ovaltest yet? How's that look by comparison?

When did you last syncmods? Flip 1 uses VSync in windowed mode for about a month or so, but didn't before that...


tonyg(Posted 2007) [#24]
Syncmods up to date.
Your Ovaltest was smooth with the odd little glitchy jump. However, I'm giving up on running non-source comparative tests as there's no chance to spot any differences. It doesn't really add much to a discussion based around somebody else's source.
<edit> Just re-read this. I'm not being grumpy it just doesn't seem useful to compare somebody's source with a .exe.


TaskMaster(Posted 2007) [#25]
Flip and Flip 1, in window mode, look about the same on my system.


HrdNutz(Posted 2007) [#26]
It should (is) be same in both modes - with VSYNC or no, there should be no difference (unless your fps > 1000) - if you'r windowed mode is giving you trouble then precautions should be made regarding Jitter equalization. (most likely due to windowed modes having to draw extra stuff in the back) - basically need to smooth out the time jitters


dmaz(Posted 2007) [#27]
yeah... this is that weird blitzmax thing. I had a tweening exe test http://www.blitzbasic.com/Community/posts.php?topic=62676#700275 that I posted 10 months ago that was perfectly smooth on Grey's comuputer and any mac I tried it on. But it had a little jump on my computer at the time(still jumps on that computer). now on my new machine it perfectly smooth....

my old computer had an old ATI driver while this new one has a new Nvidia driver...


Grey Alien(Posted 2007) [#28]
tonyg: OK I understand, but could you possibly answer me this? On my exe you get the odd glitchy jump (probably background tasks). Did the same occur with HrdNutz code or not?

HrdNutz: Yeah it's weird, but it's not. It's also not DX as it's the same for OpenGL. I changed the top of the code to:

SetGraphicsDriver GLMax2DDriver()
Graphics(640, 480, 0)

And the circle is mainly smooth but it's not totally smooth. No big jerks, just inconsistent (hard to describe really).
With Flip 1 it's 100% mega smooth (like my exe).
I'll test it on my laptop later, and Mac.

I'm really weirded out byt this. It's annoying. It's been plagueing me ever since I got Bmax and started making my framework (over a year ago now). NO ONE has an answer...maybe there isn't one.


tonyg(Posted 2007) [#29]
Did the same occur with HrdNutz code or not?

It was completely smooth in both modes.


Grey Alien(Posted 2007) [#30]
bummer. Also weird because if you got any background tasks kicking in, HrdNutz's code should suffer from the same "catchup" glitch as there is no anti-jitter code in it.


HrdNutz(Posted 2007) [#31]
Technically there is an answer :P

And yes my tweening example should suffer form jitters as well, althought the main differense may be in that with tweening - the higher FPS you achieve, the better results will be, but with no tweening, if FPS is higher than the logic or get odd updates like 2 updates and one render in one pass, and 1 update and 2 renders in the second pass, then you will have issues, because tweening keeps track of that remainder. Technically you could have 2.69 updates per frame rendered (or the other way around), and if that remainder gets lost somewhere in the process you will have problems with timing.

Also, the reason why windowed mode with no vsync might appear not as smooth is because your FPS is greater than 1k and blitzmax timer is accurate to 1ms, meaning that you cannot achieve delta smaler than 1 but bigger than 0 (anything above 1000FPS would have a delta thats between 0 and 1), any bigger FPS than 1000 and you will get varied deltas from frame to frame (either 0 or 1, but nothing in between). Vsync on assures that deltas stay above 1 (delta = 1000/refreshrate), thus you don't see that disrepancy. To be able to achieve ultimate smooth results with FPS > 1k you would have to use a high resolution timer like Windows Performance Counter. This is naturally expected from Blitzmax or any app that uses timeGetTime() counters that are only accurate to 1ms.


Grey Alien(Posted 2007) [#32]
yeah my timing code keeps track of the remainder. The jerk TonyG was seeing didn't sound like like a 2 frame/1 frame "moire" effect (which occurs continuously), it sounded like the millisecs from one from frame to another had a mini peak which lasted a few frames thus resulting in a "glitch". I've seen this plenty of times with background tasks interfering.

Apparently some CPUs have issues with Performance Counters.

In your example Flip uses a default flag of -1 which does have syncing of sorts, but not VSync. BlitzMax uses a timer to keep it at 60Hz by default and then Flips without VSync. Flip 1 properly uses VSync so will have an FPS the same as your desktop Hz (85Hz in my case for my CRT). Perhaps TonyG has a TFT and is running his desktop at 60Hz so Flip -1 looked OK and so did Flip 1 (also 60Hz). Full-screen on my PC was fine too with Flip because the timer was running at 60Hz, the same as the full-screen Hz.


tonyg(Posted 2007) [#33]
Yep, my TFT is 60 mhz, Luckily I am only writing stuff for use on my machine. I feel for you people who are trying to get graphics and audio working on different machines and OS. Maybe BRL will provide an example which demonstrates x-platform worhthiness on various machines.


Grey Alien(Posted 2007) [#34]
Ah 60Hz, thanks that explains why it was smooth on yours.

Yeah writing for your own machine is fun as you only need to make sure it works on that. Or writing for a hobby as, although not ideal, it doesn't matter if it doesn't run properly on a wide range of machines. If your are self-publishing a game naturally you want it to work on as many PCs as possible, but at least the buck stops with you. I'm in a situation where I'm writing a game for Big Fish Games, so when they beta test it on about a zillion PCs I'll have to explain why it jitters/jerks on some, doesn't display anything on others, sound breaks up/doesn't play/is noisy on others etc. Arg...OK, moan over. thx.


CGV(Posted 2007) [#35]
Grey, are these problems specific to BlitzMax or would you be having them with any engine like say the PopCap framework?

If they are specific to BlitzMax, then you're not really saving any development time over using C++ and some framework like PopCap or PTK.

I hate to say it but maybe BlitzMax isn't ready for commercial development.


HrdNutz(Posted 2007) [#36]
i seem not be having many issues with timing ... other than it's not high resolution enought, but looks fine on my end. What seems to be the exact problem you're having Grey? As long as things look smooth in full screen with vsync on, nothing to worry about :D


Grey Alien(Posted 2007) [#37]
2 main problems:

1) Sounds is inconsistent on a range of PC/OSes. FMOD is not free and requires some techie knowledge and thought to implement (which I don't yet possess).

2) What looks perfectly smooth on my PC is not smooth on other PCs despite having good "proven" timing code. The issue is with background tasks interfering too much with BMax and putting out Millisecs() by a large amount every so often (well by sometimes taking 50ms+ to render a frame basically) thus causing the odd jerk here and there.

I haven't compared BMax to BPlus or C++ but I'm tempted to see if it's any smoother on the others.

Meanwhile, I've been inspired by these threads to add in some jitter correction (via storing a history and picking an average value) to my framework timing. It works very nicely and smooths out some tiny occasional jitters caused by "moire" effect and also when big jerks occur (caused by background tasks interfering) the framework doesn't try to "catch up" in one frame, it spreads the catch up over several frames, thus the jerks don't look as bad.

I've may also drop my core timing from 200HZ to 120HZ for future games because if my game is in full-screen (60Hz) then this will result in 2 updates per frame which is a 2:1 ratio i.e. no "moire" effect if I decide to move objects at 1 pixel per frame for example. This could mean ultra smooth full-screen games with proper timing so if Vsync is off they still play at the right speed, and also windowed mode will play at the right speed (but won't look so utterly smooth as full-screen, although it will still be pretty darn smooth)

I'll post a demo soon in another thread for testing.


dmaz(Posted 2007) [#38]
Grey... over the last year I think these forums have pretty much proved that the smoothness problem is a Max thing... And you have been one of the biggest contributers to figuring this out! ;) we also know that Blitz3d DOES NOT have these issues...


Gabriel(Posted 2007) [#39]
I'm going to have to go with dmaz ( assuming that he means a Max2D thing when he says "a Max thing". ) If the code that hrdnutz has posted is anything less than perfectly smooth, it almost has to be a Max2D issue, because render tweening has always been universally smooth in B3D and it's smooth as silk in TV3D too.


HrdNutz(Posted 2007) [#40]
I personally haven't noticed any BMAX specific problems with smoothness, other than 1ms resolution timer, but that doesn't really make a difference. 2D games are generally harder to make appear 'smooth' because side scrolling usually happens at a universal magnitude, so any hichups are noticible right away.

Also, DX and OGL rendering happen at subpixel precision - but some monitors (like LCDs) have very pronounced pixels, so transition of a polygon between a single pixel might appear like it's jumping from one pixel to the next (like no subpixel accuracy), but if you pay close attention to the texture inside the polygon, it will glide smoothly from one pixel to the next. Subpixel precision is done with texture filtering (OGL&DX), and specific monitors will show much edgier transition of polys from pixel to pixel. Defringing your sprites, adding alpha blended edge smoothing to textures, and setting proper texture wrapping modes (clamping) helps tremendously with this discrepansy.


Grey Alien(Posted 2007) [#41]
I gotta do a similar test in BPlus (I don't have B3D) and see what it's like. The thing is, my framework is smooth on my PC (and plenty of others), but just not all PCs. I don't know 100% it's a BMax only thing. Other say that but I haven't seen the proof myself yet (although I'm tempted to believe it).

HrdNutz: Yeah a nicely made sprite will look great drawn at subpixel positions but ones with a straight edge can look a bit crap.

OK I'm ready to post my test app in another thread now.


HrdNutz(Posted 2007) [#42]
It basically boils down to reducing latency spikes: http://www.blitzbasic.com/Community/posts.php?topic=70611


Chroma(Posted 2007) [#43]
HrdNutz, this is the best timing code I've ever seen. Excellent work!


Grey Alien(Posted 2007) [#44]
yes it is rad, it just needs jitter correction for the icing on the cake. I added it to my timing code recently and it helps keep things smooth.


Chroma(Posted 2007) [#45]
Is there jitter correction in the original Fix Your TimeStep code by that professional game programmer? I think his name is Gaffer or something.


Chroma(Posted 2007) [#46]
Hmm...GA, your OvalTest is jittery on my comp while HrdNutz port of the Gaffer code works perfect.

I'm guessing you based your timing code off the Gaffer code also but maybe you've done something to it that isn't meshing with a lot of people's computers. /shrug I even shut off as much background things as I could and had nothing else running on the desktop. /shrug /shrug


HrdNutz(Posted 2007) [#47]
There is no Jitter correction in his code or this example - it's easy to implement thought. The idea here is low frequency updates with linear interpolation to make it get smoother with better framerates. Also perfect fixed logic steps can ensure reproducible behaviour and network/physics stability.

Reducing jitters can be done with averaging deltas and simple spike equalization.


Grey Alien(Posted 2007) [#48]
Chroma: That oval test is out of date and it may be working at a refresh rate not in sync with your screen so HrzNutz's code will look smoother. This is much more up to date, get any jitters? http://www.greyaliengames.com/misc/jitter.zip

HrdNutz is right, the original code or the example above do not have jitter correction. But tweening is a good method, I'm just not into it at the moment. I think lots of 3D games do it.

I may try running my next game at 30FPS refresh and 60FPS logic as that will smooth out jitters a lot just by the low-res updates. Not all PCs can cope with ultra smooth timing unfortunately.


HrdNutz(Posted 2007) [#49]
I really suggest you guys give tweenign a shot - it's basically fixed step logic and delta time smoothness. (without having high freq updates, and get's better with framerate)

It really is easy if you get a grasp on it. (some people very much notice a 30fps refresh)


Grey Alien(Posted 2007) [#50]
I notice a 30fps. It's just that lots of big name casual games seem to get away with it. I prefer 60.


tonyg(Posted 2007) [#51]
How do you do 'slow-mo' with fixed-rate logic?
@HrdNutz what more can you provide about your special interpolation type?


Grey Alien(Posted 2007) [#52]
Slow mo is great for testing animations and special particle effects. I use it all the time with a hot key in my framework. I assume in the top example you just modify:

Global UPDATE_FREQUENCY = 10 ' times per second

to say 2 and you are running at 1/5th the speed.

All I do in my framework is change the framerate to anything I want and it just works.


tonyg(Posted 2007) [#53]
Hmmm, that's how I got it to work in my version of the same code but doesn't seem to 'stick'. Nevermind.


ImaginaryHuman(Posted 2007) [#54]
You'd have to do something else other than set th update frequency as the interpolation is still running at the rate of the display.


HrdNutz(Posted 2008) [#55]
bump