Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add improved support for unscaled time #7775

Open
donovani opened this issue Sep 21, 2023 · 5 comments
Open

Add improved support for unscaled time #7775

donovani opened this issue Sep 21, 2023 · 5 comments

Comments

@donovani
Copy link

donovani commented Sep 21, 2023

Describe the project you are working on

2.5D Party Game, Not Important.
For the sake of this I'm using C#, but I'd love to see it for all languages.

Describe the problem or limitation you are having in your project

I have several scripts that need to work in unscaled time (AKA: not multiplied by Engine.TimeScale); among these, UI components, debugging systems, & a service to interpolate Engine.TimeScale. Unscaled time is really handy in a lot of cases, but Godot does not provide it to Nodes.

What can you do about it in Godot right now?

  1. Divide deltaTime by Engine.TimeScale in your Node lifecycle methods

As I understand there's no guarantee Engine.TimeScale hasn't been modified between completion of render/physics timestep and the the point in time that the Node processes. This could theoretically lead to inaccuracies. Additionally, it falls apart when Engine.TimeScale is 0

public void _Process(float deltaTime)
{
  // Fallback to deltaTime if impossible
  var unscaled = (Engine.TimeScale == 0) ? deltaTime : ((float)(deltaTime / Engine.TimeScale));
}
  1. Manually track it through the system time

While this no longer tracks the actual deltaTime (as it instead measures the time between that Node's updates), it guarantees it's detached from Engine.TimeScale. However, making this requires a lot more code once you have to start considering Nodes being disabled, paused, etc.

DateTime lastUpdated = DateTime.Now;

public void _Process(float deltaTime)
{
  var now = DateTime.Now;
  var unscaled = (now - lastUpdated).TotalSeconds;
  lastUpdated = now;
}

Both of these are serviceable for 80% of cases, but rather than estimate & potentially introduce inaccuracies, I think it makes sense to instead just have it provided by the engine.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

The solution for this is that the engine should provide an authoritatively true unscaled delta time if the developer needs it. The exact mechanism isn't exactly important to me, so long as there's a simple, easily accessible API.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

There are a few potential methods of implementation that I imagine. All of which could be done in a way to be non-breaking.

At the very minimum, I think there should be a global way of getting the current deltaTime - both scaled and unscaled.

float scaled = Engine.GetDeltaTime();
float unscaled = Engine.GetUnscaledDeltaTime();

Another option would to be introduce new methods that can be extended, which offer unscaled time instead.

// As unique methods
public void _UnscaledProcess(float deltaTime);
public void _UnscaledPhysicsProcess(float deltaTime);
// As method overloads (less of a fan of this)
public void _Process(float deltaTime);
public void _Process(float deltaTime, float unscaledDeltaTime);
public void _PhysicsProcess(float deltaTime);
public void _PhysicsProcess(float deltaTime, float unscaledDeltaTime);

Stretch Goal: If we want to make unscaled time more of a first-class citizen. We might also want to consider adding a scaledTime flag on Nodes that change over time - like timers & animations.

If this enhancement will not be used often, can it be worked around with a few lines of script?

Yes.

  • If we add a static method like Engine.GetUnscaledDeltaTime() users can use it, but it does not force its use.
  • If we offer unique methods in Node like _UnscaledProcess() they do not have to extend them.
  • If we offer unique overloads to lifecycle functions, they do not have to extend them.

Is there a reason why this should be core and not an add-on in the asset library?

Unscaled time has tons of uses - from UIs, debugging, efects, animations, and gameplay. Rather than have devs jump through hoops to estimate this value, it should simply be available. It already has that value calculated at some point, as it's used to calculate the scaled time. It just seems to be a matter of exposing it in the API.

@Calinou
Copy link
Member

Calinou commented Sep 21, 2023

We might also want to consider adding a scaledTime flag on Nodes that change over time - like timers & animations.

Note that similar proposals have been made in the past, and were rejected: #2507

These proposals were about using arbitrary time scale values for any node, rather than only making specific things able to count in unscaled time (which should be simpler and have fewer edge cases).

@donovani
Copy link
Author

Interesting. I think the idea of having time scale per-node could be really powerful, but I can potentially see why they might have been rejected. That said, I'm not sure I agree with the philosophy of "you can calculate it yourself so it's not necessary" - as brought up in 2507.

  • As mentioned above I believe manual time calculations within Nodes are at best estimates.
  • Given that they're estimates, there's room for error, & implementations can be inconsistent across a project.
  • Having to manually calculate unscaled time is something many games have to do, & can be seen as boilerplate.
  • It just makes the developer experience easier to not have to repeatedly calculate it.
  • Other engines support unscaled time out of the box.

@MarkoSFG
Copy link

MarkoSFG commented Dec 27, 2023

Time.deltaTime and Time.unscaledDeltaTime should exist; this doesn't bloat any base classes and is a clean solution. Time.time (timeScaled float seconds since app opened) should exist too. I'm currently migrating a project from unity and for now I'll have to hack in the missing Time functionality; all these values are currently used by various different parts of the game (same as many other released games I've worked on); including game logic.

@Calinou
Copy link
Member

Calinou commented Jan 3, 2024

Time.time (timeScaled float seconds since app opened) should exist too.

This is already available as Time.get_ticks_usec() * 0.000001 🙂

Time.get_ticks_msec() * 0.001 can also be used, but is slightly less accurate.

@MarkoSFG
Copy link

MarkoSFG commented Jan 3, 2024

Time.time (timeScaled float seconds since app opened) should exist too.

This is already available as Time.get_ticks_usec() * 0.000001 🙂

Time.get_ticks_msec() * 0.001 can also be used, but is slightly less accurate.

I thought this returned unscaled time; unaffected by Engine.timeScale. Yeah I just double checked the source; from what I can see (for windows) it does QueryPerformanceCounter in OS_Windows::get_ticks_usec(). In order to get scaled time since startup you'd need to accumulate ticks multiplied by timescale each frame as the timescale can change each frame (or similar).

(and if we had get_ticks_usec() return scaled time we would also need an unscaled version, both are needed)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants