reckfellENGINE

Spawn a prefab from a signal in world coordinates

An emitter reports intent and the world creates the instance; direction, parent transforms and subscription cleanup.

On this page

Separate intent from creation

The emitter reports a world position and direction through SpawnRequested. It neither searches for a parent nor hands out an already-created object. Without a subscriber, a request leaves no detached projectile allocation behind. The world owns the PackedEntity and decides where to place instances.

Run the example

In a 1.0.0 C# project add the three C# files at the project root and Actors/SignalBolt.rprefab at the shown path. Attach BoltWorld2D to an Entity2D in a separate level, set a 640×420 viewport, build and run with F6. The white circle is the emitter; left click launches blue markers toward the cursor. They live for three seconds.

An emitter signal creates independent blue instances in the world

Actors/SignalBolt.rprefab
Configuration
[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;
    }
}

Coordinates and lifetime

worldTarget − GlobalPosition computes direction in one coordinate space. Zero or nonfinite requests are rejected before creation. The receiver normalizes direction and assigns a speed of 240 world units per second. ToLocal converts position before AddChild, so the instance's _Ready sees configured data. _PhysicsProcess then changes GlobalPosition, keeping speed independent of parent scale.

A projectile becomes a child of BoltWorld2D rather than the emitter. Moving or rotating the emitter no longer drags an existing projectile. The world remains its parent: moving the world still changes hierarchical transforms. If it must move independently of projectiles, choose another stable container and convert position relative to that container.

The handler connects before AddChild of the emitter. _ExitTree disconnects; reentry subscribes once. _Ready is not used for reentry subscription. QueueFree releases expired instances through the deletion queue. This example is not an object pool: measure frequent creation cost first and separately design state reset for reuse.

Example limits

The blue marker is an Entity2D with drawing and movement. It has no hit detection, collision shape, network ownership or damage. Choose a physics body/query for your game's rules; changing position alone does not prevent crossing obstacles.

The fixture verifies a real signal, spawning, world placement under rotation/scale, speed, emitter independence, invalid-direction refusal and exit/reentry cleanup. The picture comes from an engine viewport; native-window mouse-click acceptance was not performed here.

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.