Godot通知

Godot中的每个对象都实现了 _notification 方法。它的目的是允许对象响应与之相关的各种引擎级回调。例如,如果引擎 CanvasItem 要“画”,它将调用 _notification(NOTIFICATION_DRAW) .

其中一些通知(如draw)对于在脚本中重写非常有用。如此之多以至于Godot向他们中的许多人展示了专门的功能:

  • _ready() :通知就绪

  • _enter_tree() :通知输入树

  • _exit_tree() :通知退出树

  • _process(delta) :通知进程

  • _physics_process(delta) :通知物理过程

  • _input() :通知输入

  • _unhandled_input() :通知u未处理的u输入

  • _draw() :通知绘制

哪些用户可能 not 认识到除了节点以外的其他类型存在通知:

还有很多回拨 do 存在于节点中没有任何专用方法,但仍然非常有用。

您可以从Universal访问所有这些自定义通知 _notification 方法。

注解

文档中标记为“virtual”的方法也将被脚本覆盖。

一个典型的例子是 _init 对象中的方法。虽然它没有 NOTIFICATION_* 等价的,引擎仍然调用该方法。大多数语言(C_除外)都依赖它作为构造函数。

那么,在哪种情况下应该使用这些通知或虚拟函数呢?

_过程与物理过程与 * _input

使用 _process 当一个人需要帧之间依赖于帧速率的增量时。如果更新对象数据的代码需要尽可能频繁地更新,那么这是正确的地方。循环逻辑检查和数据缓存经常在这里执行,但归根结底,就是需要更新评估的频率。如果他们不需要执行每一帧,那么实现一个timer yield timeout循环是另一个选项。

# Infinitely loop, but only execute whenever the Timer fires.
# Allows for recurring operations that don't trigger script logic
# every frame (or even every fixed frame).
while true:
    my_method()
    $Timer.start()
    yield($Timer, "timeout")

使用 _physics_process 当一个人需要帧间独立的增量时。如果代码随着时间的推移需要一致的更新,不管时间前进有多快或有多慢,这是正确的地方。这里应该执行循环的运动学和对象转换操作。

虽然有可能实现最佳性能,但是应该避免在这些回调过程中进行输入检查。 _process_physics_process 每次机会都会触发(默认情况下不会“休息”)。相反, *_input 回调仅在引擎实际检测到输入的帧上触发。

您可以检查输入回调中的输入操作。如果你想使用delta时间,你可以根据需要从相关的delta time方法中获取它。

# Called every frame, even when the engine detects no input.
func _process(delta):
    if Input.is_action_just_pressed("ui_select"):
        print(delta)

# Called during every input event.
func _unhandled_input(event):
    match event.get_class():
        "InputEventKey":
            if Input.is_action_just_pressed("ui_accept"):
                print(get_process_delta_time())
public class MyNode : Node
{

    // Called every frame, even when the engine detects no input.
    public void _Process(float delta)
    {
        if (Input.IsActionJustPressed("ui_select"))
            GD.Print(delta);
    }

    // Called during every input event. Equally true for _input().
    public void _UnhandledInput(InputEvent event)
    {
        switch (event)
        {
            case InputEventKey keyEvent:
                if (Input.IsActionJustPressed("ui_accept"))
                    GD.Print(GetProcessDeltaTime());
                break;
            default:
                break;
        }
    }

}

_初始化vs.初始化vs.导出

如果脚本在没有场景的情况下初始化自己的节点子树,那么应该在此处执行该代码。其他与属性或scenetree无关的初始化也应在此处运行。这在之前就触发了 _ready_enter_tree ,但在脚本创建并初始化其属性之后。

脚本有三种类型的属性分配,可以在实例化期间发生:

# "one" is an "initialized value". These DO NOT trigger the setter.
# If someone set the value as "two" from the Inspector, this would be an
# "exported value". These DO trigger the setter.
export(String) var test = "one" setget set_test

func _init():
    # "three" is an "init assignment value".
    # These DO NOT trigger the setter, but...
    test = "three"
    # These DO trigger the setter. Note the `self` prefix.
    self.test = "three"

func set_test(value):
    test = value
    print("Setting: ", test)
public class MyNode : Node
{
    private string _test = "one";

    // Changing the value from the inspector does trigger the setter in C#.
    [Export]
    public string Test
    {
        get { return _test; }
        set
        {
            _test = value;
            GD.Print("Setting: " + _test);
        }
    }

    public MyNode()
    {
        // Triggers the setter as well
        Test = "three";
    }
}

在实例化场景时,将根据以下顺序设置属性值:

  1. 初始值分配: 实例化将分配初始化值或初始分配值。初始化分配优先于初始化值。

  2. 导出值分配: 如果是从场景而不是脚本进行实例化,godot将指定导出的值以替换脚本中定义的初始值。

因此,实例化脚本与场景都会影响初始化 and 引擎调用setter的次数。

_就绪vs.进入树vs.通知父级

当实例化连接到第一个执行场景的场景时,godot将实例化树下的节点(使 _init 调用)并从根开始向下构建树。这导致了 _enter_tree 呼叫从树上飘落下来。树完成后,叶节点调用 _ready . 一旦所有子节点都完成调用,节点将调用此方法。这将导致反向级联返回到树的根。

在实例化脚本或独立场景时,节点不会在创建时添加到场景中,因此不会 _enter_tree 回调触发。相反,只有 _init 后来呢 _ready 调用发生。

如果需要触发作为另一个节点的父节点发生的行为,无论它是否作为主/活动场景的一部分发生,都可以使用 PARENTED 通知。例如,这里有一个片段,它将节点的方法连接到父节点上的自定义信号,而不会失败。对于运行时可能创建的以数据为中心的节点很有用。

extends Node

var parent_cache

func connection_check():
    return parent.has_user_signal("interacted_with")

func _notification(what):
    match what:
        NOTIFICATION_PARENTED:
            parent_cache = get_parent()
            if connection_check():
                parent_cache.connect("interacted_with", self, "_on_parent_interacted_with")
        NOTIFICATION_UNPARENTED:
            if connection_check():
                parent_cache.disconnect("interacted_with", self, "_on_parent_interacted_with")

func _on_parent_interacted_with():
    print("I'm reacting to my parent's interaction!")
public class MyNode : Node
{
    public Node ParentCache = null;

    public void ConnectionCheck()
    {
        return ParentCache.HasUserSignal("InteractedWith");
    }

    public void _Notification(int what)
    {
        switch (what)
        {
            case NOTIFICATION_PARENTED:
                ParentCache = GetParent();
                if (ConnectionCheck())
                    ParentCache.Connect("InteractedWith", this, "OnParentInteractedWith");
                break;
            case NOTIFICATION_UNPARENTED:
                if (ConnectionCheck())
                    ParentCache.Disconnect("InteractedWith", this, "OnParentInteractedWith");
                break;
        }
    }

    public void OnParentInteractedWith()
    {
        GD.Print("I'm reacting to my parent's interaction!");
    }
}