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 written,再输出 Note freed
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) 会创建一个名为 Goblin、拥有 5 HP 的敌人节点。请编写 Wave 的三个方法:
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()会返回剩余敌人的数量。
必须释放每个节点:退出时有任何内存泄漏都会导致测试失败。提供的代码会将 wave 添加到场景树中,读取后续命令的数量(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 在线编译器