reckfellENGINE

Создать префаб по сигналу в мировых координатах

Источник сообщает намерение, мир создаёт экземпляр; направление, преобразования родителя и очистка подписок.

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

Разделить запрос и создание

Источник сообщает мировую позицию и направление через SpawnRequested. Он не ищет родителя и не отдаёт наружу уже созданный объект. Если подписчика нет, запрос не оставит отсоединённый снаряд в памяти. Мир владеет PackedEntity и решает, где разместить экземпляр.

Запустить пример

В C#-проекте 1.0.0 добавьте три C#-файла в корень и Actors/SignalBolt.rprefab по указанному пути. Прикрепите BoltWorld2D к Entity2D отдельного уровня, задайте viewport 640×420, соберите и запустите F6. Белый круг — источник; ЛКМ выпускает синие маркеры к курсору. Они живут три секунды.

Сигнал источника создаёт отдельные синие экземпляры в мире

Actors/SignalBolt.rprefab
Конфигурация
[reckfell_tree format=3]

[ext_resource type="Script" path="asset://SignalBolt2D.cs" id="BoltScript"]

[entity name="SignalBolt" type="Entity2D"]
script = ExtResource("BoltScript")
SignalBolt2D.cs
C#
using Reckfell;

public partial class SignalBolt2D : Entity2D
{
    public Vector2 WorldVelocity { get; set; }
    public double RemainingSeconds { get; set; } = 3.0;

    public override void _Draw() => DrawCircle(Vector2.Zero, 8, new Color(0.25f, 0.45f, 1));

    public override void _PhysicsProcess(double delta)
    {
        GlobalPosition += WorldVelocity * (float)delta;
        RemainingSeconds -= delta;
        if (RemainingSeconds <= 0) QueueFree();
    }
}
BoltEmitter2D.cs
C#
using Reckfell;

public partial class BoltEmitter2D : Entity2D
{
    [Signal]
    public delegate void SpawnRequestedEventHandler(Vector2 worldPosition, Vector2 worldDirection);

    public bool RequestTowards(Vector2 worldTarget)
    {
        Vector2 offset = worldTarget - GlobalPosition;
        if (!offset.IsFinite() || offset.IsZeroApprox()) return false;
        EmitSignal(SignalName.SpawnRequested, GlobalPosition, offset.Normalized());
        return true;
    }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (@event is InputEventMouseButton { Pressed: true, ButtonIndex: MouseButton.Left })
            RequestTowards(GetGlobalMousePosition());
    }

    public override void _Draw() => DrawCircle(Vector2.Zero, 16, Colors.White);
}
BoltWorld2D.cs
C#
using Reckfell;

public partial class BoltWorld2D : Entity2D
{
    public BoltEmitter2D Emitter { get; private set; } = null!;
    public int CreatedCount { get; private set; }
    private PackedEntity _bolt = null!;

    public override void _EnterTree()
    {
        _bolt = ResourceLoader.Load<PackedEntity>("asset://Actors/SignalBolt.rprefab");
        if (Emitter is null)
        {
            Emitter = new BoltEmitter2D { Name = "Emitter", Position = new Vector2(100, 210) };
            Emitter.SpawnRequested += OnSpawnRequested;
            AddChild(Emitter);
        }
        else Emitter.SpawnRequested += OnSpawnRequested;
    }

    public override void _ExitTree() => Emitter.SpawnRequested -= OnSpawnRequested;

    public override void _Ready()
    {
        AddChild(new Label
        {
            Position = new Vector2(28, 24),
            Text = TranslationServer.GetLocale().StartsWith("ru")
                ? "Сигнал → экземпляр · ЛКМ: выстрел к курсору"
                : "Signal → instance · Left click: fire at cursor"
        });
    }

    private void OnSpawnRequested(Vector2 worldPosition, Vector2 worldDirection) => TrySpawn(worldPosition, worldDirection);

    public bool TrySpawn(Vector2 worldPosition, Vector2 worldDirection)
    {
        if (_bolt is null || !worldPosition.IsFinite() || !worldDirection.IsFinite() || worldDirection.IsZeroApprox()) return false;
        var bolt = _bolt.Instantiate<SignalBolt2D>();
        bolt.Position = ToLocal(worldPosition);
        bolt.WorldVelocity = worldDirection.Normalized() * 240;
        AddChild(bolt);
        CreatedCount++;
        return true;
    }
}

Координаты и жизненный цикл

worldTarget − GlobalPosition вычисляет направление в одном пространстве. Нулевой или нечисловой запрос отклоняется до создания. Получатель нормализует направление и задаёт скорость 240 мировых единиц в секунду. Перед AddChild позиция переводится через ToLocal: _Ready экземпляра уже увидит подготовленные данные. _PhysicsProcess затем меняет GlobalPosition, поэтому скорость не умножается на масштаб родителя.

Снаряд добавляется ребёнком BoltWorld2D, а не источника. Поворот или перемещение источника больше не тянут выпущенный объект. Сам мир по-прежнему остаётся его родителем: перемещение мира тоже изменит иерархическое преобразование. Если мир должен двигаться независимо от снарядов, выберите другой стабильный контейнер и пересчитайте позицию относительно него.

Подписка создаётся до AddChild источника. _ExitTree её снимает; повторный вход подписывает один раз. _Ready не используется для повторной подписки. По окончании жизни QueueFree освобождает экземпляр через очередь. Здесь нет Object Pool: для частого создания сначала измерьте стоимость и отдельно спроектируйте сброс состояния при переиспользовании.

Границы примера

Синий маркер — Entity2D с рисованием и движением. Он не обнаруживает попадания, не имеет физического коллайдера, сетевого владельца или урона. Для столкновений выберите физическое тело и запрос под правила своей игры; простое изменение позиции не предотвращает пролёт через препятствие.

В стенде проверены настоящий сигнал, триггер создания, мировое положение под поворотом/масштабом, скорость, независимость от источника, отказ неверного направления и очистка при выходе/повторном входе. Изображение получено из viewport движка; ручной сценарий клика в native-окне здесь не выполнялся.

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

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

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

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