Switch particle lifetimes to use std::chrono instead of sf::Clock

Also create emitter properties, fix some code and some other things.
This commit is contained in:
SeanOMik 2021-03-29 19:52:27 -05:00
parent c949d44d98
commit bde19814a5
No known key found for this signature in database
GPG Key ID: CA09E5BE1F32728A
11 changed files with 221 additions and 65 deletions

View File

@ -4,10 +4,15 @@
// Email: seanomik@gmail.com // Email: seanomik@gmail.com
// //
#include "simpleengine/random.h"
#include <SFML/Graphics/CircleShape.hpp>
#include <SFML/Graphics/PrimitiveType.hpp>
#include <SFML/Graphics/Rect.hpp> #include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/RenderStates.hpp> #include <SFML/Graphics/RenderStates.hpp>
#include <SFML/System/Vector2.hpp>
#include <SFML/System/Vector3.hpp> #include <SFML/System/Vector3.hpp>
#include <memory>
#include <simpleengine/game.h> #include <simpleengine/game.h>
#include <simpleengine/event.h> #include <simpleengine/event.h>
#include <simpleengine/entity.h> #include <simpleengine/entity.h>
@ -16,6 +21,7 @@
// Particle // Particle
#include <simpleengine/particle/emitter.h> #include <simpleengine/particle/emitter.h>
#include <simpleengine/particle/particle_property.h> #include <simpleengine/particle/particle_property.h>
#include <simpleengine/particle/particle_emitter_property.h>
#include <simpleengine/particle/properties/random_lifetime_property.h> #include <simpleengine/particle/properties/random_lifetime_property.h>
#include <simpleengine/particle/properties/random_velocity_property.h> #include <simpleengine/particle/properties/random_velocity_property.h>
@ -25,6 +31,9 @@
#include <ios> #include <ios>
#include <random> #include <random>
#define _USE_MATH_DEFINES
#include <math.h>
namespace se = simpleengine; namespace se = simpleengine;
class ParticleEmitter : public se::particle::ParticleEmitter { class ParticleEmitter : public se::particle::ParticleEmitter {
@ -34,14 +43,17 @@ private:
sf::Sprite sprite; sf::Sprite sprite;
sf::Texture texture; sf::Texture texture;
Super::ParticlePropertyVector properties = { Super::ParticlePropertyVector particle_properties;
std::make_shared<se::particle::RandomVelocityParticleProperty>(se::Range2f(-1.5f, 1.5f, -1.5f, 1.5f)), Super::EmitterPropertyVector emitter_properties;
std::make_shared<se::particle::RandomLifetimeParticleProperty>(se::Rangef(1'500, 9'500))
};
public: public:
ParticleEmitter() : Super(texture, sf::Vector2f(350, 350), 0.7, 5, 1500, se::Range2f(-50, 50, -50, 50), se::particle::ParticleAttributes{0, sf::Vector2f(), 5, sf::Vector2f(2, 2)}, properties) { ParticleEmitter() : Super(texture, sf::Vector2f(350, 350), .5f, 10, 900, se::Range2f(-50, 50, -50, 50), se::particle::ParticleAttributes{2000, sf::Vector2f(.1f, .1f), 5, sf::Vector2f(2, 2)}, particle_properties, emitter_properties) {
texture.loadFromFile("particle.png"); // The particle I tested with was 5x5 pixels texture.loadFromFile("particle.png"); // The particle I tested with was 5x5 pixels
texture.setSmooth(true); texture.setSmooth(true);
particle_properties.emplace_back(std::make_unique<se::particle::RandomVelocityParticleProperty>(se::Range2f(-1.5f, 1.5f, -1.5f, 1.5f)));
using namespace std::chrono_literals;
particle_properties.emplace_back(std::make_unique<se::particle::RandomLifetimeParticleProperty>(2'500ms, 5'500ms));
} }
void Update(const float& delta_time) override { void Update(const float& delta_time) override {

View File

@ -21,7 +21,6 @@ namespace simpleengine {
virtual void Render(sf::RenderTarget* target) = 0; virtual void Render(sf::RenderTarget* target) = 0;
protected: protected:
sf::RenderWindow* window; sf::RenderWindow* window;
bool quit = false;
}; };
} }

View File

@ -9,6 +9,7 @@
#include "../event.h" #include "../event.h"
#include "particle_property.h" #include "particle_property.h"
#include "particle_emitter_property.h"
#include "particle.h" #include "particle.h"
#include "../random.h" #include "../random.h"
#include "../range_2.h" #include "../range_2.h"
@ -19,7 +20,6 @@
#include <SFML/System/Clock.hpp> #include <SFML/System/Clock.hpp>
#include <SFML/System/Vector2.hpp> #include <SFML/System/Vector2.hpp>
#include <iostream>
namespace simpleengine { namespace simpleengine {
namespace particle { namespace particle {
@ -32,12 +32,15 @@ namespace simpleengine {
class ParticleEmitter : public simpleengine::Event { class ParticleEmitter : public simpleengine::Event {
protected: protected:
using ParticlePropertyPtr = std::shared_ptr<simpleengine::particle::ParticleProperty>; using ParticlePropertyPtr = std::unique_ptr<simpleengine::particle::ParticleProperty>;
using ParticlePropertyVector = std::vector<ParticlePropertyPtr>; using ParticlePropertyVector = std::vector<ParticlePropertyPtr>;
public:
ParticleEmitter(sf::Texture& texture, sf::Vector2f position, double emit_variance, uint32_t emit_count, uint32_t particle_count, Range2f particle_range, ParticleAttributes attributes, ParticlePropertyVector& properties);
void SetProperties(ParticlePropertyVector& properties); using EmitterPropertyPtr = std::unique_ptr<simpleengine::particle::ParticleEmitterProperty>;
using EmitterPropertyVector = std::vector<EmitterPropertyPtr>;
public:
ParticleEmitter(sf::Texture& texture, sf::Vector2f position, double emit_variance, uint32_t emit_count, uint32_t particle_count, Range2f particle_range, ParticleAttributes attributes, ParticlePropertyVector& particle_properties, EmitterPropertyVector& emitter_properties);
void SetProperties(ParticlePropertyVector particle_properties);
virtual void Update(const float& delta_time) override; virtual void Update(const float& delta_time) override;
virtual void Render(sf::RenderTarget* target) override; virtual void Render(sf::RenderTarget* target) override;
@ -49,7 +52,8 @@ namespace simpleengine {
uint32_t particle_count; uint32_t particle_count;
Range2f particle_range; Range2f particle_range;
ParticleAttributes attributes; ParticleAttributes attributes;
ParticlePropertyVector& properties; ParticlePropertyVector& particle_properties;
EmitterPropertyVector& emitter_properties;
std::vector<simpleengine::particle::Particle> particles; std::vector<simpleengine::particle::Particle> particles;
sf::Clock time; sf::Clock time;

View File

@ -15,6 +15,7 @@
#include <SFML/System/Time.hpp> #include <SFML/System/Time.hpp>
#include <SFML/System/Vector2.hpp> #include <SFML/System/Vector2.hpp>
#include <chrono>
#include <memory> #include <memory>
namespace simpleengine { namespace simpleengine {
@ -23,25 +24,47 @@ namespace simpleengine {
private: private:
using Super = simpleengine::Event; using Super = simpleengine::Event;
protected: protected:
using ParticlePropertyPtr = std::shared_ptr<simpleengine::particle::ParticleProperty>; using ParticlePropertyPtr = std::unique_ptr<simpleengine::particle::ParticleProperty>;
using ParticlePropertyVector = std::vector<ParticlePropertyPtr>; using ParticlePropertyVector = std::vector<ParticlePropertyPtr>;
public: public:
Particle(sf::Texture& texture, sf::Vector2f velocity, uint32_t lifetime_ms, float rotation_velocity, ParticlePropertyVector properties); friend class ParticleEmitter;
template<class DurRep, class DurPeriod>
Particle(sf::Texture& texture, sf::Vector2f velocity, std::chrono::duration<DurRep, DurPeriod> lifetime, float rotation_velocity,
ParticlePropertyVector properties)
: Super(nullptr), velocity(velocity), rotation_velocity(rotation_velocity), properties(std::move(properties)) {
sprite.setTexture(texture);
birth_point = std::chrono::high_resolution_clock::now();
death_point = birth_point + lifetime;
// Trigger OnParticleSpawn for particle properities.
for (ParticlePropertyPtr& property : this->properties) {
property->OnParticleSpawn(*this);
}
}
Particle(const Particle& other);
Particle& operator=(Particle other);
virtual void Update(const float& delta_time) override; virtual void Update(const float& delta_time) override;
virtual void Render(sf::RenderTarget* target) override; virtual void Render(sf::RenderTarget* target) override;
sf::Sprite& GetSprite(); template<class DurRep, class DurPeriod>
const sf::Time GetAge() const; void SetLifetime(std::chrono::duration<DurRep, DurPeriod> lifetime) {
death_point = std::chrono::high_resolution_clock::now() + lifetime;
}
std::chrono::microseconds GetAge() const;
std::chrono::duration<long long, std::ratio<1, 1000000000>> GetLifetime() const;
sf::Sprite sprite; sf::Sprite sprite;
sf::Vector2f velocity; sf::Vector2f velocity;
uint32_t lifetime_ms;
float rotation_velocity; float rotation_velocity;
ParticlePropertyVector properties; ParticlePropertyVector properties;
private: private:
sf::Clock age_clock; std::chrono::time_point<std::chrono::high_resolution_clock> birth_point;
std::chrono::time_point<std::chrono::high_resolution_clock> death_point;
}; };
} }
} }

View File

@ -0,0 +1,24 @@
//
// Created by SeanOMik on 3/23/2021.
// Github: https://github.com/SeanOMik
// Email: seanomik@gmail.com
//
#ifndef SIMPLEENGINE_PARTICLE_EMITTER_PROPERTY_H
#define SIMPLEENGINE_PARTICLE_EMITTER_PROPERTY_H
#include "../event.h"
namespace simpleengine {
namespace particle {
class Particle;
class ParticleEmitterProperty {
public:
//virtual void Clone() = 0;
virtual void BeforeParticleSpawn(simpleengine::particle::Particle& particle) = 0;
virtual void Update(simpleengine::particle::Particle& particle) = 0;
};
}
}
#endif //SIMPLEENGINE_PARTICLE_EMITTER_PROPERTY_H

View File

@ -8,14 +8,14 @@
#define SIMPLEENGINE_PARTICLE_PROPERTY_H #define SIMPLEENGINE_PARTICLE_PROPERTY_H
#include "../event.h" #include "../event.h"
#include <algorithm>
#include <SFML/Graphics/RenderStates.hpp>
namespace simpleengine { namespace simpleengine {
namespace particle { namespace particle {
class Particle; class Particle;
class ParticleProperty { class ParticleProperty {
public: public:
virtual std::unique_ptr<ParticleProperty> Clone() const = 0;
virtual void OnParticleSpawn(simpleengine::particle::Particle& particle) = 0; virtual void OnParticleSpawn(simpleengine::particle::Particle& particle) = 0;
virtual void Update(simpleengine::particle::Particle& particle) = 0; virtual void Update(simpleengine::particle::Particle& particle) = 0;
}; };

View File

@ -12,28 +12,50 @@
#include "../../random.h" #include "../../random.h"
#include "../particle.h" #include "../particle.h"
#include <chrono>
#include <memory>
#include <iostream>
#include <ratio>
namespace simpleengine { namespace simpleengine {
namespace particle { namespace particle {
class RandomLifetimeParticleProperty : public ParticleProperty { class RandomLifetimeParticleProperty : public ParticleProperty {
public: public:
RandomLifetimeParticleProperty(simpleengine::Rangef range) : range(range) { template<class DurRep, class DurPeriod>
RandomLifetimeParticleProperty(std::chrono::duration<DurRep, DurPeriod> min, std::chrono::duration<DurRep, DurPeriod> max)
: range(Range<std::chrono::milliseconds>(
std::chrono::duration_cast<std::chrono::milliseconds>(min),
std::chrono::duration_cast<std::chrono::milliseconds>(max))) {
} }
RandomLifetimeParticleProperty(float min, float max) : RandomLifetimeParticleProperty(Rangef(min, max)) { template<class DurRep, class DurPeriod>
RandomLifetimeParticleProperty(const simpleengine::Range<std::chrono::duration<DurRep, DurPeriod>>& range) : RandomLifetimeParticleProperty(range.min, range.max) {
} }
RandomLifetimeParticleProperty(const RandomLifetimeParticleProperty& other) {
this->range = other.range;
}
std::unique_ptr<ParticleProperty> Clone() const override {
return std::make_unique<RandomLifetimeParticleProperty>(range);
}
void OnParticleSpawn(simpleengine::particle::Particle& particle) override { void OnParticleSpawn(simpleengine::particle::Particle& particle) override {
simpleengine::Random<float> rand; simpleengine::Random<double, std::random_device, std::mt19937_64> rand;
particle.lifetime_ms = rand.NextInRange(range.min, range.max);
int lifetime = rand.NextInRange(range.min.count(), range.max.count());
particle.SetLifetime(std::chrono::milliseconds(lifetime));
//std::cout << "Lifetime: " << lifetime << "ms" << std::endl;
} }
void Update(simpleengine::particle::Particle& particle) override { void Update(simpleengine::particle::Particle& particle) override {
} }
private: private:
simpleengine::Rangef range; simpleengine::Range<std::chrono::milliseconds> range;
}; };
} }
} }

View File

@ -12,11 +12,14 @@
#include "../../random.h" #include "../../random.h"
#include "../particle.h" #include "../particle.h"
#include <random>
#include <iostream>
namespace simpleengine { namespace simpleengine {
namespace particle { namespace particle {
class RandomVelocityParticleProperty : public ParticleProperty { class RandomVelocityParticleProperty : public ParticleProperty {
public: public:
RandomVelocityParticleProperty(simpleengine::Range2f range) : range(range) { RandomVelocityParticleProperty(const simpleengine::Range2f& range) : range(range) {
} }
@ -25,11 +28,19 @@ namespace simpleengine {
} }
RandomVelocityParticleProperty(const RandomVelocityParticleProperty& other) {
this->range = other.range;
}
std::unique_ptr<ParticleProperty> Clone() const override {
return std::make_unique<RandomVelocityParticleProperty>(range);
}
void OnParticleSpawn(simpleengine::particle::Particle& particle) override { void OnParticleSpawn(simpleengine::particle::Particle& particle) override {
simpleengine::Random<float> rand; simpleengine::Random<float, std::random_device, std::mt19937_64> rand;
sf::Vector2f velocity; sf::Vector2f velocity;
velocity.x = rand.NextInRange(range.min_x, range.max_x); velocity.x = rand.NextInRange<std::uniform_real_distribution<float>>(range.min_x, range.max_x);
velocity.y = rand.NextInRange(range.min_y, range.max_y); velocity.y = rand.NextInRange<std::uniform_real_distribution<float>>(range.min_y, range.max_y);
particle.velocity = velocity; particle.velocity = velocity;
} }

View File

@ -11,8 +11,6 @@
#include <SFML/Window/Keyboard.hpp> #include <SFML/Window/Keyboard.hpp>
#include <algorithm> #include <algorithm>
#include <iostream>
simpleengine::MovementComponent::MovementComponent(Entity& owning_entity, Game& game, float max_velocity, float acceleration, float deceleration) : simpleengine::MovementComponent::MovementComponent(Entity& owning_entity, Game& game, float max_velocity, float acceleration, float deceleration) :
Component(owning_entity), max_velocity(max_velocity), acceleration(acceleration), deceleration(deceleration) { Component(owning_entity), max_velocity(max_velocity), acceleration(acceleration), deceleration(deceleration) {

View File

@ -1,44 +1,75 @@
#include "particle/emitter.h" #include "particle/emitter.h"
#include "destructable.h"
simpleengine::particle::ParticleEmitter::ParticleEmitter(sf::Texture& texture, sf::Vector2f position, double emit_variance, uint32_t emit_count, uint32_t particle_count, Range2f particle_range, ParticleAttributes attributes, ParticlePropertyVector& properties) : texture(texture), position(position), emit_variance(emit_variance), emit_count(emit_count), particle_count(particle_count), particle_range(particle_range), attributes(attributes), properties(properties) { #include <chrono>
#include <iostream>
simpleengine::particle::ParticleEmitter::ParticleEmitter(sf::Texture& texture, sf::Vector2f position, double emit_variance,
uint32_t emit_count, uint32_t particle_count, Range2f particle_range, ParticleAttributes attributes,
ParticlePropertyVector& particle_properties, EmitterPropertyVector& emitter_properties)
: texture(texture), position(position), emit_variance(emit_variance), emit_count(emit_count), particle_count(particle_count),
particle_range(particle_range), attributes(attributes), particle_properties(particle_properties), emitter_properties(emitter_properties) {
time.restart(); time.restart();
} }
void simpleengine::particle::ParticleEmitter::SetProperties(ParticlePropertyVector& properties) { void simpleengine::particle::ParticleEmitter::SetProperties(ParticlePropertyVector particle_properties) {
this->properties = properties; this->particle_properties = std::move(particle_properties);
} }
void simpleengine::particle::ParticleEmitter::Update(const float &delta_time) { void simpleengine::particle::ParticleEmitter::Update(const float &delta_time) {
// Update particles and check if they are being destroyed. If they are, destroy them. // Update each particle and remove it from the vector if it is ready to be destroyed.
for (std::vector<simpleengine::particle::Particle>::iterator it = particles.begin(); it != particles.end(); ) { particles.erase(std::remove_if(particles.begin(), particles.end(),
it->Update(delta_time); [&delta_time](simpleengine::particle::Particle& particle){
particle.Update(delta_time);
if (it->IsDestroying()) { return particle.IsDestroying();
it = particles.erase(it); }), particles.end());
} else {
it++;
}
}
// Check if we can spawn particles with max particle count, also use chance to check if we can. // Check if we can spawn particles with max particle count, also use chance to check if we can.
if (particles.size() < particle_count && random.NextInRange(0, 1) < emit_variance) { if (particles.size() + emit_count <= particle_count && random.NextInRange(0, 1) < emit_variance) {
// Make sure that we don't emit too many particles to go over the max particle count // Make sure that we don't emit too many particles to go over the max particle count
uint32_t emitting_ct = (particles.size() + emit_count > particle_count) ? particle_count - particles.size() : emit_count; //uint32_t emitting_ct = (particles.size() + emit_count > particle_count) ? particle_count - particles.size() : emit_count;
for (uint32_t i = 0; i < emitting_ct; i++) { for (uint32_t i = 0; i < emit_count; i++) {
Particle particle(texture, attributes.start_velocity, attributes.lifetime_ms, attributes.rotation_velocity, properties);
ParticlePropertyVector particle_properties_clone;
for (ParticlePropertyPtr& property : particle_properties) {
particle_properties_clone.push_back(std::move(property->Clone()));
}
Particle particle(texture, attributes.start_velocity, std::chrono::milliseconds(attributes.lifetime_ms), attributes.rotation_velocity, std::move(particle_properties_clone));
// Offset the position of the particle randomly // Offset the position of the particle randomly
sf::Vector2f new_pos; sf::Vector2f new_pos;
new_pos.x = random.NextInRange(particle_range.min_x, particle_range.max_x); new_pos.x = random.NextInRange(particle_range.min_x, particle_range.max_x);
new_pos.y = random.NextInRange(particle_range.min_y, particle_range.max_y); new_pos.y = random.NextInRange(particle_range.min_y, particle_range.max_y);
new_pos += position; new_pos += position;
particle.GetSprite().setPosition(new_pos); particle.sprite.setPosition(new_pos);
particle.GetSprite().setScale(attributes.scale.x, attributes.scale.y); particle.sprite.setScale(attributes.scale.x, attributes.scale.y);
// Run the emitter properties.
for(EmitterPropertyPtr& emitter_property : emitter_properties) {
emitter_property->BeforeParticleSpawn(particle);
}
particles.emplace_back(std::move(particle)); particles.emplace_back(std::move(particle));
} }
} }
if (particles.size() > 0) {
std::chrono::milliseconds avg_particle_lifetime(0);
std::chrono::milliseconds max_lifetime(0);
for (const Particle& particle : particles) {
auto lifetime = std::chrono::duration_cast<std::chrono::milliseconds>(particle.death_point - particle.birth_point);
avg_particle_lifetime += lifetime;
if (lifetime > max_lifetime) {
max_lifetime = lifetime;
}
}
avg_particle_lifetime /= particles.size();
//std::cout << "Particle Count: " << particles.size() << " - Avg. lifetime: " << avg_particle_lifetime.count() << "ms - Max lifetime: " << max_lifetime.count() << "ms" << std::endl;
}
} }
void simpleengine::particle::ParticleEmitter::Render(sf::RenderTarget *target) { void simpleengine::particle::ParticleEmitter::Render(sf::RenderTarget *target) {

View File

@ -1,26 +1,58 @@
#include "particle/particle.h" #include "particle/particle.h"
#include "particle/particle_property.h"
simpleengine::particle::Particle::Particle(sf::Texture& texture, sf::Vector2f velocity, uint32_t lifetime_ms, float rotation_velocity, #include <chrono>
ParticlePropertyVector properties) : Super(nullptr), velocity(velocity), lifetime_ms(lifetime_ms), #include <iostream>
rotation_velocity(rotation_velocity), properties(properties) {
age_clock.restart(); // Start age clock
sprite.setTexture(texture);
// Trigger OnParticleSpawn for particle properities. simpleengine::particle::Particle::Particle(const simpleengine::particle::Particle& other) {
for (ParticlePropertyPtr property : properties) { // From simpleengine::Destructable
property->OnParticleSpawn(*this); this->destroying = other.destroying;
this->sprite = other.sprite;
this->velocity = other.velocity;
this->rotation_velocity = other.rotation_velocity;
// Copy the properties from the other particle. Make sure to empty properties.
this->properties.clear();
for(const ParticlePropertyPtr& property : other.properties) {
std::unique_ptr<ParticleProperty> ptr = property->Clone();
this->properties.push_back(std::move(ptr));
} }
birth_point = other.birth_point;
death_point = other.death_point;
}
simpleengine::particle::Particle& simpleengine::particle::Particle::operator=(Particle other) {
// From simpleengine::Destructable
this->destroying = other.destroying;
this->sprite = other.sprite;
this->velocity = other.velocity;
this->rotation_velocity = other.rotation_velocity;
// Copy the properties from the other particle. Make sure to empty properties.
this->properties.clear();
for(const ParticlePropertyPtr& property : other.properties) {
std::unique_ptr<ParticleProperty> ptr = property->Clone();
this->properties.push_back(std::move(ptr));
}
birth_point = other.birth_point;
death_point = other.death_point;
return *this;
} }
void simpleengine::particle::Particle::Update(const float& delta_time) { void simpleengine::particle::Particle::Update(const float& delta_time) {
// If the particle is older than its lifetime, destroy it. // If death_point has passed then destroy the particle.
if (age_clock.getElapsedTime().asMilliseconds() >= lifetime_ms) { if (std::chrono::high_resolution_clock::now().time_since_epoch() > death_point.time_since_epoch()) {
Super::Destroy(); Super::Destroy();
return; return;
} }
// Update all properties. // Update all properties.
for (ParticlePropertyPtr property : properties) { for (ParticlePropertyPtr& property : properties) {
property->Update(*this); property->Update(*this);
} }
@ -31,10 +63,10 @@ void simpleengine::particle::Particle::Render(sf::RenderTarget* target) {
target->draw(sprite); target->draw(sprite);
} }
sf::Sprite& simpleengine::particle::Particle::GetSprite() { std::chrono::microseconds simpleengine::particle::Particle::GetAge() const {
return sprite; return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - birth_point);
} }
const sf::Time simpleengine::particle::Particle::GetAge() const { std::chrono::duration<long long, std::ratio<1, 1000000000>> simpleengine::particle::Particle::GetLifetime() const {
return age_clock.getElapsedTime(); return death_point - birth_point;
} }