Menu
CoddyTech

RefCounted и Node

Часть раздела Объектно-ориентированное программирование путешествия по GDScript на Coddy. Урок 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().

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. Если у врага осталось 0 HP или меньше, метод выводит 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())
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор GDScript