Godot
August 5, 2023

Достаём информацию из классов и сцен без создания инстансов

Godot 4.1 наконец-то стал стабильнее и перестал вылетать, а ещё там завезли статичные переменные

Что это даёт?

Раньше были константы:

class_name Projectile

const MOVE_SPEED = 500

Эти константы были доступны как из инстанса класса, так и напрямую через Projectile.MOVE_SPEED

Однако, в константы можно было класть только примитивы

То есть, нельзя сделать так:

const foo = Node.new()

Но иногда статичная информация включает в себя не только примитивы

К примеру, у меня есть массивы перков/скиллов персонажей, которые состоят из load("../some_script.gd")

Теперь появился кейворд static, позволяющий как раз положить это в переменную:

class_name Rogue
extends Unit

static var perks_map = [load("..."), load("...")]

И, соответственно, можно делать Rogue.perks_map, не создавая инстанс роги

В моём случае это очень важно, поскольку в персонажах лежат скиллы и перки, а в скиллах и перках лежит логика из кучи нод и объектов. Создавать инстанс = создать под 500 объектов, которые тебе по сути не нужны. А учитывая, что сборщик мусора в GDScript работает так себе, ты ещё и утечки памяти впридачу получал

Однако, есть одна проблема - если ты загружаешь сцену, а не класс, то ты всё ещё не можешь обращаться к статичным переменным

То есть вот такой код не сработает:

var Rogue = load("./rogue.tscn")

# Ошибка, несмотря на то, что рутовая нода имеет класс роги
Rogue.perks_map

Чтобы решить эту проблему, я пришёл к вот такому вспомогательному коду:

class_name Instance

# Takes anything, and
# - Returns class instance, if passed .gd script
# - Returns smth.Scene.instantiate() and sets smth as script to scene root, if passed .gd script with Scene variable
# - Returns scene instance, if passed PackedScene
static func create(something):
	var instance
	if "Scene" in something:
		instance = something.Scene.instantiate()
		instance.set_script(something)
	elif "instantiate" in something:
		instance = something.instantiate()
	elif "new" in something:
		instance = something.new()
	else:
		instance = something
	return instance

И вместо того, чтобы навешивать класс роги на рутовую ноду сцены, я прописываю в самом классе роги

var Scene = load("./rogue.tscn")

И везде вместо сцены загружаю класс:

var Rogue = load("./rogue.gd")

# `perks_map` доступен
Rogue.perks_map

# А вот так получаем инстанс сцены
var rogue = Instance.create(Rogue)