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 去执行“绘制”(draw)操作,它就会去调用 _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
What users might not realize is that notifications exist for types other than Node alone, for example:
Object::NOTIFICATION_POSTINITIALIZE:在对象初始化期间触发的回调。脚本无法访问。
Object::NOTIFICATION_PREDELETE: a callback that triggers before the engine deletes an Object, i.e. a "destructor".
而且, 在节点中 确实 存在许多回调, 都没有任何专用方法, 但是它们仍然非常有用.
Node::NOTIFICATION_PARENTED: 任何时候将一个子节点添加到另一个节点时, 都会触发的回调.
Node::NOTIFICATION_UNPARENTED: 任何时候从另一个节点删除一个子节点时, 都会触发的回调.
One can access all these custom notifications from the universal
_notification()
method.
备注
文档中被标记为“virtual”的方法(虚方法)是为使用脚本覆盖准备的。
一个经典的例子是 Object
中的 _init 方法. 虽然它没有等效的 NOTIFICATION_*
, 但是引擎仍然调用该方法. 大多数语言(C#除外)都将其用作构造函数.
那么, 在哪种情况下应该使用这些通知或虚函数呢?
_process vs. _physics_process vs. *_input¶
Use _process()
when one needs a framerate-dependent delta time between
frames. If code that updates object data needs to update as often as
possible, this is the right place. Recurring logic checks and data caching
often execute here, but it comes down to the frequency at which one needs
the evaluations to update. If they don't need to execute every frame, then
implementing a Timer-timeout loop is another option.
# 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")
)
Use _physics_process()
when one needs a framerate-independent delta time
between frames. If code needs consistent updates over time, regardless
of how fast or slow time advances, this is the right place.
Recurring kinematic and object transform operations should execute here.
While it is possible, to achieve the best performance, one should avoid
making input checks during these callbacks. _process()
and
_physics_process()
will trigger at every opportunity (they do not "rest" by
default). In contrast, *_input()
callbacks will trigger only on frames in
which the engine has actually detected the input.
One can check for input actions within the input callbacks just the same. If one wants to use delta time, one can fetch it from the related delta time methods as needed.
# 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. export¶
If the script initializes its own node subtree, without a scene,
that code should execute here. Other property or SceneTree-independent
initializations should also run here. This triggers before _ready()
or
_enter_tree()
, but after a script creates and initializes its properties.
脚本具有三种可能在实例化过程中发生的属性分配方法:
# "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)
using Godot;
public partial 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";
}
}
当实例化一个场景时, 将根据以下顺序设置属性值:
初始值分配:实例化将先赋初值 initialization 或在 _init 方法中赋值。_init 赋值优先级高于 initialization 初值。
导出值赋值:如果从一个场景而不是脚本中实例化,Godot 将用导出的值替换脚本中定义的初始值。
因此,实例化脚本和场景,将影响初始化 和 引擎调用 setter
的次数.
_ready vs. _enter_tree vs. NOTIFICATION_PARENTED¶
When instantiating a scene connected to the first executed scene, Godot will
instantiate nodes down the tree (making _init()
calls) and build the tree
going downwards from the root. This causes _enter_tree()
calls to cascade
down the tree. Once the tree is complete, leaf nodes call _ready
. A node
will call this method once all child nodes have finished calling theirs. This
then causes a reverse cascade going up back to the tree's root.
When instantiating a script or a standalone scene, nodes are not
added to the SceneTree upon creation, so no _enter_tree()
callbacks
trigger. Instead, only the _init()
call occurs. When the scene is added
to the SceneTree, the _enter_tree()
and _ready()
calls occur.
如果需要触发作为节点设置父级到另一个节点而发生的行为, 无论它是否作为在主要/活动场景中的部分发生, 都可以使用 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!");
}
}