Menu
CoddyTech

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。

challenge icon

挑战

简单

一个 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())
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: GDScript 在线编译器