reckfellENGINE

Spawning hazards and rounds

One controller connects timer, player, hazard prefab and UI, clearing old instances on restart.

On this page

Prepare the level

Arena needs Player and Hud instances, an empty Entity3D named Mobs, and SpawnTimer. Set the timer to Wait Time = 1.2, Process Callback = Physics, with One Shot and Autostart off. Keep level and Mobs transforms at their defaults, scale 1.

Attach Scripts/SquashGame.cs to the root. Add SquashHud.cs and the other classes before building.

Complete SquashGame.cs
C#
using Reckfell;

public partial class SquashGame : Entity3D
{
    public bool Playing { get; private set; }
    public int Score { get; private set; }
    private SquashPlayer _player = null!;
    private Entity3D _mobs = null!;
    private Timer _spawnTimer = null!;
    private SquashHud _hud = null!;
    private PackedEntity? _template;
    private readonly RandomNumberGenerator _random = new();

    public override void _EnterTree()
    {
        _player = GetEntity<SquashPlayer>("Player");
        _mobs = GetEntity<Entity3D>("Mobs");
        _spawnTimer = GetEntity<Timer>("SpawnTimer");
        _hud = GetEntity<SquashHud>("Hud");
        _player.Hit += EndRun;
        _spawnTimer.Timeout += OnSpawnTimeout;
        _hud.StartRequested += StartRun;
    }

    public override void _ExitTree()
    {
        if (ReckfellObject.IsInstanceValid(_player)) _player.Hit -= EndRun;
        if (ReckfellObject.IsInstanceValid(_spawnTimer)) _spawnTimer.Timeout -= OnSpawnTimeout;
        if (ReckfellObject.IsInstanceValid(_hud)) _hud.StartRequested -= StartRun;
        if (ReckfellObject.IsInstanceValid(_mobs))
            foreach (SquashMob mob in _mobs.GetChildren()) mob.Squashed -= OnSquashed;
    }

    public override void _Ready()
    {
        _player.Active = false;
        _random.Randomize();
        _hud.SetScore(0);
        _template = ResourceLoader.Load("asset://Prefabs/Mob.rprefab") as PackedEntity;
        if (_template == null || !_template.CanInstantiate()) _hud.ShowSetupError();
        else _hud.ShowReady();
    }

    public void StartRun()
    {
        if (Playing || _template == null) return;
        foreach (Entity mob in _mobs.GetChildren()) mob.QueueFree();
        Playing = true;
        Score = 0;
        _player.StartAt(new Vector3(0, 0.9f, 0));
        _hud.SetScore(0);
        _hud.ShowPlaying();
        _spawnTimer.Start();
    }

    public SquashMob? SpawnMob()
    {
        if (!Playing || _template == null || _mobs.GetChildCount() >= 32) return null;
        Entity instance = _template.Instantiate();
        if (instance is not SquashMob mob)
        {
            instance?.Free();
            ReckfellApi.PushError("Mob.rprefab must have a SquashMob root.");
            EndRun();
            return null;
        }
        float side = _random.RandfRange(-6, 6);
        mob.Position = _random.RandiRange(0, 3) switch
        {
            0 => new Vector3(-6, 0.4f, side),
            1 => new Vector3(6, 0.4f, side),
            2 => new Vector3(side, 0.4f, -6),
            _ => new Vector3(side, 0.4f, 6)
        };
        mob.MoveDirection = _player.Position - mob.Position;
        mob.Squashed += OnSquashed;
        _mobs.AddChild(mob);
        return mob;
    }

    public void EndRun()
    {
        if (!Playing) return;
        Playing = false;
        _player.Active = false;
        _player.Velocity = Vector3.Zero;
        _spawnTimer.Stop();
        foreach (Entity mob in _mobs.GetChildren()) mob.SetPhysicsProcess(false);
        _hud.ShowGameOver();
    }

    private void OnSpawnTimeout() => SpawnMob();
    private void OnSquashed()
    {
        if (Playing) _hud.SetScore(++Score);
    }
}

Where instances appear

Prefabs/Mob.rprefab loads once. Spawning creates a SquashMob; an incompatible root is freed, an error is logged and the round stops. Position is selected on one of four sides of the X/Z = ±6 square, at Y = 0.4.

Initial direction points toward the player's current position. Position and direction are assigned before AddChild(), so the lifecycle starts with its initial data in place. These are container-local coordinates; transforming the container changes world placement.

Count limit

With 32 children in Mobs, another request returns null. A queued-for-deletion object still counts until freed. Each hazard also has a lifetime limit. This bounds objects in the local prototype; it is not world streaming.

Starting and ending

StartRun() clears score and old instances, returns the player to (0, 0.9, 0) and starts the timer. Calling it during an active round changes nothing. EndRun() disables controls, timer and remaining hazards' physics processing while preserving final score.

Each new hazard gets one Squashed handler. _EnterTree() obtains references and _ExitTree() removes subscriptions. Do not duplicate those connections in the editor. See score and UI.

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.