godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.14k stars 93 forks source link

Add step signal to Timer #5213

Open MaaaxiKing opened 2 years ago

MaaaxiKing commented 2 years ago

Describe the project you are working on

preparing user for game which is about to start—telling the user he should get ready

Describe the problem or limitation you are having in your project

I can not easily get the time left of a running timer for a certain time. Long ago, I tried to use

    while (GetNode<Timer>("Start").TimeLeft >= 1.0) // entire program hang
        GetNode<Label>("CanvasLayer/Time/Label").Text = Math.Round(GetNode<Timer>("Start").TimeLeft).ToString();

but the entire program began to hang immediately. Time is a ColorRect, by the way. I do not want to fetch the time left in _Process because I would have to check if the timer is running or so. So I came up with the following workaround, although it does not work as intended, yet.

    float TimeLeftToStart = 3;
    [Signal]
    delegate void TimeToStartIsOver();
    …
    public override void _Ready()
    {
        …
            Connect(nameof(TimeToStartIsOver), this, nameof(Start));
            Countdown();
    }
    async void Countdown()
    {
        GetNode<Timer>("Start").WaitTime = 1;
        GetNode<Timer>("Start").Start();
        for (byte i = 0; i < 2; i++)
        {
            await ToSignal(GetNode<Timer>("Start"), "timeout");
            GetNode<Timer>("Start").WaitTime = 1;
            GetNode<Timer>("Start").Start();
        }
        GetNode("Start").QueueFree();
        EmitSignal(nameof(TimeToStartIsOver));
    }
…
    public void On_Start_timeout()
    {
        TimeLeftToStart--;
        GetNode<Label>("CanvasLayer/Time/Label").Text = TimeLeftToStart.ToString();
    }
    public void Start()
    {
        …
    }

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

You could fetch the time left of a running timer easily, so you would not need a workaround.

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

using the signal step as provides Tween

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

depends on how you define “a few”

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

yes: It were extremely useful in every kind of project.

Calinou commented 2 years ago

This was proposed in the past and rejected: https://github.com/godotengine/godot/pull/24208

Signals are not designed to be used in scenarios where they would be emitted every frame, as this is inefficient. Use polling instead (get_time_left()).

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

depends on how you define “a few”

This can be worked around with a few lines of code, as explained in https://github.com/godotengine/godot/pull/24208#issuecomment-445291476:

extends Timer

signal tick

func _process(delta):
    if !is_stopped():
        emit_signal("tick")
Mickeon commented 2 years ago

Although it's about SceneTreeTweens, this comment proposes another workaround, if it has to be done this way.

var tween = create_tween()
tween.tween_property(object, "some_property", 100.0, 2.0)
tween.parallel().tween_method(object, "step", 0.0, 1.0, 2.0)

func step(value):
    # the value represents animation progress
Zireael07 commented 2 years ago

Please please put either of those (or both) somewhere in docs :)

seppoday commented 2 years ago

Although it's about SceneTreeTweens, this comment proposes another workaround, if it has to be done this way.

var tween = create_tween()
tween.tween_property(object, "some_property", 100.0, 2.0)
tween.parallel().tween_method(object, "step", 0.0, 1.0, 2.0)

func step(value):
    # the value represents animation progress

Just a quick sidenote for newbs like me :P.

tween_method has 4 arguments not 5 (you just don't write object at begining), and you call method: Callable so without quotation marks.

tween_method(method_name, from, to, duration)

so in this case:

tween_method(step, 0.0, 1.0, 2.0)

Mickeon commented 2 years ago

Please please put either of those (or both) somewhere in docs :)

First example proposed by @Calinou is more suited for beginners and therefore the Docs. But honestly I'm not sure it would be wise, since Timers aren't really... designed to emit a signal on every step. At worst, they're meant to be looked at, every frame, by the Object in control of whatever significant action they are related to, through the time_left property.

That's not to say programmers shouldn't have the freedom to make a custom Timer script if they want to, but it's not quite... great practice I would encourage.