Материал частиц 2D: движение и область видимости
Разделить параметры эмиттера, симуляции и отрисовки; задать безопасные границы эффекта.
Разделить три группы настроек
| Группа | Где находится | Примеры |
|---|---|---|
| Жизненный цикл и пространство | GpuParticles2D | Amount, Lifetime, OneShot, LocalCoords, seed |
| Движение и свойства частиц | ProcessMaterial | Direction, Spread, InitialVelocityMin/Max, Gravity, Color |
| Изображение | Texture и Material самого CanvasItem | Текстура частицы, материал её отрисовки |
ParticleProcessMaterial — материал симуляции. CanvasItemMaterial — другой ресурс для отрисовки; не назначайте его вместо ProcessMaterial. Пользовательский particle-шейдер требует соответствующего ShaderMaterial и отдельной проверки.
Полный запуск и Particle2DExample.cs находятся в руководстве CPU/GPU. Файл ниже содержит весь пресет, включая CPU-вариант с аналогичными начальными параметрами.
Particle2DPreset.cs
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);
}
}Настроить направление и ускорение
В 2D положительный Y направлен вниз. Direction=(0,-1,0) задаёт выброс вверх, Spread=30 — разброс направлений, InitialVelocityMin=60 и Max=100 — диапазон начальной скорости. Gravity=(0,90,0) ускоряет вниз, поэтому частицы со временем возвращаются.
У GPU-материала здесь Vector3, хотя эффект двумерный. ParticleFlagDisableZ=true ограничивает движение плоскостью XY. У CpuParticles2D соответствующие Direction и Gravity — Vector2 на самой Entity.
Скорость и ускорение — разные величины. Не умножайте значения пресета на delta перед присваиванием: шаг времени учитывает симуляция. Часть параметров выбирается при рождении частиц, поэтому после изменения начальных условий делайте Restart для сопоставимого просмотра.
Не полагаться на скрытую адаптацию ресурса
Новый ParticleProcessMaterial имеет 3D-ориентированную гравитацию (0,-9.8,0). В текущей реализации назначение такого материала в GpuParticles2D при выключенном DisableZ включает этот флаг и меняет гравитацию на (0,98,0). Это условная адаптация, а не правило для любого произвольного материала.
Если ресурс общий, изменяется именно общий экземпляр. Другой эмиттер может получить новые параметры неожиданно. В пресете DisableZ и Gravity заданы до назначения; для независимых настроек создавайте независимые ресурсы либо осознанно дублируйте их.
Рассчитать VisibilityRect
VisibilityRect нужен для culling всего GPU-эффекта. Он не задаёт область рождения, не удерживает частицы внутри и не является физической границей. Слишком маленький прямоугольник может выключать отображение эффекта, когда видимые частицы ещё находятся на экране.
Для данного пресета Lifetime=2, начальная скорость не больше 100, разброс до 30°, ускорение вниз 90 и текстура 8×8. Для неподвижного эмиттера с единичным масштабом можно взять консервативные оценки:
- по X: 100 × sin(30°) × 2 + 4 = 104 в каждую сторону;
- минимальная координата Y не меньше −100² / (2 × 90) − 4, то есть примерно −60;
- максимальная координата Y не больше 90 × 2² / 2 + 4 = 184, если отбросить начальную скорость вверх.
VisibilityRect=(-140,-120,280,320) покрывает X от −140 до 140 и Y от −120 до 200. Это запас для этого пресета, не универсальная формула для любого материала. При другом Lifetime, ускорении, размере, масштабе, дополнительных силах или быстром движении эмиттера пересчитайте его. Особенно учитывайте оставляемые мировые частицы при LocalCoords=false: эмиттер может уйти далеко от них.
Ограничение CaptureRect в текущем OpenGL-пути
В реальном прогоне GpuParticles2D.CaptureRect() для видимых 2D-частиц вернул пустую область с ошибками чтения GPU-буфера. В реализации Compatibility этот путь читает данные как ParticleInstanceData3D. Это сохранённая проблема текущей проверенной сборки, а не успешный расчёт границ.
Поэтому пример задаёт VisibilityRect явно и не использует CaptureRect как автоматическую настройку. Не интерпретируйте пустой результат этого вызова как отсутствие живых частиц. Для других backend надёжность этого readback отдельно не проверена.
Настраивать эффект без потери причины
Сначала зафиксируйте Amount, Lifetime, seed и пространство. Затем меняйте одну группу: направление/скорость, гравитацию, изображение, границы. Сравнивайте после Restart при одинаковом времени наблюдения. FixedFps управляет шагом симуляции, а не гарантирует частоту отображения окна.
При исчезновении эффекта сначала проверьте жизненный цикл и видимость, затем материал и траекторию. Увеличение Energy света или CanvasItem.Modulate не заменяет ProcessMaterial и не исправляет слишком маленькие границы.