- Home /
How to shape time however I want?
I'm currently developing some kind of 2D ecossystem with animals, plants etc, and I would like to skip, speed up and down time however I want. How can I do that? I thought about creating some kind of a tick system but I don't know how to get more than 60 or hundreds ticks per real life second.
Answer by kromenak · Jan 09, 2020 at 09:09 PM
Unity uses what's called a "variable time step." At the beginning of each frame, the amount of time passed since the beginning of the last frame is calculated (the "delta time" value). This value is then used to update the state of the objects in the simulation. Finally, the state of the simulation is rendered to screen.
One thing you'll notice about the above approach is that each time you update the simulation (let's call that a "tick"), you use the amount of real-world time that was measured to have passed since the last tick, and you only do one tick per render. As a result, the game appears to run at a speed equal to real-time.
One thing you could do, rather than calculating the amount of time at the beginning of each frame, is to simply ASSUME that a certain delta time will occur. For example, we might say assume that the delta time for each frame is always 33ms. If the REAL amount of time that passes per tick is greater than 33ms, our game appears to run in fast motion. In the REAL amount of time that passes per tick is less, our game appears to run in slow motion. The problem with this is you can't choose whether the game runs in slow or fast motion!
The common solution (and my suggested approach) is to disconnect the number of simulation "ticks" from rendering. You can do this fairly easily with a structure like this:
for(int i = 0; i < tickCount; i++)
{
Tick();
}
Render();
In Unity, Monobehaviors always update once per render, so you'd need to do something like this:
public event System.Action Tick();
private void Update()
{
// TODO: Calculate desired tick count
for(int i = 0; i < tickCount; i++)
{
Tick();
}
}
You might call a Monobehavior like this a "TickManager" or similar. Any other object in your game can use the "Tick" event to update at the correct speed.
The next question is: how do you calculate "tickCount"? That depends on your needs! For example, I had a game where we could only achieve 30FPS on mobile, but we needed to simulate at 60FPS for animations to look correct. So, we did something like this:
accumulatedDeltaTime += Time.deltaTime;
int tickCount = accumulatedDeltaTime / 0.016f; // how many 16ms ticks?
accumulatedDeltaTime -= tickCount * 0.016f;
If your goal is simply fast-forward or slow-motion, you could try to simply tick more or less often. Using the above logic, if your "normal" tick rate is 16ms, you could double that for slow motion or halve it for fast motion. You'd still assume a tick is 16ms, even if you are calling Tick more or less frequently than that! Another method for slow motion is to simply skip ticking in some cases (e.g. skip every odd tick in the tick loop).
If you want to "skip" some amount of time ahead, you simply need to call Tick() enough times in a row before rendering. For example, to skip 60s ahead you could call tick (60 * 1000 / 16) 3,750 times in a row. Of course, you'd need to make sure that your ticks can complete in a very short amount of time for this to work, or you'd have a noticeable performance hitch while doing this loop.
It's also worth noting that Unity has a "fixed timestep" system that does some of the above logic, if you want to use that. However, I think you get more control over how the logic works if you write something like the above instead.
Loved your awnser, thank you very much. Just a little question, how did you choose the number 0.16 for achieving 60 FPS.
You can calculate the milliseconds required for a certain FPS using this equation:
s = 1 / FPS
So, 1/60 is 0.01666666667 seconds - multiply by 1000 to get 16.666ms. People often shorten this to just 16ms, but I suppose if you round up it'd actually be 17ms!
Likewise, at 30FPS, 1/30 gives you 0.03333 seconds, or about 33ms.
This also works in the other direction: So, 1 / 0.0166666 ~= 60.
While your answer is quite detailed, it has several flaws. First of all your common solution is not "disconnected" from rendering considering that your tick loop is just part of the main game loop which runs once per frame just like your example when using Unity's Update method. What could make it "sort of" independent is calculating
Your "tickcount" calculation would be the important part to actually make it frame independent. Though from your code snippets it's not clear what datatype you used for your tickcount variable. If it's a float, that wouldn't really work since your code perfectly cancels the increase and decrease so each frame accumulatedDeltaTime will be back to 0. Just have a look
accumulatedDeltaTime += Time.deltaTime;
tickCount = accumulatedDeltaTime / 0.16f;
accumulatedDeltaTime -= tickCount * 0.16f;
Just sustitude tickCount in the 3rd line and you get
accumulatedDeltaTime -= (accumulatedDeltaTime / 0.16f) * 0.16f;
simplify and you get
accumulatedDeltaTime -= accumulatedDeltaTime;
Which is the same as setting accumulatedDeltaTime back to 0. I don't really see any reason why you need to calculate a tickcount in the first place. I guess you wanted to floor the tickcount to only subtract "whole" ticks.
Next issue is if you want 60 fps you probably wanted to use 16.666666ms which is 0.01666666. So you're off by one order of 10 and you're missing the recurring digits. Using 0.16 would equal 6,25 frames / ticks per second.
You can simply use my CustomFixedUpdate which does the same thing that Unity does for its own FixedUpdate. Though this works independent and you can have as many of those as you like. However keep in $$anonymous$$d that high simulation speeds can severely impact performance. If you usually run your simulation at about 60 ticks per second, if you want to speed up time be a factot of 100 you will call your tick method 6000 times per second.
Note that just throttling the tick count doesn't work very well with slowing down the time. You get very choppy results. That's why you usually just want to calculate everything based on deltaTime. Unity has the timeScale which is factored in in deltaTime. However you can always introduce your own additional scale factor. However what to do when highly depends on what things you actually have that depend on "time".
Oops, you're right - I should be using 0.016f in my examples - I'll fix that.
While the solution above doesn't "disconnect" from rendering per se, it does allow you to "tick" your simulation more than once per render (or less than once per render).
"tickCount" should be an integer or long variable, since we can't really do anything with a partial tick - my code could be clearer about that for sure! The idea of the code would be to "save" fractions of a tick for future update loops. For example, if "deltaTime" is 0.035 seconds, and we want to tick every 0.016 seconds, we should tick two times on that frame and accumulate 0.003 seconds towards the next update loop.
The CustomFixedUpdate system seems like a good object-oriented approach to the problem! Being able to create several instances at different rates seems helpful.
Answer by RendrWyre · Jan 09, 2020 at 08:40 PM
https://docs.unity3d.com/ScriptReference/Time-timeScale.html
You could use timeScale. By default it's 1 so if you changed it programatically to 2 then you effectively speed up time (x2 'normal' time) . Setting it to 0.5 would "slow down" time. (x0.5 'normal' time).
Also, re: ticks. Here is how you might "tick" once per second. If you reduced timeScale to 0.5 then it should tick twice per "real life second".
Update()
{
if(!ticking)
{
ticking = true;
Debug.Log("Tick");
yield return new WaitForSeconds(1f);
ticking = false
}
}
I think timeScale can also be a good way to solve this problem, for sure. A nice thing about using timeScale is how you just have to change the one number to see a result!