Godot通知
Godot中的每个对象都实现 _notification 方法.其目的是允许对象响应可能与之相关的各种引擎级回调.例如,如果引擎告诉一个 CanvasItem 去 绘制
,它将调用 _notification(NOTIFICATION_DRAW)
.
这些通知中的某些通知,例如 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_input()
: NOTIFICATION_INPUT_unhandled_input()
: NOTIFICATION_UNHANDLED_INPUT_draw()
: NOTIFICATION_DRAW
用户可能 不会 意识到,通知仅针对 Node
以外的类型存在:
Object::NOTIFICATION_POSTINITIALIZE:在对象初始化期间触发的回调.脚本无法访问.
Object::NOTIFICATION_PREDELETE: 在引擎删除一个Object(即 ‘destructor’ 析构函数)之前触发的回调.
MainLoop::NOTIFICATION_WM_MOUSE_ENTER:当鼠标进入,显示游戏内容的操作系统窗口时,触发的回调.
而且,在节点中 确实 存在许多回调,都没有任何专用方法,但是它们仍然非常有用.
Node::NOTIFICATION_PARENTED:任何时候将一个子节点添加到另一个节点时,都会触发的回调.
Node::NOTIFICATION_UNPARENTED:任何时候从另一个节点删除一个子节点时,都会触发的回调.
Popup::NOTIFICATION_POST_POPUP: 一个回调,在Popup节点完成任何
popup*
方法后触发.注意与它的about_to_show
信号的区别,后者在*出现之前*触发.
您可以从通用的 _notification
方法,访问所有这些自定义通知.
注解
文档中标记为 虚拟(virtual)
的方法,也打算被脚本重写.
一个经典的例子是 Object
中的 _init 方法.虽然它没有等效的 NOTIFICATION_*
,但是引擎仍然调用该方法.大多数语言(C#除外)都将其用作构造函数.
那么,在哪种情况下应该使用这些通知或虚函数呢?
_process vs. _physics_process vs. *_input
当需要帧之间依赖于帧速率的 deltatime
时,请使用 _process
.如果更新对象数据的代码,需要尽可能频繁地更新,那么这是正确放置这些代码的地方.经常在这里执行循环逻辑检查和数据缓存,但它取决于需要更新估算的频率.如果他们不需要执行每一帧,那么执行一个 Timer-yield-timeout
循环是另一种选择.
GDScript
# 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")
当一个帧之间需要独立于帧速率的 deltatime
时,请使用 _physics_process
.如果不管时间是快还是慢,代码需要随着时间的推移进行一致的更新,那么这是正确的放置这些代码的地方.重复的运动学和对象变换操作,应在此处执行.
为了获得最佳性能,应尽可能避免在这些回调期间,进行输入检查.``_process`` 和 _physics_process
将在每个机会触发(默认情况下它们不会 休息
).相反,``*_input`` 回调仅在,引擎实际检测到输入的帧上触发.
可以同样检查输入回调中的输入动作.如果要使用增量时间,则可以根据需要从相关的增量时间方法中获取它.
GDScript
C#
# 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;
}
}
}
_init vs. initialization vs. export
如果脚本初始化它自己的节点子树,没有场景,代码应该在这里执行.其他属性或独立于 SceneTree
的 initialization
也应在此处运行.这会在 _ready
或 _enter_tree
之前触发,但是会在脚本创建并初始化其属性之后触发.
脚本具有实例化期间,可能发生的三种类型的属性分配:
GDScript
C#
# "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";
}
}
当实例化一个场景时,将根据以下顺序设置属性值:
Initial value assignment:
instantiation
将分配instantiation
值或init
分配值.init
分配的优先级高于initialization
值.导出值赋值:如果从一个场景而不是脚本中实例化,Godot将分配导出的值,来替换脚本中定义的初始值.
因此,实例化脚本和场景,将影响初始化,*和* 引擎调用 setter
的次数.
_ready vs. _enter_tree vs. NOTIFICATION_PARENTED
当实例化连接到第一个执行场景的场景时,Godot将实例化树下的节点(进行 _init
调用),并构建从根向下的树.这导致 _enter_tree
调用,向下级联树.当树构建完成,叶子节点调用 _ready
.一旦所有子节点都完成了对它们的子节点的调用,一个节点就会调用这个方法.然后,这将导致反向级联回到树的根部.
在实例化一个脚本或一个独立场景时,节点不会在创建时添加到场景树,所以没有 _enter_tree
的回调触发器.相反,只发生 _init
和之后的 _ready
调用.
如果需要触发作为节点设置父级到另一个节点而发生的行为,无论它是否作为在主要/活动场景中的部分发生,都可以使用 PARENTED 通知.例如,这有一个将节点的方法,连接到父节点上的自定义信号,而不会失败的代码段.在运行时可能创建的,以数据为中心的节点上有用.
GDScript
C#
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!");
}
}