Простая система частиц на SFML C++

1c17059fe0f62612015a6a7349b696e8.jpg

Простая система частиц на SFML C++

Приветствую всех, кто интересуется графикой и программированием! Сегодня мы погрузимся в увлекательный мир создания систем частиц с помощью библиотеки SFML на C++. Эта тема открывает широкие возможности для визуализации различных эффектов, от простых искр и дыма до сложных взрывов и магических заклинаний в играх и других приложениях.

Системы частиц — это мощный инструмент для генерации динамических визуальных эффектов. Они основаны на идее создания множества мелких объектов (частиц), каждый из которых обладает собственными параметрами, такими как положение, скорость, цвет, размер и время жизни. Управляя этими параметрами, можно добиться самых разнообразных результатов.

Создадим отдельный класс для управления системой частиц.

Назовём его ParticleSystem. Ниже реализация кода данного класса.

#pragma once

#include 
#include 


struct Particle {

    sf::Vector2f velocity;     

    sf::Vector2f position; 

    sf::Color color;           
};


class ParticleSystem : public sf::Drawable, public sf::Transformable {

public:

    ParticleSystem();

    
    void addParticle(sf::Vector2f position, sf::Vector2f velocity, sf::Color color);

    
    void update(sf::Time elapsed, sf::RenderWindow& window);

private:

    std::vector particles;   
    
    sf::VertexArray vertices;         

    virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
};

Опишем свойства и методы класса ParticleSystem.

Класс наследуется от классов библиотеки SFML:

Drawable — позволяет объектам класса ParticleSystem быть нарисованными с помощью метода window.draw, переопределяя метод draw;

Transformable — позволяет использовать трансформации, в нашем случае перемещение для всей системы частиц.

Публичные поля.

Конструктор класса — инициализирует систему частиц.

Метод addParticle добавляет новую частицу в систему.

Параметры данного метода:

position — начальная позиция частицы;

velocity — вектор скорости;

color — цвет частицы.

Метод update — обновляет состояние всех частиц.

Параметры метода:

elapsed — время, прошедшее с момента последнего обновления, используется для корректного перемещения частиц;

window — cсылка на графическое окно, для проверки, находятся ли частицы внутри границ окна.

Приватные поля.

Контейнер вектор particles для хранения всех частиц. Каждая частица представляет собой объект структуры Particle.

Массив вершин vertices, представляющий графическое отображение всех частиц. Связывает частицы с их визуальными атрибутами это позиция и цвет.

Виртуальный метод draw. Это переопределённый метод от класса Drawable, отвечающий за отрисовку частиц.

Далее реализовываем методы данного класса.

#include "ParticleSystem.h"

// Конструктор
ParticleSystem::ParticleSystem() {
    vertices.setPrimitiveType(sf::PrimitiveType::Points);
}


void ParticleSystem::addParticle(sf::Vector2f position, sf::Vector2f velocity, sf::Color color) {
    Particle particle;
    particle.position = position;
    particle.velocity = velocity;
    particle.color = color;

    particles.push_back(particle);

    sf::Vertex vertex;
    vertex.position = position;
    vertex.color = color;
    vertices.append(vertex);
}


void ParticleSystem::update(sf::Time elapsed, sf::RenderWindow& window) {

    std::vector updatedParticles;
    sf::VertexArray updatedVertices(sf::PrimitiveType::Points);

    for (std::size_t i = 0; i < particles.size(); ++i) {
        Particle& particle = particles[i];
        particle.position += particle.velocity * elapsed.asSeconds();

        if (particle.position.x >= 0 && particle.position.x <= window.getSize().x &&
            particle.position.y >= 0 && particle.position.y <= window.getSize().y) {
            updatedParticles.push_back(particle);

            sf::Vertex vertex;
            vertex.position = particle.position;
            vertex.color = particle.color;
            updatedVertices.append(vertex);
        }
    }

    particles = std::move(updatedParticles);
    vertices = std::move(updatedVertices);
}

void ParticleSystem::draw(sf::RenderTarget& target, sf::RenderStates states) const {
    states.transform *= getTransform();
    target.draw(vertices, states);
}

Конструктор ParticleSystem устанавливает тип графического примитива массива вершин как «точки». Это значит, что каждая вершина будет отрисовываться как отдельная точка на экране.

Метод добавления частицы addParticle. Добавляет новую частицу в систему. Создаёт объект Particle, в который записываются: позиция, скорость, цвет. Этот объект добавляется в массив particles т.е. вектор всех частиц. Создаётся вершина vertex, которая определяет: позицию частицы и цвет частицы. Эта вершина добавляется в массив vertices, чтобы позже её можно было отобразить.

Метод обновления частиц update. Обновляет позиции частиц и удаляет те частицы, которые вышли за границы окна. Создаются временные контейнеры:

updatedParticles — для хранения обновлённых частиц;

updatedVertices — для хранения новых вершин, соответствующих обновлённым частицам.

Цикл перебирает каждую частицу из вектора particles. Вычисляется новое положение частицы. Если новая позиция частицы находится в пределах границ окна: Частица добавляется в updatedParticles. Создаётся соответствующая вершина, которая добавляется в updatedVertices. После завершения цикла, cтарые массивы particles и vertices заменяются на новые с использованием move для оптимального переноса данных.

Метод отрисовки draw отвечает за отрисовку системы частиц на экране. Метод является переопределением функции draw из класса Drawable. Конструкция states.transform *= getTransform () применяет текущую трансформацию, например позицию к нашей точке, перед отрисовкой. Далее target.draw (vertices, states) отрисовывает массив вершин vertices на целевом объекте target т.е. RenderWindow.

В следующем коде используем данный класс для реализации эффекта фейерверка т.е. распыления частиц в разные стороны, пока будет зажата левая кнопка мыши.


#include "ParticleSystem.h"
#include 


std::random_device rd;
std::mt19937 gen(rd());


float randomFloat(float min, float max) {
    std::uniform_real_distribution dist(min, max);
    return dist(gen);
}

int randomInt(int min, int max) {
    std::uniform_int_distribution dist(min, max);
    return dist(gen);
}

int main() {
    
    sf::RenderWindow window(sf::VideoMode(1280, 720), "Particle System with Modules");

    ParticleSystem particles;

    sf::Clock clock;                   
    sf::Clock mousePressTimer;         

    bool isMousePressed = false;       
    sf::Vector2f emitterPosition;      

    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();

            
            if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left) {
                isMousePressed = true;
                mousePressTimer.restart();
                emitterPosition = sf::Vector2f(sf::Mouse::getPosition(window));
            }

            if (event.type == sf::Event::MouseButtonReleased && event.mouseButton.button == sf::Mouse::Left) {
                isMousePressed = false;
            }
        }

        sf::Time elapsed = clock.restart();

        
        if (isMousePressed) {

            if (mousePressTimer.getElapsedTime().asMilliseconds() >= 1.0f) {
                mousePressTimer.restart(); 

                float angle = randomFloat(0.f, 360.f) * 3.14f / 180.f;
                float speed = randomFloat(50.f, 200.f);

                sf::Vector2f velocity(std::cos(angle) * speed, std::sin(angle) * speed);
                sf::Color color = sf::Color(randomInt(0, 255), randomInt(0, 255), randomInt(0, 255));

                particles.addParticle(emitterPosition, velocity, color);

            }
        }

        particles.update(elapsed, window);

        window.clear();
        window.draw(particles);
        window.display();
    }

    return 0;
}

Устанавливаем глобальные переменные для генерации случайных чисел.

std: random_device rd;

std: mt19937 gen (rd ());

Функция randomFloat возвращает случайное число с плавающей точкой в диапазоне от min до max.

Функция randomInt возвращает целое случайное число в заданном диапазоне.

sf: RenderWindow window (sf: VideoMode (1280, 720), «Particle System with Modules»);

Данный код создаёт графическое окно размером 1280×720 пикселей с заголовком «Particle System with Modules».

Объекты и переменные:

particles — экземпляр системы частиц, где хранятся и обновляются данные о частицах;

clock — таймер для измерения времени между кадрами т.е. управление скоростью обновления;

mousePressTimer — таймер для определения длительности удержания левой кнопки мыши;

isMousePressed — флаг, указывающий, зажата ли левая кнопка мыши; emitterPosition — позиция эмиттера т.е. координаты точки появления частиц.

Блок обработки событий:

event — объект для обработки событий;

if (event.type == sf: Event: Closed) window.close ();

Конструкция обработки события закрытия окна. Если пользователь нажал кнопку «Закрыть», окно закрывается.

Блок обработки события — нажатие и удержание левой кнопки мыши.

if (event.type == sf: Event: MouseButtonPressed && event.mouseButton.button == sf: Mouse: Left) {

isMousePressed = true;

mousePressTimer.restart ();

emitterPosition = sf: Vector2f (sf: Mouse: getPosition (window));

}

if (event.type == sf: Event: MouseButtonReleased && event.mouseButton.button == sf: Mouse: Left) {

isMousePressed = false;

}

При нажатии кнопки мыши, устанавливается флаг isMousePressed в положение true. Сбрасывается таймер mousePressTimer т.е. запускается отсчёт времени. Записывается текущая позиция мыши в emitterPosition.

При отпускании кнопки: флаг isMousePressed сбрасывается.

Также не забываем создать глобальный цикл графического окна while (window.isOpen ()) и цикл обработки событий while (window.pollEvent (event)).

Блок обновление частиц:

elapsed — время, прошедшее с последнего кадра, используется для обновления движения частиц.

if (mousePressTimer.getElapsedTime ().asMilliseconds () >= 1.0f)

Если мышь зажата, каждую одну миллисекунду создаётся новая частица.

float angle = randomFloat (0.f, 360.f) * 3.14f / 180.f;

Вычисляется случайный угол и скорость частицы.

float speed = randomFloat (50.f, 200.f);

Вычисляется начальная скорость и цвет.

Новая частица добавляется в систему через метод addParticle.

Метод particlesupdate обновляет все частицы.

Далее очищаем графическое окно window.clear (), отрисовываем частицы через метод draw системы частиц window.draw (particles).

window.display ()

Отображаем новый кадр.

58c050db3e01cb0b8640c743847fe1a4.gif

Более подробную инструкцию вы можете получить, посмотрев видео «Простая система частиц SFML C++»

Клонировать репозиторий

Телеграмм канал «Программирование игр С++/С#»

Habrahabr.ru прочитано 1731 раз