godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.17k stars 98 forks source link

Add a property to make `Engine.time_scale` ignore certain nodes #9068

Open TheContainer opened 9 months ago

TheContainer commented 9 months ago

Describe the project you are working on

A 3D sandbox game where you will be able to slow down time around you but not yourself.

Describe the problem or limitation you are having in your project

With Engine.time_scale, you can slow down time, but you can't add exceptions, which means that not only you as the Player will be in slowmotion speeds (which I don't want), but also every UI element.

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

Each node will have a boolean variable, and when this variable is set to true, the time_scale variable will be ignored, and the node will run at normal speed, similar to process_mode.

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

The variables value can be changed in the property editor of the node, maybe in the "process" tab.

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

For simple animations, tweens for example, you can balance the speed out with some math manually, to give it the illusion of ignoring the time_scale value, but with physics movement for example, you can't.

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

This is very useful overall and belongs to the core in my opinion.

Calinou commented 9 months ago

This was also proposed in https://github.com/godotengine/godot-proposals/issues/7775, but I'll leave this one open as to make the discussion more specific in this proposal.

KoBeWi commented 9 months ago

with physics movement for example, you can't.

You can, by modifying the delta. Though it's not possible with move_and_slide() until https://github.com/godotengine/godot/pull/84665

Mickeon commented 9 months ago

I sorta agree with this, but I wonder if it could be expanded to be a float multiplier, instead of a boolean. The problem with both solutions may hurt performance a bit even for nodes that are set to the default.

thanasip commented 9 months ago

I had a need for this exact thing, and in the end I just did this (I'm in .NET world so I'll show a C# example, but I imagine this is perfectly doable in GDScript)

Create an Autoload (in my example, FrameTimer). Define an event and a DateTime private field:

public partial class FrameTimer : Node
{
    public static event Action<double> FrameRendered;

    private DateTime _lastFrameDt;
}

In that Autoload's _EnterTree(), set your _frameTimeDt initial value, and hook the RenderingServer's FramePostDraw event, which you'll use to calculate the delta:

public override void _EnterTree()
{
    _lastFrameDt = DateTime.Now;

    // Hook the event with a lambda (or assign an Action delegate)
    RenderingServer.FramePostDraw += () => {
        var now = DateTime.Now;
        var delta = (now - _lastFrameDt).TotalSeconds;
        _lastFrameDt = now; // Set your last frame time to now
        FrameRendered?.Invoke(delta); // safely invoke the event (does nothing if not hooked)
    };
}

And that's pretty much it... Now all you have to do is in any script where you want the delta independent of Engine.TimeScale, just define a handler, and in your _EnterTree() or _Ready(), hook the event in FrameTimer (or whatever you called it) and use that handler instead of _Process(double delta):

public partial class SomeClass : Node
{
    /*... other class stuff ...*/

    public override void _Ready()
    {
        FrameTimer.FrameRendered += TimeScaleIndependent_Process;
        // other _Ready() stuff...
    }

    private void TimeScaleIndependent_Process(double delta)
    {
        // whatever you want to do independent of Engine.TimeScale
    }

    /*... other class stuff ...*/
}

The one caveat to this advice: I've literally been using Godot for like, 3 weeks and am not fully aware of any subtleties I may be tripping over - but this seems to be working just fine for me so far.

Calinou commented 9 months ago

I sorta agree with this, but I wonder if it could be expanded to be a float multiplier, instead of a boolean. The problem with both solutions may hurt performance a bit even for nodes that are set to the default.

This was proposed before and rejected: https://github.com/godotengine/godot/pull/29861