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 认识到除了节点以外的其他类型存在通知:
Object::NOTIFICATION_POSTINITIALIZE :在对象初始化期间触发的回调。脚本无法访问。
Object::NOTIFICATION_PREDELETE :在引擎删除对象(即“析构函数”)之前触发的回调。
MainLoop::NOTIFICATION_WM_MOUSE_ENTER :当鼠标进入显示游戏内容的操作系统窗口时触发的回调。
还有很多回拨 do 存在于节点中没有任何专用方法,但仍然非常有用。
Node::NOTIFICATION_PARENTED :一个回调,它在任何时候触发一个将子节点添加到另一个节点的回调。
Node::NOTIFICATION_UNPARENTED :每次从另一个节点删除子节点时触发的回调。
Popup::NOTIFICATION_POST_POPUP :在弹出节点完成任何
popup*
方法。注意与它的区别about_to_show
触发信号 之前 它的外观。
您可以从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";
}
}
在实例化场景时,将根据以下顺序设置属性值:
初始值分配: 实例化将分配初始化值或初始分配值。初始化分配优先于初始化值。
导出值分配: 如果是从场景而不是脚本进行实例化,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!");
}
}