QML

Qt Quick Particles: системы частиц в QML

Введение

Системы частиц — способ создать визуально насыщенные эффекты (искры, дождь, дым, конфетти при успешном действии) из большого количества простых, однотипных элементов, движущихся по заданным правилам. Qt Quick предоставляет встроенный модуль QtQuick.Particles для декларативного описания таких эффектов прямо в QML, без необходимости вручную анимировать каждый отдельный элемент. В статье разберём базовую структуру системы частиц.

Концепция

Система частиц в Qt Quick состоит из нескольких связанных компонентов: ParticleSystem — корневой контейнер, координирующий все остальные части; ImageParticle или ItemParticle — определяет, как выглядит каждая отдельная частица (изображение или произвольный QML-компонент); Emitter — определяет, где, как часто и с какими начальными параметрами (скорость, направление, время жизни) рождаются новые частицы; и опционально различные «воздействия» (Gravity, Wander, Turbulence) — модификаторы, влияющие на движение частиц после их рождения.

Пример кода: Qt Quick Particles

import QtQuick
import QtQuick.Particles

Item {
    width: 400
    height: 400

    ParticleSystem {
        id: particleSystem
    }

    // Определяет визуальное представление частиц — здесь простое изображение искры
    ImageParticle {
        system: particleSystem
        source: "qrc:/particles/spark.png"
        color: "#ffaa33"
        colorVariation: 0.3 // случайная вариация оттенка для каждой частицы
        alpha: 0.8
    }

    // Источник рождения частиц — расположен в центре сцены
    Emitter {
        id: emitter
        system: particleSystem
        anchors.centerIn: parent
        emitRate: 50          // частиц в секунду
        lifeSpan: 2000        // время жизни каждой частицы в миллисекундах
        lifeSpanVariation: 500
        size: 16
        sizeVariation: 8

        velocity: AngleDirection {
            angle: 270         // направление "вверх" (270 градусов в системе координат Qt Quick)
            angleVariation: 45 // случайное отклонение направления для естественности эффекта
            magnitude: 100
            magnitudeVariation: 50
        }

        enabled: false // по умолчанию выключен, активируется программно
    }

    // Гравитация, постепенно "роняющая" частицы вниз — типична для эффекта искр/конфетти
    Gravity {
        system: particleSystem
        magnitude: 150
        angle: 90
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            emitter.enabled = true;
            burstTimer.start();
        }
    }

    Timer {
        id: burstTimer
        interval: 300
        onTriggered: emitter.enabled = false // короткий "всплеск" частиц при клике
    }
}

Пояснения к коду

ParticleSystem сам по себе не имеет визуального представления — это координирующий объект, к которому через свойство system подключаются все остальные части (ImageParticle, Emitter, Gravity). ImageParticle определяет, что каждая отдельная частица — это изображение spark.png с заданным базовым цветом и случайной вариацией оттенка (colorVariation), что делает множество одновременно видимых частиц визуально разнообразнее, а не идентичными копиями друг друга.

Emitter настраивает параметры рождения частиц — emitRate определяет интенсивность эффекта, velocity через AngleDirection задаёт направление и силу начального «импульса» каждой частицы с элементом случайности (angleVariation, magnitudeVariation) для естественного, не механически одинакового вида эффекта. Gravity — пример воздействия, постоянно влияющего на все частицы системы после их рождения, создавая эффект падения под действием «силы тяжести», направленной в данном случае вниз (угол 90 градусов). Включение emitter.enabled по клику и таймер, выключающий его через короткое время, создают эффект кратковременного «всплеска» частиц, а не непрерывного потока.

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

  • Большое количество одновременно видимых частиц (высокий emitRate в сочетании с длительным lifeSpan) может создавать значительную нагрузку на рендеринг, особенно на слабом оборудовании или embedded-устройствах (основной цикл статей, статья 242) — количество одновременно живых частиц равно приблизительно emitRate × lifeSpan / 1000, и для эффекта с emitRate: 50 и lifeSpan: 2000 это около 100 одновременных частиц, что для большинства устройств приемлемо, но для более интенсивных эффектов стоит явно тестировать производительность на целевом железе.
  • Угол в AngleDirection отсчитывается не интуитивно для разработчиков, ожидающих стандартную математическую систему координат — в Qt Quick ось Y направлена вниз, и угол 270 градусов соответствует направлению «вверх» на экране, а не привычному из математики направлению вправо при угле 0 — путаница с направлением углов частая причина, когда частицы «летят не туда, куда ожидалось» при первой попытке настройки эмиттера.
  • Файл изображения частицы, заданный с неверным путём или не включённый в Qt Resource System, тихо не создаёт видимого эффекта — частицы продолжают логически существовать и двигаться согласно настройкам, но визуально невидимы, что легко спутать с проблемой настройки самого эмиттера, тогда как реальная причина — простая опечатка в пути к ресурсу изображения.
  • Время жизни приложения с активной, но не остановленной системой частиц (забытый emitter.enabled = false или работающий бесконечно Timer) продолжает потреблять ресурсы на генерацию и анимацию частиц даже когда визуальный эффект давно не нужен пользователю — для эффектов, призванных быть кратковременными (как взрыв конфетти при успешном действии), важно явно и надёжно останавливать эмиттер после завершения нужной продолжительности эффекта.