Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Godot 通知¶
Godot 中的每个对象都实现了 _notification 方法。其目的是允许对象响应可能与之相关的各种引擎级回调。例如,如果引擎告诉 CanvasItem 去“绘制”,则它将调用 _notification(NOTIFICATION_DRAW)。
在所有这些通知之中,有很多类似“绘制”这样经常需要在脚本中去覆盖的通知,多到 Godot 要提供专用函数的地步:
_ready():NOTIFICATION_READY_enter_tree():NOTIFICATION_ENTER_TREE_exit_tree():NOTIFICATION_EXIT_TREE_process(delta):NOTIFICATION_PROCESS_physics_process(delta):NOTIFICATION_PHYSICS_PROCESS_draw():NOTIFICATION_DRAW
用户可能不会意识到 Node 之外的类型也有通知,例如:
Object::NOTIFICATION_POSTINITIALIZE:在对象初始化期间触发的回调。脚本无法访问。
Object::NOTIFICATION_PREDELETE:在引擎删除 Object 之前触发的回调,即析构函数。
并且,在节点中也有很多很有用的回调,但是这些回调不存在专门的方法。
Node::NOTIFICATION_PARENTED: 将子节点添加到另一个节点时,会触发此回调。
Node::NOTIFICATION_UNPARENTED: 将子节点从另一个节点下删除时,会触发此回调。
你可以在通用的 _notification() 方法中访问所有这些自定义通知。
备注
文档中被标记为“virtual”的方法(即虚方法)可以被脚本覆盖重写。
一个经典的例子是 Object 中的 _init 方法。虽然它没有等效的 NOTIFICATION_* 通知,但是引擎仍然会调用该方法。大多数语言(C#除外)都将其用作构造函数。
所以说,应该在哪些情况下使用这些通知或虚函数呢?
_process vs. _physics_process vs. *_input¶
当需要使用“依赖于帧速率的 delta 时间增量”时,请使用 _process 。如果需要尽可能频繁地更新对象数据,也应该在这里处理。频繁执行的逻辑检查和数据缓存操作,大多数都在这里执行。但也需要注意执行频率,如果不需要每帧都执行,则可以选择用定时器循环来替代。
# Allows for recurring operations that don't trigger script logic
# every frame (or even every fixed frame).
func _ready():
var timer = Timer.new()
timer.autostart = true
timer.wait_time = 0.5
add_child(timer)
timer.timeout.connect(func():
print("This block runs every 0.5 seconds")
)
using Godot;
public partial class MyNode : Node
{
// Allows for recurring operations that don't trigger script logic
// every frame (or even every fixed frame).
public override void _Ready()
{
var timer = new Timer();
timer.Autostart = true;
timer.WaitTime = 0.5;
AddChild(timer);
timer.Timeout += () => GD.Print("This block runs every 0.5 seconds");
}
}
当需要与帧速率无关的时间增量时,请使用 _physics_process 。如果代码需要随着时间的推移进行一致的更新,不管时间推进速度是快还是慢,那么就应该在这里执行代码。频繁执行的运动学和对象变换操作,应在此处执行。
为了获得最佳性能,应尽可能避免在这些回调期间进行输入检查。 _process 和 _physics_process 每次都会触发(默认情况下这些更新回调不会 “休眠”)。相反, *_input 回调仅在引擎实际检测到输入的帧上触发。
在 input 回调中同样可以检查输入动作。如果要使用增量时间,则可以使用相关的增量时间获取方法来获取。
# 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())
using Godot;
public partial class MyNode : Node
{
// Called every frame, even when the engine detects no input.
public void _Process(double 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:
if (Input.IsActionJustPressed("ui_accept"))
GD.Print(GetProcessDeltaTime());
break;
}
}
}
_init vs. 初始化 vs. 导出¶
如果脚本初始化它自己的没有场景的节点子树,则该代码将会在 _init() 中执行。其他属性或独立于 SceneTree 的初始化也应在此处运行。
备注
与 GDScript 的 _init() 方法等效的 C# 方法是构造函数。
_init() 在 _enter_tree() 或 _ready() 之前触发,但在脚本创建并初始化其属性之后。实例化场景时,属性值将按照以下顺序设置:
初始值赋值:一个属性被赋值为它的显式默认值,若无指定,则赋予缺省值。如果存在setter函数,它并不会被使用。
``_init()`` 赋值: 某个属性的值被任何在``_init()``中的赋值操作导致变更时,触发其setter函数。
导出值赋值: 一个导出属性的值如再次因Inspector中的任何设值操作导致变更,触发其setter函数。
# test is initialized to "one", without triggering the setter.
@export var test: String = "one":
set(value):
test = value + "!"
func _init():
# Triggers the setter, changing test's value from "one" to "two!".
test = "two"
# If someone sets test to "three" from the Inspector, it would trigger
# the setter, changing test's value from "two!" to "three!".
using Godot;
public partial class MyNode : Node
{
private string _test = "one";
[Export]
public string Test
{
get { return _test; }
set { _test = $"{value}!"; }
}
public MyNode()
{
// Triggers the setter, changing _test's value from "one" to "two!".
Test = "two";
}
// If someone sets Test to "three" in the Inspector, it would trigger
// the setter, changing _test's value from "two!" to "three!".
}
因此,实例化一个脚本而非场景,将同时影响初始化 和 引擎调用setter函数的次数。
_ready、_enter_tree、NOTIFICATION_PARENTED对比¶
首次实例化一个场景并添加到场景树时,Godot将实例化节点(调用 _init)为树,同时自上而下地调用 _enter_tree。当树构建完成后,再自下而上地调用 _ready,最终反向回到树的顶点。
当实例化脚本或独立的场景时,节点不会在创建时被添加到 SceneTree 中,所以未触发 _enter_tree 回调。而只有 _init 调用发生。当场景被添加到 SceneTree 时,才会调用 _enter_tree 和 _ready 。
如果需要触发作为节点设置父级到另一个节点而发生的行为, 无论它是否作为在主要/活动场景中的部分发生, 都可以使用 PARENTED 通知. 例如, 这有一个将节点方法连接到其父节点上自定义信号, 而不会失败的代码段。对可能在运行时创建并以数据为中心的节点很有用。
extends Node
var parent_cache
func connection_check():
return parent_cache.has_user_signal("interacted_with")
func _notification(what):
match what:
NOTIFICATION_PARENTED:
parent_cache = get_parent()
if connection_check():
parent_cache.interacted_with.connect(_on_parent_interacted_with)
NOTIFICATION_UNPARENTED:
if connection_check():
parent_cache.interacted_with.disconnect(_on_parent_interacted_with)
func _on_parent_interacted_with():
print("I'm reacting to my parent's interaction!")
using Godot;
public partial class MyNode : Node
{
private Node _parentCache;
public void ConnectionCheck()
{
return _parentCache.HasUserSignal("InteractedWith");
}
public void _Notification(int what)
{
switch (what)
{
case NotificationParented:
_parentCache = GetParent();
if (ConnectionCheck())
{
_parentCache.Connect("InteractedWith", Callable.From(OnParentInteractedWith));
}
break;
case NotificationUnparented:
if (ConnectionCheck())
{
_parentCache.Disconnect("InteractedWith", Callable.From(OnParentInteractedWith));
}
break;
}
}
private void OnParentInteractedWith()
{
GD.Print("I'm reacting to my parent's interaction!");
}
}