Fixing your Timestep and evaluating Godot

Published September 24, 2018
Advertisement

The past few days I have been playing with Godot engine with a view to using it for some Gamedev challenges. Last time for Tower Defence I used Unity, but updating it has broken my version so I am going to try a different 'rapid development' engine.

So far I've been very impressed by Godot, it installs down to a small size, isn't bloated and slow with a million files, and is very easy to change versions of the engine (I compiled it from source to have latest version as an option).

Unfortunately, after working through a couple of small tutorials I get the impression that Godot suffers from the same frame judder problem I had to deal with in Unity.

Let me explain: (tipping a hat to Glenn Fiedler's article)

Some of the first games ran on fixed hardware so they didn't have a big deal about timing, each 'tick' of the game was a frame that was rendered to the screen. If the screen rendered at 30fps, the game ran at 30fps for everyone.

This was used on PCs for a bit, but the problem was that some hardware was faster than others, and there were some games that ran too fast or too slow depending on the PC.

Clearly something had to be done to enable the games to deal with different speed CPUs and refresh rates.

Delta Time?

The obvious answer was to sample a timer at the beginning of each frame, and use the difference (delta) in time between the current frame and the previous to decide how far to step the simulation.

This is great except that things like physics can produce different results when you give it shorter and longer timesteps, for instance a long pause while jumping due to a hard disk whirring could give enough time for your player to jump into orbit. Physics (and other logic) tends to work best and be simpler when given fixed regular  intervals. Fixed intervals also makes it far easier to get deterministic behaviour, which can be critical in some scenarios (lockstep multiplayer games, recorded gameplay etc).

Fixed Timestep

If you know you want your gameplay to have a 'tick' every 100 milliseconds, you can calculate how many ticks you want to have complete at the start of any frame.


// some globals
iCurrentTick = 0

void Update()
{
  // Assuming our timer starts at 0 on level load:
  // (ideally you would use a higher resolution than milliseconds, and watch for overflow)
  iMS = gettime();

  // ticks required since start of game
  iTicksRequired = iMS / 100;

  // number of ticks that are needed this frame
  iTicksRequired -= iCurrentTick;

  // do each gameplay / physics tick
  for (int n=0; n<iTicksRequired; n++)
  {
    TickUpdate();
    iCurrentTick++;
  }

  // finally, the frame update
  FrameUpdate();
}

Brilliant! Now we have a constant tick rate, and it deals with different frame rates. Providing the tick rate is high enough (say 60fps), the positions when rendered look kind of smooth. This, ladies and gentlemen, is about as far as Unity and Godot typically get.

The Problem

However, there is a problem. The problem can be illustrated by taking the tick rate down to something that could be considered 'ridiculous', like 10 or less ticks per second. The problem is, that frames don't coincide exactly with ticks. At a low tick rate, several frames will be rendered with dynamic objects in the same position before they 'jump' to the next tick position.

The same thing happens at high tick rates. If the tick does not exactly match the frame rate, you will get some frames that have 1 tick, some with 0 ticks, some with 2. This appears as a 'jitter' effect. You know something is wrong, but you can't put your finger on it.

Semi-Fixed Timestep

Some games attempt to fix this by running as many fixed timesteps as possible within a frame, then a smaller timestep to make up the difference to the delta time. However this brings with it many of the same problems we were trying to avoid by using fixed timestep (lack of deterministic behaviour especially).

Interpolation

The established solution that is commonly used to deal with both these extremes is to interpolate, usually between the current and previous values for position, rotation etc. Here is some pseudocode:


// some globals
int iCurrentTick = 0
  
// player
Vector3 m_Pos_previous = (0, 0, 0);
Vector3 m_Pos_current = (0, 0, 0);
Vector3 m_Pos_render = (0, 0, 0);
  
// called each frame by engine
void Update()
{
	// Assuming our timer starts at 0 on level load:
	// (ideally you would use a higher resolution than milliseconds, and watch for overflow)
	int iMS = gettime();

	// ticks required since start of game
	int iTicksRequired = iMS / 100;

	// remainder
	int iMSLeftOver = iMS % 100;

	// number of ticks that are needed this frame
	iTicksRequired -= iCurrentTick;

	// do each gameplay / physics tick
	for (int n=0; n<iTicksRequired; n++)
	{
		TickUpdate();
		iCurrentTick++;
	}  

	// finally, the frame update
	float fInterpolationFraction = iMSLeftOver / 100.0f;
	FrameUpdate(fInterpolationFraction);
}

// just an example
void TickUpdate()
{
	m_Pos_previous = m_Pos_current;
	m_Pos_current.x += 10.0f;
}

// very pseudocodey, just an example for translate for one object
void FrameUpdate(float fInterpolationFraction)
{
	// where pos is Vector3 translate
	m_Pos_render = m_Pos_previous + ((m_Pos_current - m_Pos_previous) * fInterpolationFraction);
}

The more astute among you will notice that if we interpolate between the previous and current positions, we are actually interpolating *back in time*. We are in fact going back by exactly 1 tick. This results in a smooth movement between positions, at a cost of a 1 tick delay.

This delay is unacceptable! You may be thinking. However the chances are that many of the games you have played have had this delay, and you have not noticed.

In practice, fast twitch games can set their tick rate higher to be more responsive. Games where this isn't so important (e.g. RTS games) can reduce processing by dropping tick rate. My Tower Defence game runs at 10 ticks per second, for instance, and many networked multiplayer games will have low update rates and rely on interpolation and extrapolation.

I should also mention that some games attempt to deal with the 'fraction' by extrapolating into the future rather than interpolation back a frame. However, this can bring in new sets of problems, such as lerping into colliding situations, and snapping.

Multiple Tick Rates

Something which doesn't get mentioned much is that you can extend this concept, and have different tick rates for different systems. You could for example, run your physics at 30tps (ticks per second), and your AI at 10tps (an exact multiple for simplicity). Or use tps to scale down processing for far away objects.

How do I retrofit frame interpolation to an engine that does not support it fully?

With care is the answer unfortunately. There appears to be some support for interpolation in Unity for rigid bodies (Rigidbody.interpolation) so this is definitely worth investigating if you can get it to work, I ended up having to support it manually (ref 7) (if you are not using internal physics, the internal mechanism may not be an option). Many people have had issues with dealing with jitter in Godot and I am as yet not aware of support for interpolation in 3.0 / 3.1, although there is some hope of allowing interpolation from Bullet physics engine in the future.

One option for engine devs is to leave interpolation to the physics engine. This would seem to make a lot of sense (avoiding duplication of data, global mechanism), however there are many circumstances where you may not wish to use physics, but still use interpolation (short of making everything a kinematic body). It would be nice to have internal support of some kind, but if this is not available, to support this correctly, you should explicitly separate the following:

  1. transform CURRENT (tick)
  2. transform PREVIOUS (tick)
  3. transform RENDER (where to render this frame)

The transform depends on the engine and object but it will be typically be things like translate, rotate and scale which would need interpolation.

All these should be accessible from the game code, as they all may be required, particularly 1 and 3. 1 would be used for most gameplay code, and 3 is useful for frame operations like following a player with a camera. 

The problem that exists today in some engines is that in some situations you may wish to manually move a node (for interpolation) and this in turn throws the physics off etc, so you have to be very careful shoehorning these techniques in.

Delta smoothing

One final point to totally throw you. Consider that typically we have been relying on a delta (difference) in time that is measured from the start of one frame (as seen by the app) and the start of the next frame (as seen by the app). However, in modern systems, the frame is not actually rendered between these two points. The commands are typically issued to a graphics API but may not be actually rendered until some time later (consider the case of triple buffering). As such the delta we measure is not actually the time difference between the 2 rendered frames, it is the delta between the 2 submitted frames.

A dropped frame may for instance have very little difference in the delta for the submitted frames, but have double the delta between the rendered frames. This is somewhat a 'chicken and the egg' problem. We need to know how long the frame will take to render in order to decide what to render, where, but in order to know how long the frame will take to render, we need to decide what to render, and where!!

On top of this, a dropped frame 2 frames ago could cause an artificially high delta in later submitted frames if they are capped to vsync!

Luckily in most cases the solution is to stay well within performance bounds and keep a steady frame rate at the vsync cap. But in any situation where we are on the border between getting dropped frames (perhaps a high refresh monitor?) it becomes a potential problem.

There are various strategies for trying to deal with this, for instance by smoothing delta times, or working with multiples of the vsync interval, and I would encourage further reading on this subject (ref 3, 8, 9).

Addendum

Note that the code examples given are pseudocode, and may be best modified for a particular engine. There is often some support for fixed tick rates within engines, for instance in Unity you may be able to use the following simplified scheme:


// unity calls update once per frame
void Update()
{
	// interpolate fraction
	float fFraction = (Time.time - Time.fixedTime) / Time.fixedDeltaTime;

	// my frame update for interpolation
	FrameUpdate(fFraction); // same as earlier examples
}

// unity calls once per tick
void FixedUpdate()
{
	TickUpdate(); // same as earlier examples
}

I would also suggest implementing interpolation without engine physics first, because using with engine physics can incorporate subtle bugs as you may be moving a physics representation inadvertently. I will address this in a future article, however in short one solution is to create completely separate renderable objects and physics objects in the game (different branches of the scene graph), store a reference on the render object to the physics object, and interpolate the render object transform based on the physics object. In Godot you can also visualize the physics colliders in the Debug Menu, which is very helpful. 

References

https://gafferongames.com/post/fix_your_timestep/
http://www.kinematicsoup.com/news/2016/8/9/rrypp5tkubynjwxhxjzd42s3o034o8
http://frankforce.com/?p=2636
http://fabiensanglard.net/timer_and_framerate/index.php
http://lspiroengine.com/?p=378
http://www.koonsolo.com/news/dewitters-gameloop/
https://forum.unity.com/threads/case-974438-rigidbody-interpolation-is-broken-in-2017-2.507002/

https://medium.com/@alen.ladavac/the-elusive-frame-timing-168f899aec92
http://bitsquid.blogspot.com/2010/10/time-step-smoothing.html

7 likes 23 comments

Comments

JTippetts

I had a similar issue with retrofitting Urho3D to use a fixed timestep. I'm not doing that now, but when I was doing it my solution was to implement a special component to store last and current transforms and work through that. It worked well enough, but my games tend to not use physics, and as you mentioned, physics is where the madness lurks. Something built into the native Node would be better, I think, but would require modifying the library.

September 24, 2018 04:14 PM
lawnjelly
2 minutes ago, JTippetts said:

I had a similar issue with retrofitting Urho3D to use a fixed timestep. I'm not doing that now, but when I was doing it my solution was to implement a special component to store last and current transforms and work through that. It worked well enough, but my games tend to not use physics, and as you mentioned, physics is where the madness lurks. Something built into the native Node would be better, I think, but would require modifying the library.

Spot on. Really you would think there should be support for previous, current and render transforms in the node base class. I am watching the Godot github site with interest to see whether anything happens on this, as a number of people have had issues with timestep jitter.

September 24, 2018 04:21 PM
Awoken

I was wondering how to do this and now I know.  Great blog entry.

September 24, 2018 11:53 PM
Hodgman

Unity does fixed timestep physics (and has a fixed and a variable rate loop for your own code), let's you configure the step sizes, and let's you choose whether to interpolate per physics object.... 

September 25, 2018 01:05 AM
lawnjelly
5 hours ago, Hodgman said:

Unity does fixed timestep physics (and has a fixed and a variable rate loop for your own code), let's you configure the step sizes, and let's you choose whether to interpolate per physics object.... 

Both Unity and Godot have fixed update and frame update. But after a bit of investigation, I couldn't get the interpolation working properly in unity for my game, so implemented it manually. Admittedly I am not very experienced with Unity, and they do seem aware of the issue (maybe some of the articles I read were out of date?). I also wasn't using their internal physics so perhaps that influenced me, maybe their interpolation only works for internal physics. I will correct this in the post though, well spotted, thank you. Unfortunately I can no longer test as I don't have Unity working any more.

In my tests so far in Godot and reading the issues on github it doesn't appear like they have an in built solution for this yet, there are several topics in the issue tracker about it (and attempts to fix some issues with a hysteresis modification). One guy has suggested that Bullet which is available in Godot does offer interpolation and perhaps Godot can make the right calls to Bullet and get the interpolation for free for rigid bodies etc. 

September 25, 2018 06:43 AM
nucky9

The jitter in both Godot and Unity has been driving me crazy, and this is the first article I have seen that tries to address the problem directly. In my Unity game, everything is being moved by adjusting the transform.positions by a factor of time and speed: 


void Update()
{
  transform.position += direction * Time.deltaTime * speed;
}

This leads to some noticeable sprite jitter.

So, I tried to implement the interpolation system you described, but it didn't make any difference. Here is what I did:


void Start()
{
Application.targetFrameRate = Screen.currentResolution.refreshRate;
timePerFrame = 1f / Screen.currentResolution.refreshRate;
direction = new Vector3(1, 0, 0);
}

void Update()
{
float timeLeftOver = Time.time % timePerFrame; 
float fractionalFrame = timeLeftOver / timePerFrame;
newPosition += direction * Time.deltaTime * speed;
Vector3 interpolatedPosition = oldPosition + ((newPosition - oldPosition) * fractionalFrame);
transform.position = interpolatedPosition;
oldPosition = newPosition;
}

Could you give any insight into why this doesn't work and/or how you would fix the jitter in Unity when moving 2D objects by position?

 

January 06, 2019 09:39 PM
Septopus

Vector3.Lerp/Slerp + FixedUpdate()..  I rarely use Update() in Unity anymore, but I do use internal physics.  I'm not sure what happens to FixedUpdate() if you turn off internal physics.. 

This combo got my recorded replays working, and nearly everything else in my SlingBots game.

January 07, 2019 12:35 AM
Septopus

Note: Lerp/Slerp can work inside Update() as well, but you have to modify the parameter you send to it with the Time.deltaTime value..  It can get kinda messy.. ;)

Also, Time settings appear to be separate from Physics settings(2018.3), so I assume that means FixedUpdate() still ticks on if internal physics are turned off.  If they are on and you're just not using them then FixedUpdate will def work. ;)

timesettingsunity.png.8bd5784604d8b49d7f539300561f11e4.png

January 07, 2019 01:18 AM
nucky9

I tried Lerping before, but maybe I'm not using it the way you are. Here is what I am currently doing in a simple scene, where all I move is the camera, and have a static 2d background image that is panned over:


void Start()
{
  startTime = Time.time;
  distance = Vector2.Distance(transform.position, targetPosition);
  timeToComplete = Mathf.Abs(distance / speed);
  startPosition = transform.position;
}

void FixedUpdate()
{
float timeComplete = Time.time - startTime;
percentComplete = timeComplete / timeToComplete;

transform.position = Vector2.Lerp(startPosition, targetPosition, percentComplete);
}

The background image still appears to jitter slightly as the camera moves.

January 07, 2019 01:52 AM
Septopus
1 hour ago, nucky9 said:

I tried Lerping before, but maybe I'm not using it the way you are. Here is what I am currently doing in a simple scene, where all I move is the camera, and have a static 2d background image that is panned over:



void Start()
{
  startTime = Time.time;
  distance = Vector2.Distance(transform.position, targetPosition);
  timeToComplete = Mathf.Abs(distance / speed);
  startPosition = transform.position;
}

void FixedUpdate()
{
float timeComplete = Time.time - startTime;
percentComplete = timeComplete / timeToComplete;

transform.position = Vector2.Lerp(startPosition, targetPosition, percentComplete);
}

The background image still appears to jitter slightly as the camera moves.

Some of your jitter might be due to use of Time.. ? 

Try maybe: System.Diagnostics.Stopwatch  <- is my favorite timekeeper.

Most of my use-cases involve a

.Lerp(current position(not startpos), target position, some fixed or variably fixed small value like 0.1f)

With this method, the third parameter then becomes a "speed" controller.  So instead setting a time for the the whole interaction, you set the speed of the "animation" and just let it play. ;)

*** I also provide a series of waypoints along a line that the objects "follow" too, so that's why I can get away with using a mostly fixed value for the third parameter..  It takes some testing and some digging into the timing of things to get it all going smoothly, no matter how you do it. ;)

January 07, 2019 03:16 AM
nucky9

No that still doesn't help :(. I think the problem is when you simply scroll over a linear background, it is quite obvious when the refresh and the transforms aren't synchronized, as discussed in the article. where there are many complex shapes moving simultaneously in many directions, which makes it much harder to pick out. Unfortunately, the game I am making is pure 2d, so this is a big issue.

January 07, 2019 03:43 AM
Septopus
5 minutes ago, nucky9 said:

No that still doesn't help :(. I think the problem is when you simply scroll over a linear background, it is quite obvious when the refresh and the transforms aren't synchronized, as discussed in the article. where there are many complex shapes moving simultaneously in many directions, which makes it much harder to pick out. Unfortunately, the game I am making is pure 2d, so this is a big issue.


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SphereLerp : MonoBehaviour
{
    public Vector3 startpos = Vector3.zero;
    public Vector3 endpos = new Vector3(0, 10, 0);
    float endtime = 2000;
    
    System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

    private void Start()
    {
        sw.Start();
        startpos = transform.position;
    }

    void FixedUpdate()
    {
        if (sw.ElapsedMilliseconds <= endtime) {
            float curpct = sw.ElapsedMilliseconds / endtime;
            transform.position = Vector3.Lerp(startpos, endpos, curpct);
        }
        else
        {
            sw.Restart();
        }
    }
}

Attach this to a sphere in a empty Unity(3d) project and hit play. ;)   Easy to convert to 2d from there.

January 07, 2019 03:50 AM
nucky9

First, I want to thank you for taking so much time to help we with this, I really appreciate it! Unfortunately though, I don't see the sphere as being smooth, and in fact is quite rough. I built it and posted it to itch.io: https://nucky9.itch.io/spheretest?secret=shdJdxrMEmjcBoatbSNoCs2ZU. A few others I had test it can see it too on other computers as well, although some people find it easier to spot than others.

For me it looks very jittery. It also looks jittery in the Unity player. If it is smooth for you, I wonder if it is something in your player/build settings, or version that is different from me? I am using 2018.3.0f2.

 

 

January 07, 2019 05:13 AM
Mussi
7 hours ago, nucky9 said:

Could you give any insight into why this doesn't work and/or how you would fix the jitter in Unity when moving 2D objects by position?

You're using deltaTime to update your new position, this should be a fixed amount of time depending on your simulation rate. How are you updating your targetPosition in your later sample?

January 07, 2019 05:23 AM
Septopus
1 hour ago, nucky9 said:

First, I want to thank you for taking so much time to help we with this, I really appreciate it! Unfortunately though, I don't see the sphere as being smooth, and in fact is quite rough.

Hmm, well I think I understand what you mean by Jitter now. ;)

try this perhaps?


using UnityEngine;

public class SphereLerp : MonoBehaviour
{
    public Vector3 startpos = Vector3.zero;
    public Vector3 nextpos = Vector3.zero;
    public Vector3 endpos = new Vector3(0, 10, 0);
    public float endtime = 4000;
    
    System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

    private void Start()
    {
        sw.Start();
        startpos = transform.position;
    }

    private void Update()
    {
        if (transform.position != nextpos)
        {
            transform.position = Vector3.Lerp(transform.position, nextpos, 0.1f);
        }
        if (sw.ElapsedMilliseconds <= endtime)
        {
            float curpct = sw.ElapsedMilliseconds / endtime;
            nextpos = Vector3.Lerp(startpos, endpos, curpct);
        }
        else
        {
            sw.Restart();
            transform.position = startpos;
        }
    }
}

This is more in line with most of my use cases and I'm using Update() here to keep the adjustments in sync with the visible frame rate.  The stopwatch fills the roll of the fixed timestep in this scenario.  The first example was moving a bit fast for my eyes to detect jitter..  I slowed this one down a bit... Looks good to me.  Hope it helps, sorry we hijacked your blog @lawnjelly, hope some of this is pertinent.. ;)

January 07, 2019 06:27 AM
nucky9

 

57 minutes ago, Mussi said:

You're using deltaTime to update your new position, this should be a fixed amount of time depending on your simulation rate. How are you updating your targetPosition in your later sample?

In that example I wasn't updating targetPosition, because I was just doing one pass of the background with the camera as a test case. In my actual game, I wasn't using Lerp, because everything is moving at deltaTime * speed in one direction (it is an endless runner). I could use Lerp, but as I said it doesn't seem to make a difference.

The deltaTime issue is discussed here: https://forum.unity.com/threads/time-deltatime-not-constant-vsync-camerafollow-and-jitter.430339/ so yeah it seems like it isn't good to use when vysnc is on, and simply turning vsync off isn't much of a fix. But if you ignore this issue, are you still saying that deltaTime shouldn't be used? I don't really understand that then, since isn't that the point of it (assuming vsync is on, and it is locked to screen refresh)? Certainly all the unity examples use deltaTime this way. My understanding of lawnjelly's post was that it was trying to fix this issue.

Anyway, all that said I do find things improve when I used the suggestion to manually set the deltaTime intervals using         Time.captureFramerate = 1 / 60, so using a fixed update does help as you and Septopus suggest. 

 

 

 

 

 

 

January 07, 2019 06:41 AM
nucky9
15 minutes ago, Septopus said:

Hmm, well I think I understand what you mean by Jitter now. ;)

try this perhaps?



using UnityEngine;

public class SphereLerp : MonoBehaviour
{
    public Vector3 startpos = Vector3.zero;
    public Vector3 nextpos = Vector3.zero;
    public Vector3 endpos = new Vector3(0, 10, 0);
    public float endtime = 4000;
    
    System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

    private void Start()
    {
        sw.Start();
        startpos = transform.position;
    }

    private void Update()
    {
        if (transform.position != nextpos)
        {
            transform.position = Vector3.Lerp(transform.position, nextpos, 0.1f);
        }
        if (sw.ElapsedMilliseconds <= endtime)
        {
            float curpct = sw.ElapsedMilliseconds / endtime;
            nextpos = Vector3.Lerp(startpos, endpos, curpct);
        }
        else
        {
            sw.Restart();
            transform.position = startpos;
        }
    }
}

This is more in line with most of my use cases and I'm using Update() here to keep the adjustments in sync with the visible frame rate.  The stopwatch fills the roll of the fixed timestep in this scenario.  The first example was moving a bit fast for my eyes to detect jitter..  I slowed this one down a bit... Looks good to me.  Hope it helps, sorry we hijacked your blog @lawnjelly, hope some of this is pertinent.. ;)

This is much better, thanks! I can still see some small jitters occasionally though (usually about one loop out of three or four has some rough frames). I'm not sure if that is a Unity problem or a Windows problem, but anyway, it is much better, and would definitely be an improvement over what I had previously. From reading the thread I posted, using your own clock as you are and Mussi suggested seems to be the best solution, given Unity's timekeeping problems.

Thanks again for all the help!

 

 

 

January 07, 2019 06:52 AM
lawnjelly

As Septopus says, this probably would have been better as a fresh forum post linking to the blog lol but we have started so we shall finish! :) 

I am intending to write a follow up with some good approaches to dealing with the interaction between interpolation code and internal physics engines, which can be a very tricky subject.

There is certainly a lot of info packed in your guys posts, and there does seem to be some confusion. I realise that I haven't made it clear enough in the blog that interpolation is to be used WITH the tick based update, i.e. the first and second pseudocode samples are intended to be used together. I alluded to this in the comment '// ... gameplay ticks' in order to save space but I will edit this to longhand in order to avoid confusion. This is great feedback, something that is obvious to me when writing, but not the reader.

First my recommendation for anyone dealing with these issues, I cannot stress enough, for testing you should reduce your fixed tick rate, say to 1tps or 2tps. This will make clear any errors that would be hidden by testing at a higher tick rate. If you can't get smooth behaviour at a low tick rate, you will not get smooth behaviour at a high tick rate.

Secondly I would also recommend getting interpolation working without using engine physics before attempting to get it working with engine physics (if you are using engine physics). Using engine physics with interpolation can be a minefield in itself, I will suggest some good approaches in a further article.

I'll try and work through some of the posts, first nucky9's original:

13 hours ago, nucky9 said:

 



void Start()
{
Application.targetFrameRate = Screen.currentResolution.refreshRate;
timePerFrame = 1f / Screen.currentResolution.refreshRate;
direction = new Vector3(1, 0, 0);
}

void Update()
{
float timeLeftOver = Time.time % timePerFrame; 
float fractionalFrame = timeLeftOver / timePerFrame;
newPosition += direction * Time.deltaTime * speed;
Vector3 interpolatedPosition = oldPosition + ((newPosition - oldPosition) * fractionalFrame);
transform.position = interpolatedPosition;
oldPosition = newPosition;
}



  • Very probably a mistake is basing the tick rate on the refresh rate. Refresh rate may vary on different computers, so any gameplay that you perfect on your development machine may run differently on players machines, giving different speeds / hard to predict bugs. I would strongly advise fixing your tick rate in advance, probably to something a multiple of the common refresh rate, 60fps. So 10tps, 12, 15, 20, 30, 60 might be sensible choices.
  • Secondly, be very wary of using single precision floats to deal with time. My example uses integer math up until the calculation of the interpolation fraction, specifically to avoid precision issues. Engine scripting languages may not offer integer data types, but be especially on the lookout for bugs due to precision issues, consider when the game has been running a long time and the gap between a tick is small relative to the game time, what happens to the precision.

The main problem as Mussi has spotted, is that the interpolation code is roughly correct, however there is no tick (fixed) update. This is probably my fault as I didn't make it clear enough, interpolation should be built on top of a tick update scheme. You are, in this quoted code, doing interpolation twice, once here:


newPosition += direction * Time.deltaTime * speed;

and once here:


Vector3 interpolatedPosition = oldPosition + ((newPosition - oldPosition) * fractionalFrame);

which gives an incorrect result.

The first line is typical of the 'Delta Time' paragraph at the beginning of the blog post. It can work okay in certain situations (like for instance a 2d endless runner camera). If you use this, there's no need to add interpolation on top of this.

The pattern I was explaining is as follows (in pseudocode):


// ticks per second
#define TICK_RATE 10

// time taken by each tick, in seconds
#define TICK_TIME (1.0f / (float) TICK_RATE)

// the name of this may vary with engine, in unity it is simply Update()
void Engine_OncePerFrame_Update()
{
    // calculate number of ticks required
    
    // do 0 to multiple ticks
    for (int n=0; n<iTicksRequired; n++)
    {
        // (move objects with a FIXED delta time, the tick time)
        TickUpdate();
    }

    // Interpolation update
    FrameUpdate();
}

// physics / logic / movement / ai
void TickUpdate()
{
    oldPosition = newPosition;
    newPosition.x += (TICK_TIME * speed);
}

// interpolation of rendered objects for this frame
void FrameUpdate()
{
    interpolatedPosition = oldPosition + ((newPosition - oldPosition) * fInterpolationFraction);
}

Note that the interpolation fraction is the fraction of a TICK, not fraction of a frame.

Note also that this is pseudocode. While you can do all the tick updates yourself, in unity, there is provided a FixedUpdate() function which can be ticked by the engine, which you can use in many circumstances, instead of doing the ticking yourself:


// unity calls update once per frame
void Update()
{
    // interpolate fraction
    float fFraction = (Time.time - Time.fixedTime) / Time.fixedDeltaTime;

    // my frame update for interpolation
    FrameUpdate(fFraction); // as above
}

// unity calls once per tick
void FixedUpdate()
{
    TickUpdate(); // same as above
}

So in unity you can sometimes get away with not calculating the number of ticks yourself, and allowing the engine to do it.

The problem of getting inaccurate timing values from Unity for deltaTime may be a different issue. Essentially you should be able to simply move something across the screen with:


void Update(deltaTime)
{
	pos += deltaTime * speed;
}

and get a reasonable result (barring the issue with frame submit / display times mentioned in the 'Delta Smoothing' final paragraph). If you are still getting bad jitter there, it does suggest an issue with the times being passed.

January 07, 2019 10:01 AM
nucky9
7 hours ago, lawnjelly said:

As Septopus says, this probably would have been better as a fresh forum post linking to the blog lol but we have started so we shall finish! :) 

 

 

Haha, sorry about that! This issue (the Unity deltaTime issue) has been driving me crazy for a few days, and I could only find people reporting the problem, not offering any solutions. I saw this blog, and got excited... I'm actually new to the site and didn't realize posting to the forum would make sense in this case. But again, thanks all for the help!

7 hours ago, lawnjelly said:

There is certainly a lot of info packed in your guys posts, and there does seem to be some confusion. I realise that I haven't made it clear enough in the blog that interpolation is to be used WITH the tick based update, i.e. the first and second pseudocode samples are intended to be used together. I alluded to this in the comment '// ... gameplay ticks' in order to save space but I will edit this to longhand in order to avoid confusion. This is great feedback, something that is obvious to me when writing, but not the reader.

Actually I think your explanation was good. I did try to do as you are saying (using both your snippets and the ref #3), and had something like this (although this is a bit cleaner then it was I think):


using UnityEngine;

public class CameraController : MonoBehaviour
{
    double accumulator;
    float dt;
    float speed = -3f;
    double currentTime;
    Vector3 oldPosition;
    Vector3 newPosition;
    Vector3 direction;

    void Start()
    {
        direction = new Vector3(1, 0, 0);
        dt = 1f / 60;
        oldPosition = transform.position;
        newPosition = transform.position;
        currentTime = Time.time;
    }

    void Update()
    {
        double frameTime = Time.time - currentTime;
        currentTime = Time.time;
        accumulator += frameTime;

        while (accumulator >= dt)
        {
            oldPosition = newPosition;
            newPosition += direction * dt * -speed;
            accumulator -= dt;
        }

        double fractionalFrame = accumulator / dt;
        transform.position = oldPosition + ((newPosition - oldPosition) * (float)(fractionalFrame));
    }
}

However, when I tested it in the editor, it was/is just as bad as using transform.position += direction * Time.deltaTime * speed in Update(). I should have tried testing a build, since it is much better there. Anyway, the combination of it apparently not working, and a lot of frustration resulted in me boiling down the parts I felt most sure of. I think I locked the framerate in to screen refresh as a troubleshooting measure originally (to see if I could get things smoother by going to the 'old ways'), but forgot about the downside, heh.  

7 hours ago, lawnjelly said:

The problem of getting inaccurate timing values from Unity for deltaTime may be a different issue. Essentially you should be able to simply move something across the screen with:



void Update(deltaTime)
{
	pos += deltaTime * speed;
}

and get a reasonable result (barring the issue with frame submit / display times mentioned in the 'Delta Smoothing' final paragraph). If you are still getting bad jitter there, it does suggest an issue with the times being passed.

Yeah, Unity's Update() deltaTime is broken, which causes major jitter if using it (and I think Godot has the same issue). I thought your post was an attempt to fix that issue (or maybe I just wanted to believe that!), but I guess it was more around how to implement your own timekeeping independent of the engine. Of course, doing so does solve the Unity issue as well, but it can also be solved by using a fixed tick that is = 1 / 60 (i.e. a multiple of typical FPS as you say), which is what Update()'s deltaTime should give when vsync is on, and the game logic isn't causing things to chug.

January 07, 2019 05:21 PM
lawnjelly
1 hour ago, nucky9 said:

I thought your post was an attempt to fix that issue (or maybe I just wanted to believe that!)

Yes, I guess wishful thinking lol! :) I do mention it at the end, and the reference 3:

http://frankforce.com/?p=2636

gives some discussion of the problem and possible solutions. Some of it may be a result of how close a frame is submitted to vsync giving stuttered delays.

I'm really not an expert on deltaTime variations, perhaps a question on the forum might get you some good answers? There are quite a few more hardware orientated guys here.

Running in the editor as you say is not a good way to get reliable measures of jitter for at least 2 reasons, afaik.. First is that all sorts of processing is going on in the editor aside from the game which can cause stutters and stalls. Second is that in my experience vsync doesn't tend to work in windowed mode, or at least not as you would expect it to.

Another discussion:

https://medium.com/@alen.ladavac/the-elusive-frame-timing-168f899aec92

http://bitsquid.blogspot.com/2010/10/time-step-smoothing.html

January 07, 2019 06:28 PM
Mussi
3 hours ago, nucky9 said:

Of course, doing so does solve the Unity issue as well, but it can also be solved by using a fixed tick that is = 1 / 60 (i.e. a multiple of typical FPS as you say), which is what Update()'s deltaTime should give when vsync is on, and the game logic isn't causing things to chug.

The main reason to go with a fixed time step is to decouple simulation from rendering so that it becomes stable and gives predictable results. Monitors have varying refresh rates so you can't really rely on vsync, 120 and 144hz are becoming more and more popular and it's not limited to those rates either, some monitors have very weird rates. Imagine being able to run twice as fast because your have a 120hz monitor :)

I'm not sure if the issues you're seeing are due to timing bugs, but your latest example does look correct. Do you see this happening in other games as well?

January 07, 2019 08:20 PM
nucky9
On 1/7/2019 at 1:20 PM, Mussi said:

I'm not sure if the issues you're seeing are due to timing bugs, but your latest example does look correct. Do you see this happening in other games as well?

No, my latest example works very well. There are definitely problems when using an unadjusted deltaTime in update though. Even Unity's own tutorial projects suffer from it, and it is especially bad if Vsync is turned on.

Thanks for the links lawnjelly! Lots to consider in them.

 

 

 

 

January 08, 2019 10:59 PM
ramdy

Wondering if Unreal engine has this same issue?

March 02, 2019 11:17 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement