概要
この記事では、シーンとノードについて個人的に調べたことを書いていきます。
シーンの構成
シーンは、1つの「ルート」ノードと、それにぶら下がる「子ノード」から構成されます。
上記画像は以下のような構成となっています。
Main
+-- bg_back
+-- player
+-- TakoLayer
+-- tako
+-- tako1
+-- tako2
同一階層には同名のオブジェクトは配置できない
また同一階層のオブジェクト名は「違う名前」でなければなりません
例えば
// × この構成にはできない
Main
+-- tako
+-- tako
+-- tako
という構成にはできません。(シーンエディタ上でこのように名前変更しようとしても、自動で連番にリネームされます)
ただし、異なる階層であれば同名のオブジェクトにすることが可能です。
// ○ 異なる階層であれば同名のオブジェクトは存在できる
Main
+-- tako ← "tako" 同一シーンに2つ存在する
+-- TakoLayer
+-- tako ← "tako" 同一シーンに2つ存在する
+-- tako1
オブジェクトの実行順
オブジェクトの実行順は、ルート以外の各オブジェクトが おおよそ 上から順に実行されます(例外があるみたい?)
- bg_back
- player
- TakoLayer
4. tako
5. tako2
6. tako3 - 最後に Main が実行される
Mainが最初に実行されるわけではないことに注意が必要そうです。
ただ、個人的にはノードの実行順に依存するような実装にはできるだけしないほうが、安全な作りになる気がしています。(よくわからない問題が起きにくくなるため)
シーンの描画順
シーンの描画順は基本的にノードの並び順で描画されるようです。
Mainが最後に描画、ということはなさそうです。
また、z_index
や CanvasLayer
で描画順を細かく制御可能です。
そのあたりについては以下の記事に書きましたので、ここでは省略します。
実行中のノードを確認する
実行中のノードの状況を確認する方法についてです。
テスト用のインスタンスとして "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ノードを取得するとします。
この場合、
- takoから TakoLayerに移動 (親の階層に移動)
- TakoLayerからMainに移動 (親の階層に移動)
- 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()
を使うのではなく、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()
は子ではなくて親を検索する関数となります。