【Godot】シーンとノードについて

この記事では、シーンとノードについて個人的に調べたことを書いていきます。

シーンの構成

シーンは、1つの「ルート」ノードと、それにぶら下がる「子ノード」から構成されます。

上記画像は以下のような構成となっています。

シーン構成
Main
 +-- bg_back
 +-- player
 +-- TakoLayer
      +-- tako
      +-- tako1
      +-- tako2

同一階層には同名のオブジェクトは配置できない

また同一階層のオブジェクト名は「違う名前」でなければなりません

そのため、例えば以下の構成にはできません。(※シーンエディタ上でこのように名前変更しようとしても、自動で連番にリネームされます)

無効なシーン構成
// × この構成にはできない
Main
 +-- tako
 +-- tako
 +-- tako

ただし、異なる階層であれば同名のオブジェクトにすることが可能です。

階層が異なれば同名のノードも可能
// ○ 異なる階層であれば同名のオブジェクトは存在できる
Main
 +-- tako ← "tako" 同一シーンに2つ存在する
 +-- TakoLayer
      +-- tako ← "tako" 同一シーンに2つ存在する
      +-- tako1

オブジェクトの実行順

オブジェクトの実行順は、ルート以外の各オブジェクトが おおよそ 上から順に実行されます(多少例外があるみたい?)

この構成であれば実行順は以下のとおりです。

  1. bg_back
  2. player
  3. TakoLayer
    1. tako
    2. tako2
    3. tako3
  4. 最後に Main が実行される

Mainが最初に実行されるわけではないことに注意が必要そうです。

ただ、個人的にはノードの実行順に依存するような実装にはできるだけしないほうが、安全な作りになる気がしています。(よくわからない問題が起きにくくなるため)

シーンの描画順

シーンの描画順は基本的にノードの並び順で描画されるようです。
Mainが最後に描画、ということはなさそうです。

また、z_index や CanvasLayer で描画順を細かく制御可能です。
そのあたりについては以下の記事に書きましたので、ここでは省略します。

【Godot】2Dゲームの描画順の制御方法

実行中のノードを確認する

実行中のノードの状況を確認する方法についてです。

テスト用のインスタンスとして “TestNode2D” というシーンを作成し、それをMainノードから生成するようにしてみます。

extends Node2D

onready var TestNode2D = preload("res://TestNode2D.tscn")

func _process(delta):
    if Input.is_action_just_pressed("ui_accept"):
        # Spaceキーで "TestNode2D" を生成
        var test = TestNode2D.instance()
        add_child(test)

実行して、シーンツリーにある「リモート」をクリックすると現在のシーンに存在するノードを確認できます。

ノードへのアクセス方法

$ / get_node() でアクセスする

最もお手軽な各ノードへのアクセス方法は $ノード名 です。

extends Node2D

func _ready():
    # Mainが現在のノード

    # Main/bg_back
    print($bg_back.name)

    # Main/player
    print($player.name)

    # Main/TakoLayer/tako2
    print($TakoLayer/tako2.name)

ノードの階層をたどって直接アクセスできます。
実行結果は以下のとおりです

実行結果
bg_back
player
tako2

$によるアクセスは、get_node() と同じなので、先程のコードは以下のように置き換えることができます。

extends Node2D

func _ready():
    # Mainが現在のノード

    # Main/bg_back
    print(get_node("bg_back").name)

    # Main/player
    print(get_node("player").name)

    # Main/TakoLayer/tako2
    print(get_node("TakoLayer/tako2").name)

文字列指定なので、動的にノードツリーのパスを作りたい場合は、get_node() を使うことになると思います。

get_children() で子ノードををすべて取得

get_children() を使うと、対象のノードの子ノードをすべて取得することが可能です。

extends Node2D

func _ready():
    # Mainが現在のノード

    # TakoLayerのすべての子ノードを取得 
    for tako in $TakoLayer.get_children():
        print(tako.name)
実行結果
tako
tako2
tako3

親の階層をたどる "../ get_parent()

親の階層をたどる方法としてお手軽なのが、”../” です。
例えば、takoノードからplayerノードを取得するとします。

この場合、

  1. takoから TakoLayerに移動 (親の階層に移動)
  2. TakoLayerからMainに移動 (親の階層に移動)
  3. Mainからplayerを取得 (子 “player” を取得)

という流れになります。

takoノードスクリプトをアタッチした場合は以下のコードとなります。

extends Sprite

func _ready():
    # ○ この書き方のみ可能
    print($"../../player".name)

    # × この書き方はNG
    print($../../player.name)

“../”で1つ上の階層に移動できますが、”.”(ピリオド) がプログラム上特別な文字として扱われるため、文字列(ダブルクオートで囲む)でないとコンパイルエラーとなります。

また get_parent() を使用すると親のノードを取得できるので、以下のような書き方も可能です。

extends Sprite

func _ready():
    # 親の親の子 "player" を取得
    print(get_parent().get_parent().get_node("player").name)

所有者の取得 get_owner()

get_owner() そのノードの所有者を取得します。
所有者とは、そのシーンのルートノードとなるようです。
(例えば現在の構成であれば常に “Main”ノード)
ただし、Node.instance() で動的に生成した場合は所有者はいない状態となります。

extends Node2D

onready var TestNode2D = preload("res://TestNode2D.tscn")

func _ready():
    # Mainが現在のノード
    # 以下はすべて "Main" が出力される
    print($player.get_owner().name)
    print($bg_back.get_owner().name)
    print($TakoLayer/tako.get_owner().name)

    # 自分自身が所有者なので null
    print(get_owner())

    # 動的に生成した場合も null
    var test = TestNode2D.instance()
    print(test.get_owner())
実行結果
Main
Main
Main
[Object:null]
[Object:null]

一応は Node.set_owner() で所有者を設定できるようですが、Godotの設計思想としては動的なインスタンスに対して所有者を設定することは良くないみたいです。

I believe you can use set_owner to make it work, but I feel it would go against the spirit of owners.

Instead, I’d suggest using node references, like get_parent or creating/setting a variable on the relevant objects.

set_ownerを使用して機能させることができると思いますが、所有者の精神に反すると思います。 代わりに、get_parentのようなノード参照を使用するか、関連するオブジェクトに変数を作成/設定することをお勧めします。

get_owner() returning null despite having an owner : godot

動的なインスタンスから動的にインスタンスを生成してノードにぶら下げるような場合(例えば、ボスが敵を生成して、敵が敵弾を発射するような場合)、敵のインスタンスは get_owner() を使うのではなく、get_parent() を使用するか、配置済みの CanvasLayer にぶら下げる……などの対応をしたほうが良さそうです。

find_node() / find_parent() でノードを検索する

find_node() を使用すると、子ノードから該当のノードを検索できます。

また検索文字列にはワイルドカードとして、*(アスタリスク) と ?(クエスチョンマーク)が使用できます。

例えば以下のコードでは “TestNode2D” が含まれるノードを検索します。

var node = find_node("*TestNode2D*")

文字数が決まっていれば “?” で検索することもできます。

# "TestNode2D" または "TestNode3D" を検索
var node = find_node("TestNode?D")

find_parent() は子ではなくて親を検索する関数となります。

参考