Godot4で子ノードをすべて取得する_ベンチマーク付
Node
の関数 get_children
では、そのノードの直下のノードをすべて取得できます。
しかしこれは直下のみで、そのさらに子ノードは取得できません。
動的にノードツリーを構築したり、子がいくつあるか分からないが、すべて取得したい場合に必要になります。
あまり使用機会が少なく、そもそもコストが高いためあまり使いたくない処理ではあります。
そのため組み込み関数としては用意されていないのでしょう。
_process()
内などの頻繁に呼び出される関数で使うのは避けましょう……
この、「指定したノードの直下の子ノードだけではなく、すべての子ノードを取得する方法」をいくつか記述します。
おすすめは1の While
を使うやり方です。いちばん速いので。
ベンチマークは雑なので参考程度に。
1.While
#1While
var ary:Array = get_all_children_while(self) #usec=2122
func get_all_children_while(in_node):
var waiting = in_node.get_children()
var ary:Array = []
while not waiting.is_empty():
var node = waiting.pop_back()
waiting.append_array(node.get_children())
ary.append(node)
return ary
引数に入れたノードのすべての子ノードが入った Array
を返します。
While
でノードの get_children()
結果が空になるまでループします。
2.再帰処理を使った関数を用意する
#2再帰
var ary:Array = get_all_children_recursive(self) #usec=4309
func get_all_children_recursive(in_node, arr:=[]):
arr.append(in_node)
for child in in_node.get_children():
arr = get_all_children_recursive(child,arr)
return arr
引数に入れたノードと、そのすべての子ノードが入った Array
を返します。
引数に入れたノードも入るので注意が必要です。
再帰処理の制限として、コールスタック最大数を超えるとエラーになってしまいます。
デフォルト値は1024ですが、2047まで増やせます
プロジェクト設定の debug/settings/gdscript/max_call_stack
で設定します。
しかし、1.While より遅いです。
3.
Node
の関数 find_children()
を使用する
#3find_children
var ary:Array = find_children("*","",true,false) #usec=33147
Array[Node] find_children(pattern: String, type: String = "", recursive: bool = true, owned: bool = true)
Node
の find_children
関数は、
指定したパターン文字列にマッチするノードを取得できる関数ですが、recursive:bool
引数に true
を入れるとすべての子ノードを取得できるで、それを使ってみます。
マッチ条件は *
でどのノードにもマッチさせます。
パターン文字列でマッチさせるという、そこそこ重い処理があるのもあって、とても遅いです。
パターンは役に立ちますが、すべてのノードがとりたいだけという目的にはあっていませんね。
4. ノードにグループを設定してシーンツリーの get_nodes_in_group()
を使用
#4グループ
# グループを設定しておく、エディタのインスペクター → ノード → グループからも可能
var node = Node.new()
node.name = "Node" + str(i)
node.add_to_group(&"group")
var ary = get_tree().get_nodes_in_group(&"group") #usec=99068
少し逸れますが、シーンツリーとグループの機能を使うこともできます。
ノードにグループを設定して、シーンツリーからグループ名を指定して get_nodes_in_group
を呼ぶと
ツリー上にある指定したグループ名のグループのノードがすべて取得できます。
Array[Node] get_nodes_in_group(group: StringName)
ただし、他の方法とは違いシーンツリーを対象にするため、
子ノードではなくツリー上の同じグループのノードが取得されます。
いちばん遅いです。これは予想外でした。
シーンツリー&グループは、よく使われているイメージでしたが、意外と遅いですね……
グループを使わずにAutoload
のシングルトンのノードのプロパティとかに Dictionary
や Array
を用意して、
そこにデータを格納して外から参照・操作させるほうがパフォーマンス良いし便利なのでは……?
結論
extends Node
class_name MyStatic
static func get_all_children(in_node):
var children = in_node.get_children()
var ary:Array = []
while not children.is_empty():
var node = children.pop_back()
children.append_array(node.get_children())
ary.append(node)
return ary
Autoload
にこんな感じの static
関数を用意しておけば良さそうです。
試したコードまとめ
extends Node
func _ready():
var n = self
for i in 1000:
var node = Node.new()
node.name = "Node" + str(i)
node.add_to_group(&"group")
n.add_child(node)
var node2 = Node.new()
node2.add_to_group(&"group")
n.add_child(node2)
n = node
var a = Time.get_ticks_usec()
#1While
# var ary:Array = get_all_children_while(self) #usec=2122
#2再帰
# var ary:Array = get_all_children_recursive(self) #usec=4309
#3find_children
# var ary:Array = find_children("*","",true,false) #usec=33147
#4グループ
var ary = get_tree().get_nodes_in_group(&"group") #usec=99068
print(ary.size())
var b = Time.get_ticks_usec()
print(b - a)
func get_all_children_recursive(in_node, arr:=[]):
arr.append(in_node)
for child in in_node.get_children():
arr = get_all_children_recursive(child,arr)
return arr
func get_all_children_while(in_node):
var waiting = in_node.get_children()
var ary:Array = []
while not waiting.is_empty():
var node = waiting.pop_back()
waiting.append_array(node.get_children())
ary.append(node)
return ary
参考