RefCountedとNode
CoddyのGDScriptジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 43/65。
すべてのオブジェクトは解放されるまでメモリを使用します。RefCountedとNodeは異なる方法で解放されます。
RefCountedオブジェクトは、それを参照する変数、配列、オブジェクトの数を数えます。その数がゼロになると、Godotは自動的にオブジェクトを解放します。「特殊メソッド」の章にあるNOTIFICATION_PREDELETEは、その瞬間を示しています。
# note.gd
class_name Note
extends RefCounted
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
print("Note freed")
# main.gd
func write() -> void:
var note := Note.new()
print("Note written")
func _ready():
write() # Note が書き込まれ、その後 Note が解放される
print("Back in _ready")noteだけが参照だったため、write()が終了するとすぐにnoteは解放されます。
Nodeはカウントされません。シーンツリー内のノードは親とともに解放され、ゲームが終了するとツリー全体が解放されます。ツリーから取り出したノード、またはツリーに追加しなかったノードは、自分で解放する必要があります。
crate.free() # すべての子とともに今すぐ解放される
crate.queue_free() # 現在のフレームの終了時に解放されるfree()の後、ノードは消えます。is_instance_valid(crate)はfalseを返し、そのノードのメソッドを呼び出すとエラーになります。このフレーム中にほかのコードがまだノードを使う可能性がある場合は、queue_free()を使うのが安全ですが、フレームが終了するまではノードはツリー内に残り、get_child_count()のカウントにも含まれます。
解放されないノードはメモリリークです。プログラムの終了時に、Godot は「ObjectDB instances were leaked at exit」と報告します。RefCounted オブジェクトも、互いに参照し合うとリークします。どちらの参照カウントもゼロにならないためです。リンクは一方向だけに保存するか、オブジェクトの代わりに名前を保存してください。
データ(アイテム、スコア、レコード)にはRefCountedを使います。シーンツリー内に存在するものにはNodeを使います。
チャレンジ
簡単Waveノードは敵を子ノードとして保持します。enemy.gdは完成しています。Enemy.new("Goblin", 5)は、5 HPのGoblinという名前の敵ノードを作成します。Waveの3つのメソッドを記述してください。
spawn(enemy_name, hp)は新しい敵を子として追加します。その名前の子がすでに存在する場合は、Orc is already hereを出力し、何も作成しません。hit(enemy_name, damage)はその敵のhpを減らし、Orc has 8 HPを出力します。HPが0以下になった場合は、Orc defeatedを出力し、敵をすぐに完全に削除します。次に呼び出されるalive()では、その敵を数えてはいけません。存在しない名前の場合は、No Orc hereを出力します。alive()は残っている敵の数を返します。
すべてのノードを解放しなければなりません。終了時にメモリリークがあるとテストに失敗します。提供されているコードはウェーブをシーンツリーに追加し、後に続くコマンドの数を読み取って(spawn Goblin 5、hit Goblin 6、count)、残っている敵を出力します。spawn Goblin 5、spawn Orc 12、hit Goblin 6、count、hit Goblin 1、spawn Orc 3、hit Orc 4を実行すると、次のように出力されます。
Goblin defeated
Enemies: 1
No Goblin here
Orc is already here
Orc has 8 HP
Enemies left: 1コードはwave.gdとenemy.gdに記述してください。main.gdには提供された入出力コードが含まれており、編集できません。
自分で試してみよう
extends Node
func _ready():
# 提供された入出力コード:そのままにしておいてください
var wave := Wave.new()
add_child(wave)
var count := int(OS.read_string_from_stdin())
for i in count:
var parts := OS.read_string_from_stdin().split(" ")
if parts[0] == "spawn":
wave.spawn(parts[1], int(parts[2]))
elif parts[0] == "hit":
wave.hit(parts[1], int(parts[2]))
else:
print("Enemies: ", wave.alive())
print("Enemies left: ", wave.alive())
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: GDScriptオンラインコンパイラ