reckfellENGINE

Создание и удаление объектов

Создание префабов у маркера, лимит экземпляров, очередь удаления и время жизни.

На этой странице

Настройка

Нужен C#-проект и префаб Crate.rprefab с корнем Entity3D. В уровне создайте иерархию:

Текст
World (Entity3D, RuntimeSpawner.cs)
├── SpawnPoint (Marker3D)
└── Spawned (Entity3D)

SpawnPoint задаёт положение и поворот нового экземпляра. Spawned хранит только объекты, созданные этим скриптом. Его трансформация может отличаться от трансформации World.

В Input Map добавьте spawn_object на E и remove_object на Q. Прикрепите RuntimeSpawner.cs к World, соберите C# и назначьте Crate.rprefab свойству Template в инспекторе. Камера и освещение должны видеть точку появления.

RuntimeSpawner.cs

C#
using Reckfell;

public partial class RuntimeSpawner : Entity3D
{
    [Export] public PackedEntity? Template { get; set; }
    [Export] public int MaxInstances { get; set; } = 64;

    private Marker3D _point = null!;
    private Entity3D _container = null!;

    public override void _Ready()
    {
        _point = GetEntity<Marker3D>("SpawnPoint");
        _container = GetEntity<Entity3D>("Spawned");
    }

    public Entity3D? Spawn()
    {
        if (Template == null)
        {
            ReckfellApi.PushError("Assign a PackedEntity to Template.");
            return null;
        }

        if (MaxInstances <= 0 || _container.GetChildCount() >= MaxInstances)
            return null;

        Transform3D parentTransform = _container.GlobalTransform;
        Transform3D spawnTransform = _point.GlobalTransform;
        if (!parentTransform.IsFinite() || !spawnTransform.IsFinite() ||
            Mathf.IsZeroApprox(parentTransform.Basis.Determinant()))
        {
            ReckfellApi.PushError("Spawn transforms must be finite; Spawned must have invertible scale.");
            return null;
        }

        Entity entity = Template.Instantiate();
        if (entity is not Entity3D instance)
        {
            entity?.Free();
            ReckfellApi.PushError("Template root must inherit Entity3D.");
            return null;
        }

        instance.Transform = parentTransform.AffineInverse() * spawnTransform;
        _container.AddChild(instance);
        return instance;
    }

    public void RemoveOldest()
    {
        foreach (Entity child in _container.GetChildren())
        {
            if (child.IsQueuedForDeletion())
                continue;

            child.QueueFree();
            return;
        }
    }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (@event.IsActionPressed("spawn_object"))
            Spawn();
        else if (@event.IsActionPressed("remove_object"))
            RemoveOldest();
        else
            return;

        GetViewport().SetInputAsHandled();
    }
}

Положение и жизненный цикл

Вызов Instantiate() создаёт отдельное дерево, ещё не добавленное в уровень. Формула переводит мировую трансформацию маркера в локальную систему Spawned. Она задаётся до AddChild, поэтому _Ready() нового объекта уже получает правильное положение и поворот.

Если сначала вызвать AddChild(), а затем задать позицию, _Ready() успеет выполниться со старой трансформацией. Это важно для скриптов, которые при старте делают физический запрос или создают дочерние эффекты.

E добавляет экземпляр, Q удаляет первый оставшийся экземпляр. QueueFree() завершает удаление в конце кадра. До этого объект ещё находится среди детей. Проверка IsQueuedForDeletion() позволяет нескольким вызовам удаления в одном кадре выбрать разные объекты.

Сохранённая C#-ссылка после удаления не означает, что native-объект существует. Для ссылок, переживающих удаление, используйте ReckfellObject.IsInstanceValid(reference) перед обращением.

Лимит и повторное удаление

MaxInstances ограничивает количество детей в Spawned. При 64 шестьдесят пятый вызов Spawn() вернёт null; ноль и отрицательные значения отключают создание. В лимит входят и объекты, уже ожидающие удаления: слот освободится после фактического удаления в конце кадра. Это не даёт быстро набрать очередь новых экземпляров поверх ещё живущих.

RemoveOldest() пропускает уже помеченных детей. Поэтому два вызова за один кадр удалят два разных объекта, а не дважды первый. Если нужен сигнал попадания снаряда, поставьте свою защиту от повторного срабатывания до QueueFree() — объект ещё может получить другие вызовы в этом кадре.

Spawned должен хранить только экземпляры этого спавнера: другие дети тоже войдут в лимит и могут быть удалены. Для постоянно существующего декора используйте отдельного родителя. Не задавайте контейнеру нулевой масштаб: его мировая трансформация должна быть обратимой. Пример отклоняет неконечные значения и почти вырожденный масштаб до создания объекта.

Удаление по времени

Чтобы экземпляр сам исчезал через три секунды, прикрепите Scripts/TimedLifetime.cs к корню Crate.rprefab и сохраните префаб. Вместо прежнего скрипта корня назначается этот; в Reckfell один объект не получает два обычных C#-скрипта одновременно.

C#
using Reckfell;

public partial class TimedLifetime : Entity3D
{
    [Export] public double LifetimeSeconds { get; set; } = 3.0;
    private double _remaining;

    public override void _EnterTree()
    {
        _remaining = LifetimeSeconds;
        SetProcess(true);
    }

    public override void _Process(double delta)
    {
        _remaining -= delta;
        if (_remaining > 0.0)
            return;

        SetProcess(false);
        QueueFree();
    }
}

Вместо таймера на каждый объект пример отсчитывает delta в собственном обновлении. После истечения срока он отключает обработку и ставит объект на удаление. Ноль или отрицательное время удалит его на первом обновлении. При стандартном наследуемом режиме обработки пауза игры останавливает этот отсчёт.

Проверьте: нажмите E, дождитесь исчезновения ящика и нажмите E ещё раз. Освободившийся слот снова доступен. Это удаление, а не пул объектов: после завершения QueueFree() старый экземпляр нельзя вернуть в дерево. Если объект только временно убирают через RemoveChild(), _EnterTree() при возврате начнёт отсчёт заново.

Сохранение и ошибки

Объекты из примера существуют только в запущенной игре. AddChild() не записывает их в исходный .rscene и не назначает Owner. Чтобы восстановить созданные объекты после перезапуска, сохраняйте идентификатор префаба и их трансформации в данные игры.

СимптомПричина
Assign a PackedEntityНе назначено свойство Template
Template root must inherit Entity3DВ префабе корень 2D, Control или другой тип
Ошибка GetEntityИмена или типы SpawnPoint / Spawned не совпадают с примером
Экземпляр есть, но не виденВ префабе нет меша, маркер вне кадра или объект скрыт
Объект не сталкивается с миромВидимому мешу не назначено физическое тело с Shape

Справочник: PackedEntity, Entity3D, Entity.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.