reckfellENGINE

Появление противников и раунды

Один контроллер связывает таймер, игрока, префаб противника и интерфейс; старые экземпляры удаляются при перезапуске.

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

Подготовьте уровень

В иерархии Arena нужны экземпляры Player и Hud, пустой Entity3D с именем Mobs и Timer с именем SpawnTimer. У таймера Wait Time = 1.2, Process Callback = Physics, One Shot и Autostart выключены. Корень уровня и Mobs имеют нулевой transform с масштабом 1.

Прикрепите Scripts/SquashGame.cs к корню. Перед сборкой добавьте SquashHud.cs и остальные классы.

Полный 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);
    }
}

Где появляется экземпляр

Ресурс Prefabs/Mob.rprefab загружается один раз. При появлении создаётся SquashMob; неподходящий корень освобождается, ошибка выводится, раунд прекращается. Позиция выбирается на одной из четырёх сторон квадрата X/Z = ±6, Y = 0.4.

Начальное направление берётся к текущей позиции игрока. Положение и направление назначены до AddChild(), поэтому жизненный цикл начинается с готовыми исходными данными. Здесь используются локальные координаты контейнера; при его преобразовании изменится и положение противников в мире.

Ограничение количества

При 32 дочерних объектах Mobs новая попытка возвращает null. Объект, ожидающий QueueFree(), ещё входит в число детей до удаления. У каждого противника дополнительно ограничено время жизни. Это локальная защита прототипа от накопления объектов, не система потоковой загрузки мира.

Начало и завершение

StartRun() сбрасывает счёт, удаляет старые экземпляры, возвращает игрока в (0, 0.9, 0) и запускает таймер. Повторный вызов во время раунда ничего не меняет. EndRun() выключает управление, таймер и физическую обработку оставшихся противников, сохраняя финальный счёт на экране.

Каждому новому противнику подключается один обработчик Squashed. Поля заполняются в _EnterTree(), подписки снимаются в _ExitTree(). Не подключайте те же события ещё раз через редактор. Очки и интерфейс.

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

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

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

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