Вопрос по ООП, экземпляр моего класса, при его создании и добавлении в дерево должен являться сценой (композицией нод), а не одной нодой

Как сделать так, чтобы экземпляр моего класса, при его создании и добавлении в дерево являлся сценой (композицией нод), а не одной нодой. (моя реализация кажется мне слишком костыльной)

То-есть проблема моей первой реализации состоит в том, что я создаю объект класса через var entity = Entity.new(аргументы для _init()), он успешно создаётся в памяти, весь скрипт срабатывает, а потом я добавляю этот объект в дерево add_child(entity) - он создаёт ноду CharacterBody2D, которую расширяет мой класс, а тк она не является Entity.gd - то не имеет его функционала (такая реализация бесполезна, ибо я просто создаю в памяти нужный мне объект, а в дереве появляется жопа)

Тогда я решил сделать опять же var entity = Entity.new(аргументы для _init()), он успешно создаётся в памяти, весь скрипт срабатывает, а потом я добавляю этот объект в дерево add_child(entity.preloaded_scene.instantiate() #preloaded_scene - это preload("res://scenes/entity.tscn"), такая реализация работает, но проблема в том, что я получаю в памяти два объекта entity, а в дерево добавляется только второй. Я хз как я написал этот костыль, но дубликат возникает из-за того, что когда я подгружаю сцену, корневой нодой которой является нода типа Entity.gd а при её добавлении в памяти создаётся ещё один объект Entity.gd, который уже и указывает на визуализированную сцену в дереве, в то время как изначальный объект теряется в памяти. (вот пока что такая у меня реализация, буду рад, если кто-то подскажет как мне убрать это лишнее звено в виде изначального объекта)

Возможно все проблемы в моей реализации класса Entity.gd:

extends CharacterBody2D
class_name Entity

var preloaded_scene = preload("res://scenes/entity.tscn")

var _name: String
var speed: float
var hp: int
var max_hp: int
var label; var label_hp; var collision

func _ready():
    print("сцена добавлена в дерево")
    label = $Label
    label_hp = $Label_hp
    collision = $CollisionShape2D
    label.text=_name
    label_hp.text="{cur_hp} / {max_hp}".format({"cur_hp": hp, "max_hp": max_hp})

func _init(_name_of_entity: String = "Entity", speed_of_entity: float = 200.0, hp_of_entity: int = 20):
    print("объект создан")
    _name=_name_of_entity
    hp = hp_of_entity
    speed=speed_of_entity
    max_hp=hp

Ответы (1 шт):

Автор решения: Oyunsky

Если вам нужно, чтобы наследуемые объекты имели все параметры базового, то предлагаю следующий вариант:

Наследование объектов

  1. Создаем базовую сцену с желаемой структурой, от которого будут наследоваться.

Структура сцены

Предпросмотр сцены

  1. Задаем параметры.

Параметры, которые будут меняться у наследуемых объектов, задаем через export (Export Properties).

@export var _name: String = "Entity"
@export var _speed: float = 200
@export var _max_health: int = 100

var _health: int

Параметры сцены

Не забываем про объекты сцены.

@onready var sprite: Panel = $Sprite
@onready var collision: CollisionShape2D = $Collision

@onready var name_label: Label = $Name
@onready var health_label: Label = $Health
  1. Пишем логику инициализации сцены.
func _ready() -> void:
    self._health = self._max_health
    self.name_label.text = self._name
    self.health_label.text = "HP: %d" % self._health

    # Для наглядности вывожу данные о сцене
    print("Сцена `%s` добавлена в дерево" % self._name)
    print("- Speed: %d" % self._speed)
    print("- HP: %d\n" % self._health)

Полный код (entity.gd):

class_name Entity extends CharacterBody2D

@export var _name: String = "Entity"
@export var _speed: float = 200
@export var _max_health: int = 100

var _health: int

@onready var sprite: Panel = $Sprite
@onready var collision: CollisionShape2D = $Collision

@onready var name_label: Label = $Name
@onready var health_label: Label = $Health

func _ready() -> void:
    self._health = self._max_health
    self.name_label.text = self._name
    self.health_label.text = "HP: %d" % self._health
    
    print("Сцена `%s` добавлена в дерево" % self._name)
    print("- Speed: %d" % self._speed)
    print("- HP: %d\n" % self._health)
  1. Теперь, когда нам нужно создать наследуемую сцену, делаем это через Instantiate Child Scene (иконка цепи).

Создаём экземпляр сцены

Выбираем базовую сцену, от которой наследуемся.

Выбираем сцену от которой наследуемся

  1. Если нужно изменить параметры какого-либо объекта сцены, выбираем нужный и нажимаем на Make Unique.

Например, если нужно изменить размер коллизии, то выбираем объект CollisionShape2D.

Выбираем объект

Нажимаем на стрелку вниз и из выпадающего списка выбираем Make Unique.

Нажимаем на кнопку Make Unique

Итог

Результат.

Результат

Вывод:

Сцена `Entity` добавлена в дерево
- Speed: 200
- HP: 100

Сцена `Player` добавлена в дерево
- Speed: 500
- HP: 200

Сцена `Enemy` добавлена в дерево
- Speed: 100
- HP: 50

Дерево сцены:

 ┖╴Playground
    ┠╴Entity
    ┃  ┠╴Collision
    ┃  ┠╴Sprite
    ┃  ┠╴Health
    ┃  ┖╴Name
    ┠╴Player
    ┃  ┠╴Collision
    ┃  ┠╴Sprite
    ┃  ┠╴Health
    ┃  ┖╴Name
    ┖╴Enemy
       ┠╴Collision
       ┠╴Sprite
       ┠╴Health
       ┖╴Name

Как по мне, такой подход к созданию экземпляров сцены подходит для быстрого создания статических объектов. Например, если игра в жанре "выживание", таким объектом будет предмет, который может подобрать игрок (например, камень, палка и т.д.).

Советую вам ознакомиться с системой компонентов (ссылка на статью)

→ Ссылка