戈多 4.0。如何停止自动调用 SceneTreeTimer?

Godot 4.0. how stop a auto call SceneTreeTimer?

我有这个代码:

extends Area2D

func _ready() -> void : auto_call()
    
func auto_call() : 
    await get_tree().create_timer(1, false).timeout
    print( "area autocall" )
    auto_call()

func stop_auto_call() : 
    auto_call = null # how stop?

我知道,我可以使用两个选项:创建一个普通的 Timer 或将引用保存在全局数组中以供稍后停止 it/them,但是...可能需要 在几个节点中进行了大量重构。

我正在尝试 desactivepause 节点和代码工作...减去 auto_call 调用。 我尝试使用 unreference()null、停止:processinputphysics。但是什么也没有。我看到 get_tree().get_processed_tweens() 有点希望,但 SceneTreeTimer 不存在任何希望。任何人都知道该怎么做、替代方案或想法?

Godot 有一个暂停系统。您可以暂停 SceneTree by setting its pausedtrue:

get_tree().paused = true

暂停或不暂停时执行哪些脚本取决于 process_mode

此外,任何SceneTreeTimer created with always_process set to false (which you specify as second argument of create_timer),在场景树暂停时都不会运行。

但是,这种方法也会影响暂停系统的任何其他工作,并且不会为您提供仅暂停其中一些计时器的粒度。


您也可以使用 Timers:

var timer := Timer.new()
add_child(timer)
timer.wait_time = 1.0
timer.one_shot = true
timer.start()
await timer.timeout
timer.queue_free()

或者像这样:

var timer := Timer.new()
add_child(timer)
timer.wait_time = 1.0
timer.one_shot = true
timer.connect("timeout", timer.queue_free, [], CONNECT_ONESHOT)
timer.start()
await timer.timeout

然后暂停它们,你可以得到每个 Timer children,没有排队等待删除,然后暂停它:

for node in get_children():
    var timer:Timer = node as Timer
    if !is_instance_valid(timer) or timer.is_queued_for_deletion():
        continue

    timer.paused = true

请注意,这将占用所有 children Timers。这可能比你想要的更多。

这将允许您仅暂停特定 Node 的计时器。


我们可以创建一个 Autoload。我称之为 TimerRoot。使用具有上述类似代码的脚本:

extends Node

func delay(wait_time:float) -> void:
    var timer := Timer.new()
    add_child(timer)
    timer.wait_time = 1.0
    timer.one_shot = true
    timer.start()
    await timer.timeout
    timer.queue_free()

func set_paused(pause:bool) -> void:
    for node in get_children():
        var timer:Timer = node as Timer
        if !is_instance_valid(timer) or timer.is_queued_for_deletion():
            continue

        timer.paused = pause

您可以像这样使用 await TimerRoot.delay(1.0) 并像这样暂停它们 TimerRoot.set_paused(true)


或者您可以 return Timer:

func delay(wait_time:float) -> Timer:
    var timer := Timer.new()
    add_child(timer)
    timer.wait_time = 1.0
    timer.one_shot = true
    timer.connect("timeout", timer.queue_free, [], CONNECT_ONESHOT)
    timer.start()
    return Timer

并像这样使用它:await TimerRoot.delay(1.0).timeout


另一个变体是添加一个 parent 参数,并让 delay 添加定时器作为 child 到它。这让您恢复了仅暂停 Node.

Timer 的粒度

顺便说一下,在这段代码中我们暂停了。你可以实现一个取消机制,你可以在你调用 delay 的地方添加一个 if 来检查它是否被取消。如果您正在 returning Timer,您可以附加一个带有 cancelled 属性 的脚本(这也有助于我们识别哪些是正确的计时器)。这建议我们从自动加载更改为 class:

class_name OneShotTimer extends Timer

@export
var cancelled:bool:
    get: return cancelled
    set(_value): pass

func _ready() -> void:
    one_shot = true
    connect("timeout", queue_free, [], CONNECT_ONESHOT)
    start()

func set_cancelled() -> void:
    cancelled = true
    emit_signal("timeout")

static func delay(parent:Node, time:float) -> bool:
    if !is_instance_valid(parent):
        push_error("Parent Node is not valid.")

    if !parent.is_inside_tree():
        push_error("Parent Node is not in the scene tree.")

    var timer := OneShotTimer.new()
    timer.wait_time = time
    parent.add_child(timer)
    await timer.timeout
    return !timer.cancelled

static func set_paused(parent:Node, pause:bool) -> void:
    for node in parent:
        var timer:OneShotTimer = node as OneShotTimer
        if !is_instance_valid(timer) or timer.is_queued_for_deletion():
            continue
        
        timer.paused = pause

static func cancel(parent:Node) -> void:
    for node in parent:
        var timer:OneShotTimer = node as OneShotTimer
        if !is_instance_valid(timer) or timer.is_queued_for_deletion():
            continue
        
        timer.set_cancelled()

你可以这样使用:

if await OneShotTimer.delay(self, 1.0):
    print("OK")
else:
    print("Aborted operation")

并像这样暂停一个节点的所有计时器OneShotTimer.set_paused(self, true)。要取消它们,您需要 OneShotTimer.cancel(self)

如果您既想要粒度又想要全局暂停或取消它们的能力,我们可以用不同的代码恢复自动加载 TimerRoot

extends Node

var _timers:Array

func register(timer:OneShotTimer) -> void:
    if is_instance_valid(timer) and !timer.is_queued_for_deletion():
        _timers.append(timer)
        timer.connect("timeout", func(): _timers.erase(timer), [], CONNECT_ONESHOT)

func cancel() -> void:
    for timer in _timers:
        timer.set_cancelled()

func set_paused(pause:bool) -> void:
    for timer in _timers:
        timer.paused = pause

然后在OneShotTimer上的_ready上添加TimerRoot.register(self),这样他们就都注册好了。然后你可以用 TimerRoot.set_paused(true) 暂停所有或用 TimerRoot.cancel().

取消所有

但那是取消。这并没有真正停止。停止是一个问题,因为您有一些代码在等待它。如果你同意他们无限期地等待信号,你可以简单地用 queue_free.

删除计时器

考虑只使用补间动画?如果计时器具有相同的功能会很好但是...

extends Node

var _tween: Tween

func _ready() -> void:
    # every 1 second call _auto_call
    _tween = create_tween().set_loops()
    _tween.tween_callback(_auto_call).set_delay(1)
    # cancel after 10 seconds
    await get_tree().create_timer(10).timeout
    _stop_auto_call()

func _auto_call():
    print( "area autocall" )

func _stop_auto_call() :
    _tween.kill()