Godotの通知
Godotのすべてのオブジェクトは _notification メソッドを実装します。その目的は、オブジェクトがそれに関連する可能性のあるさまざまなエンジンレベルのコールバックに応答できるようにすることです。たとえば、エンジンが CanvasItem に「描画(draw)」するように指示すると、_ notification(NOTIFICATION_DRAW) が呼び出されます。
drawなどのこれらの通知の一部は、スクリプトでオーバーライドするのに役立ちます。Godotは、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: エンジンがオブジェクトを削除する前にトリガーされるコールバック。つまり「デストラクタ」です。
また、ノードに存在するコールバックの多くには専用のメソッドはありませんが、それでも非常に便利です。
Node::NOTIFICATION_PARENTED: 子ノードを別のノードに追加するたびにトリガーするコールバック。
Node::NOTIFICATION_UNPARENTED: 別のノードから子ノードを削除するたびにトリガーするコールバック。
これらすべてのカスタム通知には、ユニバーサルな _notification() メソッドからアクセスできます。
注釈
"virtual" というラベルの付いたドキュメント内のメソッドも、スクリプトによってオーバーライドされることを目的としています。
典型的な例は、Objectの _init メソッドです。NOTIFICATION_* に相当するものはありませんが、エンジンはメソッドを呼び出します。ほとんどの言語(C#を除く)は、コンストラクターとして使用します。
それでは、これらの通知や仮想機能のそれぞれをどの状況で使用すべきでしょうか?
_process 対 _physics_process 対 *_input
フレームレートに依存するフレーム間のデルタタイムが必要な場合は _process を使用します。オブジェクトデータを更新するコードをできるだけ頻繁に更新する必要がある場合、これが適切な場所です。繰り返しのロジックチェックとデータキャッシングがここで実行されることがよくありますが、更新するために評価が必要になる頻度になります。すべてのフレームを実行する必要がない場合は、Timer-timeoutループを実装することも別のオプションです。
# 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");
}
}
using namespace godot;
class MyNode : public Node {
GDCLASS(MyNode, Node)
public:
// Allows for recurring operations that don't trigger script logic
// every frame (or even every fixed frame).
virtual void _ready() override {
Timer *timer = memnew(Timer);
timer->set_autostart(true);
timer->set_wait_time(0.5);
add_child(timer);
timer->connect("timeout", callable_mp(this, &MyNode::run));
}
void run() {
UtilityFunctions::print("This block runs every 0.5 seconds.");
}
};
フレームレートに依存しないフレーム間のデルタタイムが必要な場合は _physics_process を使用します。時間の進み具合に関係なく、コードが時間の経過とともに一貫した更新を必要とする場合は、これが適切な場所です。繰り返しのキネマティックおよびオブジェクトのトランスフォーム操作をここで実行する必要があります。
最高のパフォーマンスを得るには、可能な限りこれらのコールバック中に入力チェックを行わないようにする必要があります。_process と _physics_process はあらゆる機会にトリガーします(デフォルトでは「休息」しません)。対照的に、*_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;
}
}
}
using namespace godot;
class MyNode : public Node {
GDCLASS(MyNode, Node)
public:
// Called every frame, even when the engine detects no input.
virtual void _process(double p_delta) override {
if (Input::get_singleton->is_action_just_pressed("ui_select")) {
UtilityFunctions::print(p_delta);
}
}
// Called during every input event. Equally true for _input().
virtual void _unhandled_input(const Ref<InputEvent> &p_event) override {
Ref<InputEventKey> key_event = event;
if (key_event.is_valid() && Input::get_singleton->is_action_just_pressed("ui_accept")) {
UtilityFunctions::print(get_process_delta_time());
}
}
};
_init 対 初期値 対 エクスポート値
スクリプトがシーンなしで独自のノードサブツリーを初期化する場合、そのコードは _init() で実行する必要があります。その他のプロパティまたは SceneTree に依存しない初期化もここで実行する必要があります。
注釈
C#でGDScriptの _init() メソッドに相当するのは、コンストラクタです。
_init() は _enter_tree() または _ready() の前にトリガーされますが、スクリプトがプロパティを作成して初期化した後にトリガーされます。シーンをインスタンス化するときに、プロパティ値は次の順序に従って設定されます。
初期値のセット: プロパティには初期値がセットされます。指定されていない場合はデフォルト値がセットされます。セッターが存在する場合は使用されません。
_init()assignment: the property's value is replaced by any assignments made in_init(), triggering the 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!".
}
using namespace godot;
class MyNode : public Node {
GDCLASS(MyNode, Node)
String test = "one";
protected:
static void _bind_methods() {
ClassDB::bind_method(D_METHOD("get_test"), &MyNode::get_test);
ClassDB::bind_method(D_METHOD("set_test", "test"), &MyNode::set_test);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "test"), "set_test", "get_test");
}
public:
String get_test() { return test; }
void set_test(String p_test) { test = p_test + "!"; }
MyNode() {
// Triggers the setter, changing _test's value from "one" to "two!".
set_test("two");
}
// If someone sets test to "three" in the Inspector, it would trigger
// the setter, changing test's value from "two!" to "three!".
};
その結果、スクリプトとシーンのインスタンス化は、初期化とエンジンがセッターを呼び出す回数の両方に影響する可能性があります。
_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 bool ConnectionCheck()
{
return _parentCache.HasUserSignal("InteractedWith");
}
public override void _Notification(int what)
{
switch ((long)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!");
}
}
using namespace godot;
class MyNode : public Node {
GDCLASS(MyNode, Node)
Node *parent_cache = nullptr;
void on_parent_interacted_with() {
UtilityFunctions::print("I'm reacting to my parent's interaction!");
}
public:
void connection_check() {
return parent_cache->has_user_signal("interacted_with");
}
void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PARENTED:
parent_cache = get_parent();
if (connection_check()) {
parent_cache->connect("interacted_with", callable_mp(this, &MyNode::on_parent_interacted_with));
}
break;
case NOTIFICATION_UNPARENTED:
if (connection_check()) {
parent_cache->disconnect("interacted_with", callable_mp(this, &MyNode::on_parent_interacted_with));
}
break;
}
}
};