reckfellENGINE

Частицы 2D: CPU, GPU и жизненный цикл эффекта

Полный пример с управлением эмиссией, перезапуском и пространством движения.

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

Выбрать систему

СистемаГде настраивается движениеЧто учитывать
CpuParticles2DСвойства самой EntityСимуляция на CPU; отображение всё равно проходит через рендерер
GpuParticles2DProcessMaterialGPU-симуляция; доступность дополнительных эффектов зависит от backend

Обе системы создают визуальные частицы, а не по Entity на каждую точку. У отдельной частицы здесь нет C# callback, физического тела или сетевого ID. Для игровых снарядов с индивидуальной логикой нужен другой подход.

Базовые GPU-частицы в приведённом примере работают в Compatibility/OpenGL. Это не означает поддержку в этом backend trails и sub-emitters: эти возможности ограничены отдельно. CPU и GPU не обязаны выдавать одинаковые пиксели при равных параметрах и seed. Выбирайте по требуемым эффектам и замерам на целевой системе, а не по обещанию «GPU всегда быстрее».

Запустить полный пример

  1. Создайте C#-проект Reckfell.NET.Sdk 1.0.0, net10.0 с рендерером Compatibility.
  2. Сохраните два файла ниже и выполните dotnet build.
  3. Создайте уровень с корнем Entity2D, прикрепите Particle2DExample.cs, сохраните particles.rscene и запустите.
  4. Сначала частицы постепенно заполняют фонтан. ui_accept перезапускает его, ui_cancel прекращает новую эмиссию. В стандартных назначениях это Enter/Space и Escape; дайте окну игры фокус.

UseGpu=false выбирает CPU, true — GPU. OneShotBurst=true создаёт разовую вспышку, false — непрерывную эмиссию. LocalSpace выбирает пространство симуляции. Count допустим от 1 до 2048; верхняя граница — защита примера, не лимит движка. Эти параметры корневого скрипта читаются при _Ready: меняйте их до запуска. Для изменения уже созданной системы обращайтесь к её Cpu или Gpu.

Пример сам создаёт эмиттер и текстуру 8×8, задаёт логический размер окна игры 320×240 во время запуска и не требует сторонних ассетов.

Particle2DPreset.cs
C#
using System;
using Reckfell;

public static class Particle2DPreset
{
    private static void Validate(int count)
    {
        if (count < 1 || count > 2048)
            throw new ArgumentOutOfRangeException(nameof(count));
    }

    public static ParticleProcessMaterial Motion() => new()
    {
        ParticleFlagDisableZ = true,
        Direction = new Vector3(0, -1, 0),
        Spread = 30,
        InitialVelocityMin = 60,
        InitialVelocityMax = 100,
        Gravity = new Vector3(0, 90, 0),
        Color = new Color(0.55f, 0.72f, 1)
    };

    public static GpuParticles2D Gpu(int count, bool oneShot, bool local)
    {
        Validate(count);
        return new GpuParticles2D
        {
            Emitting = false,
            Amount = count,
            Lifetime = 2,
            OneShot = oneShot,
            Explosiveness = oneShot ? 1 : 0,
            LocalCoords = local,
            UseFixedSeed = true,
            Seed = 42,
            FixedFps = 60,
            ProcessMaterial = Motion(),
            Texture = Texture(),
            VisibilityRect = new Rect2(-140, -120, 280, 320)
        };
    }

    public static CpuParticles2D Cpu(int count, bool oneShot, bool local)
    {
        Validate(count);
        return new CpuParticles2D
        {
            Emitting = false,
            Amount = count,
            Lifetime = 2,
            OneShot = oneShot,
            Explosiveness = oneShot ? 1 : 0,
            LocalCoords = local,
            UseFixedSeed = true,
            Seed = 42,
            FixedFps = 60,
            Direction = Vector2.Up,
            Spread = 30,
            InitialVelocityMin = 60,
            InitialVelocityMax = 100,
            Gravity = new Vector2(0, 90),
            Color = new Color(0.55f, 0.72f, 1),
            Texture = Texture()
        };
    }

    private static ImageTexture Texture()
    {
        using var image = Image.CreateEmpty(8, 8, false, Image.Format.Rgba8);
        image.Fill(Colors.Transparent);
        for (int y = 0; y < 8; y++)
        for (int x = 0; x < 8; x++)
            if (new Vector2(x - 3.5f, y - 3.5f).LengthSquared() <= 12)
                image.SetPixel(x, y, Colors.White);
        return ImageTexture.CreateFromImage(image);
    }
}
Particle2DExample.cs
C#
using Reckfell;

public partial class Particle2DExample : Entity2D
{
    [Export] public bool UseGpu { get; set; } = true;
    [Export] public bool OneShotBurst { get; set; }
    [Export] public bool LocalSpace { get; set; } = true;
    [Export] public int Count { get; set; } = 128;
    public GpuParticles2D? Gpu { get; private set; }
    public CpuParticles2D? Cpu { get; private set; }

    public override void _Ready()
    {
        if (GetViewport() is Window window)
        {
            window.ContentScaleSize = new Vector2I(320, 240);
            window.ContentScaleMode = Window.ContentScaleModeEnum.Viewport;
        }
        Vector2 position = new(160, 180);
        if (UseGpu)
        {
            Gpu = Particle2DPreset.Gpu(Count, OneShotBurst, LocalSpace);
            Gpu.Position = position;
            AddChild(Gpu);
        }
        else
        {
            Cpu = Particle2DPreset.Cpu(Count, OneShotBurst, LocalSpace);
            Cpu.Position = position;
            AddChild(Cpu);
        }
        RestartEffect();
    }

    public void RestartEffect()
    {
        Gpu?.Restart(keepSeed: true);
        Cpu?.Restart(keepSeed: true);
    }

    public void StopEmission()
    {
        if (Gpu != null) Gpu.Emitting = false;
        if (Cpu != null) Cpu.Emitting = false;
    }

    public override void _UnhandledInput(InputEvent input)
    {
        if (input.IsActionPressed("ui_accept")) RestartEffect();
        else if (input.IsActionPressed("ui_cancel")) StopEmission();
    }
}

Различать три операции

ОперацияЧто происходит
StopEmission / Emitting=falseНовые частицы не появляются; уже живущие остаются до конца жизни
RestartEffect / Restart(keepSeed:true)Состояние эффекта сбрасывается и эмиссия начинается заново
Естественное завершение OneShotПосле завершения частиц приходит Finished

Emitting=false не доказывает пустой экран. При OneShot с растянутым появлением последняя частица может жить после остановки эмиссии. Для завершения используйте Finished; для удаления Entity и ресурсов задайте отдельное правило времени жизни владельца.

В примере Lifetime=2. При непрерывной эмиссии с Explosiveness=0 и стандартными остальными параметрами Amount=128 даёт примерно 64 рождения в секунду и до 128 живых частиц после заполнения. При OneShotBurst=true Explosiveness=1 собирает рождение в один выброс. Lifetime — время жизни, не команда задержать запуск.

Restart сохраняет seed при keepSeed=true или UseFixedSeed=true. false у keepSeed не заставляет выбрать новый seed, когда UseFixedSeed уже включён. Это управление случайностью, а не гарантия межплатформенного или CPU/GPU-побитового повтора.

Выбрать локальные или мировые координаты

LocalCoords=true заставляет уже выпущенные частицы следовать трансформации эмиттера. При false перемещение эмиттера не переносит существующие частицы: это подходит для оставляемого следа. Выбирайте пространство до начала эффекта; смена параметра посреди симуляции требует отдельной проверки.

Не рассчитывайте траекторию умножением настроек скорости на delta в своём _Process: движение выполняет система частиц. В C# меняйте команды и параметры эффекта, а не позицию каждой визуальной точки.

Сравнить реальный результат

Ниже — кадры текущего движка с Count=128, OneShotBurst=true, LocalSpace=true примерно через 0,6 секунды симуляции. Это иллюстрации, не сравнение FPS и не обещание одинаковой последовательности CPU/GPU.

CpuParticles2D

Разовая вспышка CPU-частиц в текущем движке

GpuParticles2D

Разовая вспышка GPU-частиц в текущем движке

Если эффект не виден или ведёт себя неверно

  • GPU-система пустая: проверьте ProcessMaterial, Amount, Emitting, текстуру и VisibilityRect. Материал отрисовки не заменяет материал симуляции.
  • Частицы не исчезли после StopEmission: это ожидаемо; дождитесь их Lifetime, не считайте остановку эмиссии очисткой.
  • После движения эмиттера след уезжает вместе с ним: проверьте LocalCoords.
  • Вспышка не повторяется: вызывайте Restart, а не полагайтесь на повторное присваивание Emitting=true в незавершённом цикле.
  • Переключили UseGpu во время игры, но тип остался прежним: этот флаг корневого примера выбирает тип только при _Ready.

Материал, траектория и границы · Координаты 2D

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

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

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

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