Создание и удаление объектов
Создание префабов у маркера, лимит экземпляров, очередь удаления и время жизни.
Настройка
Нужен 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
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#-скрипта одновременно.
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.