From bde19814a56829c49492d6dee2881c747eca46b8 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Mon, 29 Mar 2021 19:52:27 -0500 Subject: [PATCH] Switch particle lifetimes to use std::chrono instead of sf::Clock Also create emitter properties, fix some code and some other things. --- examples/particles/src/main.cpp | 22 ++++-- include/simpleengine/event.h | 1 - include/simpleengine/particle/emitter.h | 16 +++-- include/simpleengine/particle/particle.h | 37 ++++++++-- .../particle/particle_emitter_property.h | 24 +++++++ .../simpleengine/particle/particle_property.h | 4 +- .../properties/random_lifetime_property.h | 32 +++++++-- .../properties/random_velocity_property.h | 19 ++++-- .../movement/movement_component.cpp | 2 - src/particle/emitter.cpp | 67 ++++++++++++++----- src/particle/particle.cpp | 62 ++++++++++++----- 11 files changed, 221 insertions(+), 65 deletions(-) create mode 100644 include/simpleengine/particle/particle_emitter_property.h diff --git a/examples/particles/src/main.cpp b/examples/particles/src/main.cpp index df4055d..a47c80c 100644 --- a/examples/particles/src/main.cpp +++ b/examples/particles/src/main.cpp @@ -4,10 +4,15 @@ // Email: seanomik@gmail.com // +#include "simpleengine/random.h" +#include +#include #include #include +#include #include +#include #include #include #include @@ -16,6 +21,7 @@ // Particle #include #include +#include #include #include @@ -25,6 +31,9 @@ #include #include +#define _USE_MATH_DEFINES +#include + namespace se = simpleengine; class ParticleEmitter : public se::particle::ParticleEmitter { @@ -34,14 +43,17 @@ private: sf::Sprite sprite; sf::Texture texture; - Super::ParticlePropertyVector properties = { - std::make_shared(se::Range2f(-1.5f, 1.5f, -1.5f, 1.5f)), - std::make_shared(se::Rangef(1'500, 9'500)) - }; + Super::ParticlePropertyVector particle_properties; + Super::EmitterPropertyVector emitter_properties; 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.setSmooth(true); + + particle_properties.emplace_back(std::make_unique(se::Range2f(-1.5f, 1.5f, -1.5f, 1.5f))); + + using namespace std::chrono_literals; + particle_properties.emplace_back(std::make_unique(2'500ms, 5'500ms)); } void Update(const float& delta_time) override { diff --git a/include/simpleengine/event.h b/include/simpleengine/event.h index 590d1fe..a86ea11 100644 --- a/include/simpleengine/event.h +++ b/include/simpleengine/event.h @@ -21,7 +21,6 @@ namespace simpleengine { virtual void Render(sf::RenderTarget* target) = 0; protected: sf::RenderWindow* window; - bool quit = false; }; } diff --git a/include/simpleengine/particle/emitter.h b/include/simpleengine/particle/emitter.h index d6a3265..534ffdd 100644 --- a/include/simpleengine/particle/emitter.h +++ b/include/simpleengine/particle/emitter.h @@ -9,6 +9,7 @@ #include "../event.h" #include "particle_property.h" +#include "particle_emitter_property.h" #include "particle.h" #include "../random.h" #include "../range_2.h" @@ -19,7 +20,6 @@ #include #include -#include namespace simpleengine { namespace particle { @@ -32,12 +32,15 @@ namespace simpleengine { class ParticleEmitter : public simpleengine::Event { protected: - using ParticlePropertyPtr = std::shared_ptr; + using ParticlePropertyPtr = std::unique_ptr; using ParticlePropertyVector = std::vector; - 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; + using EmitterPropertyVector = std::vector; + 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 Render(sf::RenderTarget* target) override; @@ -49,7 +52,8 @@ namespace simpleengine { uint32_t particle_count; Range2f particle_range; ParticleAttributes attributes; - ParticlePropertyVector& properties; + ParticlePropertyVector& particle_properties; + EmitterPropertyVector& emitter_properties; std::vector particles; sf::Clock time; diff --git a/include/simpleengine/particle/particle.h b/include/simpleengine/particle/particle.h index 112abc9..e8358e0 100644 --- a/include/simpleengine/particle/particle.h +++ b/include/simpleengine/particle/particle.h @@ -15,6 +15,7 @@ #include #include +#include #include namespace simpleengine { @@ -23,25 +24,47 @@ namespace simpleengine { private: using Super = simpleengine::Event; protected: - using ParticlePropertyPtr = std::shared_ptr; + using ParticlePropertyPtr = std::unique_ptr; using ParticlePropertyVector = std::vector; public: - Particle(sf::Texture& texture, sf::Vector2f velocity, uint32_t lifetime_ms, float rotation_velocity, ParticlePropertyVector properties); + friend class ParticleEmitter; + + template + Particle(sf::Texture& texture, sf::Vector2f velocity, std::chrono::duration 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 Render(sf::RenderTarget* target) override; - sf::Sprite& GetSprite(); - const sf::Time GetAge() const; + template + void SetLifetime(std::chrono::duration lifetime) { + death_point = std::chrono::high_resolution_clock::now() + lifetime; + } + + std::chrono::microseconds GetAge() const; + std::chrono::duration> GetLifetime() const; sf::Sprite sprite; sf::Vector2f velocity; - uint32_t lifetime_ms; float rotation_velocity; ParticlePropertyVector properties; - private: - sf::Clock age_clock; + std::chrono::time_point birth_point; + std::chrono::time_point death_point; }; } } diff --git a/include/simpleengine/particle/particle_emitter_property.h b/include/simpleengine/particle/particle_emitter_property.h new file mode 100644 index 0000000..e5925d3 --- /dev/null +++ b/include/simpleengine/particle/particle_emitter_property.h @@ -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 diff --git a/include/simpleengine/particle/particle_property.h b/include/simpleengine/particle/particle_property.h index 20a6874..d927091 100644 --- a/include/simpleengine/particle/particle_property.h +++ b/include/simpleengine/particle/particle_property.h @@ -8,14 +8,14 @@ #define SIMPLEENGINE_PARTICLE_PROPERTY_H #include "../event.h" - -#include +#include namespace simpleengine { namespace particle { class Particle; class ParticleProperty { public: + virtual std::unique_ptr Clone() const = 0; virtual void OnParticleSpawn(simpleengine::particle::Particle& particle) = 0; virtual void Update(simpleengine::particle::Particle& particle) = 0; }; diff --git a/include/simpleengine/particle/properties/random_lifetime_property.h b/include/simpleengine/particle/properties/random_lifetime_property.h index b30fa15..cd2a256 100644 --- a/include/simpleengine/particle/properties/random_lifetime_property.h +++ b/include/simpleengine/particle/properties/random_lifetime_property.h @@ -12,28 +12,50 @@ #include "../../random.h" #include "../particle.h" +#include +#include +#include +#include + namespace simpleengine { namespace particle { class RandomLifetimeParticleProperty : public ParticleProperty { public: - RandomLifetimeParticleProperty(simpleengine::Rangef range) : range(range) { + template + RandomLifetimeParticleProperty(std::chrono::duration min, std::chrono::duration max) + : range(Range( + std::chrono::duration_cast(min), + std::chrono::duration_cast(max))) { } - RandomLifetimeParticleProperty(float min, float max) : RandomLifetimeParticleProperty(Rangef(min, max)) { + template + RandomLifetimeParticleProperty(const simpleengine::Range>& range) : RandomLifetimeParticleProperty(range.min, range.max) { } + RandomLifetimeParticleProperty(const RandomLifetimeParticleProperty& other) { + this->range = other.range; + } + + std::unique_ptr Clone() const override { + return std::make_unique(range); + } + void OnParticleSpawn(simpleengine::particle::Particle& particle) override { - simpleengine::Random rand; - particle.lifetime_ms = rand.NextInRange(range.min, range.max); + simpleengine::Random rand; + + 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 { } private: - simpleengine::Rangef range; + simpleengine::Range range; }; } } diff --git a/include/simpleengine/particle/properties/random_velocity_property.h b/include/simpleengine/particle/properties/random_velocity_property.h index c079580..5808145 100644 --- a/include/simpleengine/particle/properties/random_velocity_property.h +++ b/include/simpleengine/particle/properties/random_velocity_property.h @@ -12,11 +12,14 @@ #include "../../random.h" #include "../particle.h" +#include +#include + namespace simpleengine { namespace particle { class RandomVelocityParticleProperty : public ParticleProperty { 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 Clone() const override { + return std::make_unique(range); + } + void OnParticleSpawn(simpleengine::particle::Particle& particle) override { - simpleengine::Random rand; + simpleengine::Random rand; sf::Vector2f velocity; - velocity.x = rand.NextInRange(range.min_x, range.max_x); - velocity.y = rand.NextInRange(range.min_y, range.max_y); + velocity.x = rand.NextInRange>(range.min_x, range.max_x); + velocity.y = rand.NextInRange>(range.min_y, range.max_y); particle.velocity = velocity; } diff --git a/src/components/movement/movement_component.cpp b/src/components/movement/movement_component.cpp index ef5e684..6807a00 100644 --- a/src/components/movement/movement_component.cpp +++ b/src/components/movement/movement_component.cpp @@ -11,8 +11,6 @@ #include #include -#include - 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) { diff --git a/src/particle/emitter.cpp b/src/particle/emitter.cpp index 3f3f545..6bdd7fd 100644 --- a/src/particle/emitter.cpp +++ b/src/particle/emitter.cpp @@ -1,44 +1,75 @@ #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 +#include + +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(); } -void simpleengine::particle::ParticleEmitter::SetProperties(ParticlePropertyVector& properties) { - this->properties = properties; +void simpleengine::particle::ParticleEmitter::SetProperties(ParticlePropertyVector particle_properties) { + this->particle_properties = std::move(particle_properties); } void simpleengine::particle::ParticleEmitter::Update(const float &delta_time) { - // Update particles and check if they are being destroyed. If they are, destroy them. - for (std::vector::iterator it = particles.begin(); it != particles.end(); ) { - it->Update(delta_time); + // Update each particle and remove it from the vector if it is ready to be destroyed. + particles.erase(std::remove_if(particles.begin(), particles.end(), + [&delta_time](simpleengine::particle::Particle& particle){ + particle.Update(delta_time); - if (it->IsDestroying()) { - it = particles.erase(it); - } else { - it++; - } - } + return particle.IsDestroying(); + }), particles.end()); // 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 - 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++) { - Particle particle(texture, attributes.start_velocity, attributes.lifetime_ms, attributes.rotation_velocity, properties); + //uint32_t emitting_ct = (particles.size() + emit_count > particle_count) ? particle_count - particles.size() : emit_count; + for (uint32_t i = 0; i < emit_count; i++) { + + 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 sf::Vector2f new_pos; 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 += 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)); } } + + 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(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) { diff --git a/src/particle/particle.cpp b/src/particle/particle.cpp index 23383a0..c312522 100644 --- a/src/particle/particle.cpp +++ b/src/particle/particle.cpp @@ -1,26 +1,58 @@ #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, - ParticlePropertyVector properties) : Super(nullptr), velocity(velocity), lifetime_ms(lifetime_ms), - rotation_velocity(rotation_velocity), properties(properties) { - age_clock.restart(); // Start age clock - sprite.setTexture(texture); +#include +#include - // Trigger OnParticleSpawn for particle properities. - for (ParticlePropertyPtr property : properties) { - property->OnParticleSpawn(*this); +simpleengine::particle::Particle::Particle(const simpleengine::particle::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 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 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) { - // If the particle is older than its lifetime, destroy it. - if (age_clock.getElapsedTime().asMilliseconds() >= lifetime_ms) { + // If death_point has passed then destroy the particle. + if (std::chrono::high_resolution_clock::now().time_since_epoch() > death_point.time_since_epoch()) { Super::Destroy(); return; } // Update all properties. - for (ParticlePropertyPtr property : properties) { + for (ParticlePropertyPtr& property : properties) { property->Update(*this); } @@ -31,10 +63,10 @@ void simpleengine::particle::Particle::Render(sf::RenderTarget* target) { target->draw(sprite); } -sf::Sprite& simpleengine::particle::Particle::GetSprite() { - return sprite; +std::chrono::microseconds simpleengine::particle::Particle::GetAge() const { + return std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - birth_point); } -const sf::Time simpleengine::particle::Particle::GetAge() const { - return age_clock.getElapsedTime(); +std::chrono::duration> simpleengine::particle::Particle::GetLifetime() const { + return death_point - birth_point; } \ No newline at end of file