LeCodesdocs

Particles

GPU-система частиц как узел. Дай ей Material и настройки эмиттера, добавь в сцену — и она стримит частицы: дым, искры, пыль. Расширяет Node: двигай/вращай узел, чтобы двигать эмиттер. Каждая опция также живой сеттер, поэтому rate, color, gravity, … можно менять в рантайме.

Обзор

TypeScript
const sparks = new Particles({
  material: Material.unlit({ color: '#ffcc44' }),
  rate: 80,                                                     // частиц/секунду
  shape: { type: 'point', v: [0, 0, 0] },
  startVelocity: { dir: [0, 1, 0], speed: { min: 2, max: 4 } },
  gravity: 9.8,
  lifetime: { min: 0.6, max: 1.2 },
  size: { min: 0.05, max: 0.12 },
  color: dynamicColor('#ffcc44', 0.7, '#ffffff', 1, '#000000'), // затухание за время жизни
})
sparks.position = [0, 1, 0]
scene.add(sparks)

crate.addEventListener('click', () => sparks.spawn(40))         // всплеск поверх rate

Опции

TypeScript
new Particles(options: {
  material: Material                       // обязательно — как рисуется каждая частица
  rate?: number                            // частиц в секунду
  shape?: { type: 'point', v: Vec3Like }   // спавн со смещением (относительно эмиттера)
        | { type: 'box', min: Vec3Like, max: Vec3Like }   // спавн случайно внутри бокса
  startVelocity?: Vec3Like | VelocityValue // см. ниже
  gravity?: number                         // ускорение вниз, мировые единицы/с²
  drag?: number                            // затухание скорости — выше тормозит частицы быстрее
  lifetime?: number | { min, max }         // секунды; диапазон = случайно на частицу
  color?: DynamicColor                     // константа, случайный диапазон или кривая dynamicColor()
  size?: DynamicValue                      // константа, диапазон или кривая dynamic() (алиас custom[0])
  rotation?: DynamicValue                  // то же (алиас custom[1])
  noise?: { strength?, frequency?, speed? } | null   // турбулентность; все поля по умолчанию 1
})

Всё, кроме material, имеет нативный дефолт — new Particles({ material }) уже эмитит. Поля типа number | { min, max } принимают плоское значение («всегда это») или диапазон («случайно на частицу»).

Not implemented

rate применяет только простое число — диапазон { min, max } типизирован, но молча игнорируется.

startVelocity

Простой Vec3Like — фиксированное направление запуска; объект выбирает режим направления плюс опциональную рандомизацию:

TypeScript
startVelocity: {
  dir?: Vec3Like                                     // запуск вдоль этого направления, или…
  from?: Vec3Like                                    // …прочь от этой точки, или…
  to?: Vec3Like                                      // …к этой точке
  speed?: number | { min: number, max: number }      // масштабирует направление; диапазон = на частицу
  randomizeAngle?: { min: Vec3Like, max: Vec3Like }  // дрожание угла запуска в пределах границ
}
TypeScript
sparks.startVelocity = {
  dir: [0, 1, 0],
  speed: { min: 2, max: 5 },
  randomizeAngle: { min: [-15, 0, -15], max: [15, 0, 15] },
}

Живые сеттеры и методы

TypeScript
sparks.spawn(count): this      // эмитировать всплеск прямо сейчас, поверх `rate` — чейнится
sparks.material                // get/set — сменить материал отрисовки на лету

// живые сеттеры только-для-записи (те же типы, что опции):
sparks.rate = 120
sparks.shape = { type: 'box', min: [-1, 0, -1], max: [1, 0, 1] }
sparks.startVelocity = [0, 3, 0]
sparks.gravity = 0
sparks.drag = 0.5
sparks.lifetime = { min: 0.5, max: 1 }
sparks.color = '#88ccff'
sparks.size = dynamic(0.1, 'multiply', 0, 1, 1, 4)
sparks.rotation = { min: 0, max: 360 }
sparks.noise = { strength: 2 }         // или null, чтобы отключить

sparks.custom[0] = 0.2                 // сырые слоты параметров кривой 0..3; size/rotation — алиасы 0/1
sparks.custom[2] = dynamic(0, 'inc', 0, 0, 1, 1)   // слоты 2/3 питают кастомный материал

Жизненный цикл: система эмитит непрерывно с конструирования; sparks.destroy() (из Node) убирает её. Для одноразового эффекта поставь rate: 0 и вызывай spawn(n).

Кривые за время жизни частицы — dynamic() / dynamicColor()

size, rotation, custom[i] и color могут анимироваться за время жизни каждой частицы. Кривая — это плоский список стопов (t, значение), где t идёт 0..1 за жизнь частицы.

TypeScript
dynamic(
  baseValue: number | { min, max },   // значение при рождении (диапазон = случайно на частицу)
  type: 'multiply' | 'inc',           // кривая умножает базу или прибавляет к ней
  ...stops: (number | { min, max })[] // плоско t, значение, t, значение, … (диапазон значения = случайно на частицу)
): DynamicValue

dynamicColor(
  baseValue: string | { min, max },                    // стартовый цвет (диапазон = случайно между двумя цветами)
  ...stops: (number | string | { min, max })[]         // плоско t, цвет, t, цвет, …
): DynamicColor                                        // кривая УМНОЖАЕТ базовый цвет
TypeScript
sparks.size = dynamic(0.1, 'multiply', 0, 1, 1, 4)       // вырасти до 4× к концу жизни
sparks.size = dynamic(0.3, 'multiply', 0, 1, 1, 0)       // сжаться в ничто
sparks.color = dynamicColor('#ffaa33', 0.7, '#ffffff', 1, '#000000')   // затухание в чёрный к концу

dynamicColor умножает базу, поэтому его главное применение — затухание яркости/альфы к смерти. Держи t по возрастанию 0 → 1 и число стопов малым — стопы запекаются в короткую нативную кривую, а не в произвольный сплайн.

Подводные камни

TypeScript
// ✗ покадровый spawn() для имитации частоты эмиссии
setLoop(() => sparks.spawn(1))
// ✓ для этого есть rate
sparks.rate = 60

// ✗ ждать, что диапазон rate сработает
new Particles({ material, rate: { min: 10, max: 50 } })   // игнорируется — см. заметку выше

Смотрите также

  • MaterialMaterial.unlit({...}) — обычный материал частиц.
  • Node — трансформ (эмиттер), destroy().
  • Scene — где живёт система.