2D Game Engine

Project Stats:

Name: IGArt
Project type: Student project
Production Time: 8 weeks
Date: 2018/03-2018/05
Engine/language: C++
Platforms: PC
Tools: Visual Studio, Perforce, Jira
Team members: 9 programming students

Project Description

A 2d platformer game engine inspired by the UbiArt framework.

My contributions

Particle system

I created a particle system inspired by the particle system in Unity.

Particle system curve editor

To change the parameters of particles over their lifetime, I utilized a curve editor in Imgui. Getting the curve editor to function in Imgui was challenging. I tried a few versions of the open-source LumixEngine’s curve editor. I had to settle for an old broken version and fix that because that was most compatible with the version of Imgui we were using. We could not change the version of Imgui because the behavior editor was dependent on that and we had already put a lot of work in that editor.

Random between two curves

In order to enable the editing of a random value between two curves in the editor, I implemented a double curve editor.

Code

Code snippets - particle_system_component.h

#pragma once /** * @file sprite_component.h * @author Kit van de Bunt * @date 27 mar 2018 * @brief iga::SpriteComponent class header. * * This file contains a class that is used in entities with sprites. * @see iga::Renderer * @see iga::RenderListener * @see iga::BaseRenderingComponent */ #include <Graphics\base_rendering_component.h> #include <Graphics\base_group_rendering_component.h> #include <vectormath\vectormath.hpp> #include <memory> #include <defines.h> #include <cereal/cereal.hpp> #include <cereal/types/base_class.hpp> #include <cereal/types/polymorphic.hpp> #include <cereal/cereal.hpp> #include <cereal/types/memory.hpp> /** * ImGui namespace. */ namespace ImGui { class IgaCurveEditor; class IgaCurveEditorDouble; class IgaCurveEditor3; class IgaCurveEditor3Double; } /** * IGArt namespace. */ namespace iga { /** * iga::Particle. Holds per particle variables. */ struct Particle { bool alive_; /**< true if particle is alive */ float life_time_; /**< time left alive */ float total_live_time_; /**< lifetime given to particle at spawn */ Vector3 velocity_;/** particles velocity */ Vector3 angular_velocity_;/** particles velocity */ Vector3 position_;/** particle position */ Vector3 rotation_;/** particles rotation */ Vector3 scale_;/** particle scale */ Vector4 color_;/** particle color */ Vector3 random_velocity_between_curves_; /** lerp value for velocity random between two curves */ Vector3 random_angular_velocity_between_curves_; /** lerp value for angular velocity random between two curves */ }; class Resource; /** * iga::ParticleSystemComponent. This is a component used by entities that are represented by a particleSystem. * Inherits iga::BaseGroupRenderingComponent. * @see iga::Component * @see iga::RenderListener * @see iga::BaseGroupRenderingComponent */ class ParticleSystemComponent final : public BaseGroupRenderingComponent { public: /** * Cereal versions of iga::ParticleSystemComponent */ enum class Version { DEFAULT = 0, /**< Default normal version. */ FIRST, /**< Default normal version. */ TEXTURE,/**< Texture added. */ ROTATION,/**< Rotation added. */ COLOR,/**< Color added. */ COLOR2,/**< Color added version 2. */ MINIMUM_VELOCITY,/**< Minimum velocity added. */ LATEST /**< Latest version. */ }; /** * Constructor. */ ParticleSystemComponent(); /** * Custom constructor with iga::entity that will own the component. * @param a_entity is a weak pointer to an iga::Entity that is to be the owner of this component. */ ParticleSystemComponent(std::weak_ptr<Entity> a_entity); /** * Destructor. */ virtual ~ParticleSystemComponent(); /** * Gets called when it gets created. Is used as an initialization function. */ virtual void OnCreate(bool a_on_load = false) override; /** * Updates the component. */ virtual void Update() override; /** * Gets called after the update has happened. */ virtual void PostUpdate() override; /** * Function to load a texture for this particle system from a path. * @param a_path A const std::string reference that contains the path to the texture. */ void SetTexture(const std::string& a_path); /** * Virtual function that gets render data when it wants to build render data. */ virtual std::vector<RenderDataStruct>* OnBuildRenderData() override; /** * Used to show information in the editor : Inspector */ virtual void Inspect() override; /** * Used to save and load iga::ParticleSystemComponent */ template<class Archive> void serialize(Archive & a_archive, uint32 const a_version); ModifierStruct modifiers_; /**< Modifier struct to modify the sprite with. @see iga::ModifierStruct */ protected: private: std::shared_ptr<Resource> texture_resource_; /**< The texture resource of this sprite. @see iga::Resource @see iga::Texture */ std::shared_ptr<Resource> mesh_resource_; /**< The mesh resource of a sprite. @see iga::Resource @see iga::Texture */ std::vector<Particle> particle_vector_; //** vector that cointains all particles in the particle system (pooled) */ std::vector<RenderDataStruct> render_data_struct_vector_;/** a render data struct for each particle (pooled) */ void ParticleSystemComponent::GrowPoolCount();/** Grows particle pool by one*/ void ParticleSystemComponent::SpawnParticle();/** Spawns one partilce*/ void ParticleSystemComponent::Burst();/** Spawns a burst of particles*/ void ParticleSystemComponent::StartCollapsingHeaderState(); void ParticleSystemComponent::EndCollapsingHeaderState(); /** * Random float between -.5 and .5 * * @param a_from_0_to_1 if true function returns a number form 0 to 1 */ float RandomFloat(bool a_from_0_to_1 = false); // Per particle system editable variables(for imgui) int max_particles_; /**< Max particles in the particle system*/ int max_spawn_per_frame_; /**< Max particles added per frame*/ bool loop_;/**< If true particle system is looping */ bool burst_;/**< If particle spawns bursts of particles */ bool burst_once_;/**< True if burst happens once */ float burst_time_;/**< Time between bursts */ int burst_count_;/**< Amount of spawns per burst */ float spawn_time_; /**< When loop true time between each particle spawn */ float particle_life_time_;/**< Particle life time on particle spawn */ float particle_life_time_random_;/**< Value multiplied by a random number between -1 and 1 and added to each particles life time on spawn */ Vector3 spawn_position_;/**< Particles pawn offset from the owners transform */ Vector3 spawn_rotation_;/**<Particle spawn rotation */ Vector3 random_spawn_rotion_;/**<Random particle spawn rotation offset*/ float minimum_velocity_magnitude_ = 0;/**< minimum added velocity per frame (only works when the magnitude of the velocity is more then 0)*/ int velocityMode; /**< 0 = constant, 1 = velocity over lifetime */ Vector3 spawn_velocity_;/**< Particles velocity on spawn */ Vector3 spawn_velocity_random_;/**< Particles velocity on spawn */ std::shared_ptr<ImGui::IgaCurveEditor3> velocity_over_life_; /**< Size over lifetime curves */ std::shared_ptr<ImGui::IgaCurveEditor3Double> velocity_over_life_between_curves; /**< Size over lifetime double curves for velocity over lifetime random between 2 curves*/ int angular_velocity_mode_; /**< 0 = constant, 1 = angular velocity over lifetime */ Vector3 spawn_angular_velocity_;/**< Particles velocity on spawn */ Vector3 spawn_angular_velocity_random_;/**< Particles angular velocity on spawn */ std::shared_ptr<ImGui::IgaCurveEditor3> angular_velocity_over_life_; /**< Size over lifetime curves */ std::shared_ptr<ImGui::IgaCurveEditor3Double> angular_velocity_over_life_between_curves; /**< Size over lifetime double curves for velocity over lifetime random between 2 curves*/ Vector3 spawn_range_;/**< Area in wish the particles spawn */ int scaleMode; /**< 0 = constant, 1 = size over lifetime */ Vector3 start_scale_; /** Particles scale on spawn*/ Vector3 start_scale_random_;/** Value multiplied by a random number between -1 and 1 and added to each particles scale on spawn */ std::shared_ptr<ImGui::IgaCurveEditor> size_over_life_; /**< Size over lifetime curves */ Vector4 start_color_; /**<Particle spawn color */ Vector4 start_color_random_offset_; /**<Particle spawn color random offset*/ std::shared_ptr<ImGui::IgaCurveEditor> color_over_life_r_; /** Red color over lifetime curve*/ std::shared_ptr<ImGui::IgaCurveEditor> color_over_life_g_; /** Green color over lifetime curve*/ std::shared_ptr<ImGui::IgaCurveEditor> color_over_life_b_; /** Blue color over lifetime curve*/ std::shared_ptr<ImGui::IgaCurveEditor> color_over_life_a_; /** Alpha color over lifetime curve*/ // particle system internal variables (not for imgui) float spawn_timer_;/**< Subtract delta time every frame if < 0 particle spawns */ float burst_timer_;/**< Subtract delta time every frame if < 0 burst hapens */ bool burst_executed_;/**< if burst_once_ = true this gets sets to tue after one burst */ int color_over_lifetime_ui_state_;/**< stores the state off the color over life time ui */ int collapsing_header_state_ = 0;/**< The collapsing header that is open */ int collapsing_header_state_last_frame_ = -2;/**< Used to open collapsed headers with a frame delay to prefent 2 form being drawn in one frame */ bool collapsing_header_open_;/**< Internal usage stores if current processed collapsing header is open*/ int collapsing_header_id_;/**< Internal usage stores the id of the collapsing header that is being processed*/ bool if_one_collapsing_header_open_; /**< Internal usage If one collepsing header is open set to true*/ bool following_other = false; bool follow_object_alive = true; float life_time_after_follow_object_destroyed = 1.0f; Vector3 last_follow_position; #ifdef EDITOR char texture_path_buffer_[256] = " "; /**< Editor only variable. A char buffer for the texture path text box. */ #endif }; template<class Archive> inline void ParticleSystemComponent::serialize(Archive &a_archive, uint32 const a_version) { switch(static_cast<Version>(a_version)) { case Version::LATEST: case Version::MINIMUM_VELOCITY: a_archive(cereal::make_nvp("minimum_velocity_magnitude_", minimum_velocity_magnitude_)); case Version::COLOR2: a_archive(cereal::make_nvp("start_color_", start_color_)); a_archive(cereal::make_nvp("start_color_random_offset_", start_color_random_offset_)); a_archive(cereal::make_nvp("color_over_life_r_", color_over_life_r_)); a_archive(cereal::make_nvp("color_over_life_g_", color_over_life_g_)); a_archive(cereal::make_nvp("color_over_life_b_", color_over_life_b_)); a_archive(cereal::make_nvp("color_over_life_a_", color_over_life_a_)); case Version::COLOR: case Version::ROTATION: a_archive(cereal::make_nvp("spawn_rotation_", spawn_rotation_)); a_archive(cereal::make_nvp("random_spawn_rotion_", random_spawn_rotion_)); a_archive(cereal::make_nvp("angular_velocity_mode_", angular_velocity_mode_)); a_archive(cereal::make_nvp("spawn_angular_velocity_", spawn_angular_velocity_)); a_archive(cereal::make_nvp("spawn_angular_velocity_random_", spawn_angular_velocity_random_)); a_archive(cereal::make_nvp("angular_velocity_over_life_", angular_velocity_over_life_)); a_archive(cereal::make_nvp("angular_velocity_over_life_between_curves", angular_velocity_over_life_between_curves)); case Version::TEXTURE: a_archive(cereal::make_nvp("texture_resource", texture_resource_)); case Version::FIRST: a_archive(cereal::make_nvp("max_particles_", max_particles_)); a_archive(cereal::make_nvp("max_spawn_per_frame_", max_spawn_per_frame_)); a_archive(cereal::make_nvp("loop_", loop_)); a_archive(cereal::make_nvp("burst_", burst_)); a_archive(cereal::make_nvp("burst_once_", burst_once_)); a_archive(cereal::make_nvp("burst_time_", burst_time_)); a_archive(cereal::make_nvp("burst_count_", burst_count_)); a_archive(cereal::make_nvp("spawn_time_", spawn_time_)); a_archive(cereal::make_nvp("particle_life_time_", particle_life_time_)); a_archive(cereal::make_nvp("particle_life_time_random_", particle_life_time_random_)); a_archive(cereal::make_nvp("spawn_position_", spawn_position_)); a_archive(cereal::make_nvp("velocityMode", velocityMode)); a_archive(cereal::make_nvp("spawn_velocity_", spawn_velocity_)); a_archive(cereal::make_nvp("spawn_velocity_random_", spawn_velocity_random_)); a_archive(cereal::make_nvp("velocity_over_life_", velocity_over_life_)); a_archive(cereal::make_nvp("velocity_over_life_between_curves", velocity_over_life_between_curves)); a_archive(cereal::make_nvp("spawn_range_", spawn_range_)); a_archive(cereal::make_nvp("scaleMode", scaleMode)); a_archive(cereal::make_nvp("start_scale_", start_scale_)); a_archive(cereal::make_nvp("start_scale_random_", start_scale_random_)); a_archive(cereal::make_nvp("size_over_life_", size_over_life_)); a_archive(cereal::make_nvp("spawn_timer_", spawn_timer_)); a_archive(cereal::make_nvp("burst_timer_", burst_timer_)); a_archive(cereal::make_nvp("burst_executed_", burst_executed_)); case Version::DEFAULT: a_archive(cereal::base_class<BaseGroupRenderingComponent>(this)); break; } } SUBSCRIBECOMPONENT(ParticleSystemComponent) } CEREAL_CLASS_VERSION(iga::ParticleSystemComponent, (static_cast<iga::uint32>(iga::ParticleSystemComponent::Version::LATEST) - 1));

Code snippets - particle_system_component.cpp

#include <ParticleSystem/particle_system_component.h> #include <ig_art_engine.h> #ifdef EDITOR #include <imgui/include/imgui.h> #include <Utility/util_editor.h> #endif #include <imgui_user.h> #include <stdlib.h> /* srand, rand */ #include <time.h> /* time */ #include <cereal/types/base_class.hpp> #include <cereal/types/polymorphic.hpp> #include <cereal/archives/json.hpp> #include <cereal/archives/binary.hpp> CEREAL_REGISTER_TYPE(iga::ParticleSystemComponent); CEREAL_REGISTER_POLYMORPHIC_RELATION(iga::BaseGroupRenderingComponent, iga::ParticleSystemComponent); namespace iga { ParticleSystemComponent::ParticleSystemComponent() {} ParticleSystemComponent::ParticleSystemComponent(std::weak_ptr<Entity> a_entity) : BaseGroupRenderingComponent(a_entity) { component_type_ = "ParticleSystemComponent"; // called on new component Vector2 default_curve_values_[4]; default_curve_values_[0].setX(0); default_curve_values_[0].setY(0); default_curve_values_[1].setX(.1f); default_curve_values_[1].setY(.0f); default_curve_values_[2].setX(.9f); default_curve_values_[2].setY(.0f); default_curve_values_[3].setX(1.0f); default_curve_values_[3].setY(.0f); // velocity velocity_over_life_ = std::make_shared<ImGui::IgaCurveEditor3>(default_curve_values_); velocity_over_life_between_curves = std::make_shared<ImGui::IgaCurveEditor3Double>(default_curve_values_); velocityMode = 0; angular_velocity_mode_ = 0; spawn_velocity_ = Vector3(0, .02f, 0); spawn_velocity_random_ = Vector3(0, .005f, 0); // angular velocity angular_velocity_over_life_ = std::make_shared<ImGui::IgaCurveEditor3>(default_curve_values_); angular_velocity_over_life_between_curves = std::make_shared<ImGui::IgaCurveEditor3Double>(default_curve_values_); spawn_angular_velocity_ = Vector3(0.f); spawn_angular_velocity_random_ = Vector3(0.f); size_over_life_ = std::make_shared<ImGui::IgaCurveEditor>(); // particle spawn max_particles_ = 60; loop_ = false; burst_executed_ = false; burst_ = true; burst_once_ = false; burst_time_ = 1.5f; burst_count_ = 10; spawn_time_ = 0.05f; spawn_timer_ = spawn_time_; max_spawn_per_frame_ = 100; spawn_position_ = Vector3(0, 0, 0); particle_life_time_ = 4.0f; particle_life_time_random_ = .5f; spawn_range_.setX(.5f); spawn_range_.setY(.5f); spawn_range_.setZ(.5f); start_scale_ = Vector3(0.05f); start_scale_random_ = Vector3(0.02f); scaleMode = 1; default_curve_values_[0].setX(0); default_curve_values_[0].setY(1); default_curve_values_[1].setX(0.1f); default_curve_values_[1].setY(1); default_curve_values_[2].setX(.9f); default_curve_values_[2].setY(1); default_curve_values_[3].setX(1); default_curve_values_[3].setY(1); color_over_life_r_ = std::make_shared<ImGui::IgaCurveEditor>(default_curve_values_); color_over_life_g_ = std::make_shared<ImGui::IgaCurveEditor>(default_curve_values_); color_over_life_b_ = std::make_shared<ImGui::IgaCurveEditor>(default_curve_values_); color_over_life_a_ = std::make_shared<ImGui::IgaCurveEditor>(default_curve_values_); start_color_ = Vector4(255.0f); start_color_random_offset_ = Vector4(0.0f); spawn_rotation_ = Vector3(0.0f); random_spawn_rotion_ = Vector3(0.0f); } ParticleSystemComponent::~ParticleSystemComponent() { //delete size_over_life_; //delete velocity_over_life_; //delete velocity_over_life_between_curves; } void ParticleSystemComponent::OnCreate(bool a_on_load) { color_over_lifetime_ui_state_ = 0; Vector2 velocityCurveValues[4]; velocityCurveValues[0].setX(0); velocityCurveValues[0].setY(0); velocityCurveValues[1].setX(.1f); velocityCurveValues[1].setY(.0f); velocityCurveValues[2].setX(.9f); velocityCurveValues[2].setY(.0f); velocityCurveValues[3].setX(1.0f); velocityCurveValues[3].setY(.0f); if(!angular_velocity_over_life_){ angular_velocity_over_life_ = std::make_shared<ImGui::IgaCurveEditor3>(velocityCurveValues); } if (!angular_velocity_over_life_between_curves) { angular_velocity_over_life_between_curves = std::make_shared<ImGui::IgaCurveEditor3Double>(velocityCurveValues); spawn_angular_velocity_ = Vector3(0.f); spawn_angular_velocity_random_ = Vector3(0.f); } if (color_over_life_r_ == nullptr) { color_over_life_r_ = std::make_shared<ImGui::IgaCurveEditor>(velocityCurveValues); }; if (color_over_life_g_ == nullptr) { color_over_life_g_ = std::make_shared<ImGui::IgaCurveEditor>(velocityCurveValues); }; if (color_over_life_b_ == nullptr) { color_over_life_b_ = std::make_shared<ImGui::IgaCurveEditor>(velocityCurveValues); }; if (color_over_life_a_ == nullptr) { color_over_life_a_ = std::make_shared<ImGui::IgaCurveEditor>(velocityCurveValues); }; if (a_on_load && texture_resource_) { SetTexture(texture_resource_->GetPath()); } else { SetTexture("Default"); } mesh_resource_ = GetResourceManager()->Load<Mesh>("Sprite"); render_data_struct_vector_.reserve(max_particles_); particle_vector_.reserve(max_particles_); // Build render data for (size_t i = 0; i < max_particles_; i++) { GrowPoolCount(); } particle_vector_[0].position_ += Vector3(0, -1, 0); } void ParticleSystemComponent::GrowPoolCount() { particle_vector_.push_back(Particle()); render_data_struct_vector_.push_back(RenderDataStruct()); int newIndex = render_data_struct_vector_.size() - 1; render_data_struct_vector_[newIndex].mesh_ = GetResourceManager()->Get<Mesh>(mesh_resource_); render_data_struct_vector_[newIndex].texture_ = GetResourceManager()->Get<Texture>(texture_resource_); render_data_struct_vector_[newIndex].model_matrix_ = Matrix4::identity(); render_data_struct_vector_[newIndex].modifier_ = modifiers_; } void ParticleSystemComponent::Update() { if (following_other && !follow_object_alive) { loop_ = false; burst_time_ = 99999999.0f; } // spawn particle float delta_time_ = GetTime()->GetGameDeltaTime(); if (loop_) { spawn_timer_ -= delta_time_; for (size_t i = 0; i < max_spawn_per_frame_; i++) { if (spawn_timer_ < 0.0f) { SpawnParticle(); spawn_timer_ += spawn_time_; } } } // burst particle if (burst_) { if (burst_once_ && !burst_executed_) { Burst(); burst_executed_ = true; } if (!burst_once_) { burst_timer_ -= delta_time_; if (burst_timer_ < 0.0f) { burst_timer_ = burst_time_; Burst(); } } } // update particles for (size_t particle_index_ = 0; particle_index_ < particle_vector_.size(); particle_index_++) { if (particle_vector_[particle_index_].alive_) { particle_vector_[particle_index_].life_time_ -= delta_time_; float deltatimeScaled = delta_time_ * 60; if (particle_vector_[particle_index_].life_time_ < 0.0f) { particle_vector_[particle_index_].alive_ = false; } float lifeScale = 1.0f - (particle_vector_[particle_index_].life_time_ / particle_vector_[particle_index_].total_live_time_); if (scaleMode == 1) { particle_vector_[particle_index_].scale_ = start_scale_ + Vector3(size_over_life_->GetPoint(lifeScale).getY()); } Vector3 velocity = Vector3(0.0f); Vector3 minVelocity = Vector3(0.0f); if (velocityMode == 1) { velocity = Vector3(velocity_over_life_->GetPoint(lifeScale)) * deltatimeScaled; } else if (velocityMode == 2) { velocity = velocity_over_life_between_curves->GetPoint(lifeScale, particle_vector_[particle_index_].random_velocity_between_curves_) * deltatimeScaled; } else { velocity = particle_vector_[particle_index_].velocity_ * deltatimeScaled; } if (lengthSqr(velocity) != 0.0f) { minVelocity = normalize(velocity) * minimum_velocity_magnitude_; } particle_vector_[particle_index_].position_ += velocity + minVelocity; if (angular_velocity_mode_ == 1) { particle_vector_[particle_index_].angular_velocity_ = Vector3(angular_velocity_over_life_->GetPoint(lifeScale)); } else if (angular_velocity_mode_ == 2) { particle_vector_[particle_index_].angular_velocity_ = angular_velocity_over_life_between_curves->GetPoint(lifeScale, particle_vector_[particle_index_].random_angular_velocity_between_curves_); } particle_vector_[particle_index_].rotation_ += particle_vector_[particle_index_].angular_velocity_ * deltatimeScaled; Vector4 colorOverLifeTime = Vector4( color_over_life_r_->GetPoint(lifeScale).getY(), color_over_life_g_->GetPoint(lifeScale).getY(), color_over_life_b_->GetPoint(lifeScale).getY(), color_over_life_a_->GetPoint(lifeScale).getY()); render_data_struct_vector_[particle_index_].modifier_.color_ = mulPerElem(particle_vector_[particle_index_].color_, colorOverLifeTime); } if (!particle_vector_[particle_index_].alive_) { particle_vector_[particle_index_].scale_ = Vector3(0); } render_data_struct_vector_[particle_index_].model_matrix_ = Matrix4::translation(particle_vector_[particle_index_].position_) * Matrix4::rotationX(particle_vector_[particle_index_].rotation_.getX()) * Matrix4::rotationY(particle_vector_[particle_index_].rotation_.getY()) * Matrix4::rotationZ(particle_vector_[particle_index_].rotation_.getZ()) * Matrix4::scale(particle_vector_[particle_index_].scale_); render_data_struct_vector_[particle_index_].model_matrix_ *= Matrix4::scale(owner_.lock()->GetTransform().lock()->GetScale()); } } void ParticleSystemComponent::SpawnParticle() { // check if can spawn int particlesAlive = 0; for (size_t particle_index_ = 0; particle_index_ < particle_vector_.size(); particle_index_++) { if (particle_vector_[particle_index_].alive_) { particlesAlive++; } } if (particlesAlive - 1 > max_particles_) { return; } // get spawn position Vector3 entity_spawn_position_; if (owner_.lock()->GetTransform().lock()->GetParent().lock()) { last_follow_position = owner_.lock()->GetTransform().lock()->GetParent().lock()->GetPosition(); following_other = true; follow_object_alive = true; entity_spawn_position_ = last_follow_position; } else { follow_object_alive = false; if (following_other) { entity_spawn_position_ = last_follow_position; } else if(owner_.lock()) { entity_spawn_position_ = owner_.lock()->GetTransform().lock()->GetPosition(); } } for (size_t particle_index_ = 0; particle_index_ < particle_vector_.size(); particle_index_++) { // spawn if (!particle_vector_[particle_index_].alive_) { // start spawning particle particle_vector_[particle_index_].alive_ = true; float randFloat = RandomFloat(); particle_vector_[particle_index_].life_time_ = particle_life_time_ + (particle_life_time_random_ * randFloat); particle_vector_[particle_index_].total_live_time_ = particle_vector_[particle_index_].life_time_; particle_vector_[particle_index_].velocity_ = spawn_velocity_ + (spawn_velocity_random_ * RandomFloat()); particle_vector_[particle_index_].angular_velocity_ = spawn_angular_velocity_ + (spawn_angular_velocity_random_* RandomFloat()); Vector3 rand_spawn_offset = mulPerElem(spawn_range_, Vector3(RandomFloat(), RandomFloat(), RandomFloat())); particle_vector_[particle_index_].position_ = entity_spawn_position_ + rand_spawn_offset + spawn_position_; particle_vector_[particle_index_].scale_ = start_scale_ + (start_scale_random_* RandomFloat()); particle_vector_[particle_index_].random_velocity_between_curves_ = Vector3(RandomFloat(true), RandomFloat(true), RandomFloat(true)); particle_vector_[particle_index_].random_angular_velocity_between_curves_ = Vector3(RandomFloat(true), RandomFloat(true), RandomFloat(true)); particle_vector_[particle_index_].rotation_ = spawn_rotation_ + (mulPerElem(random_spawn_rotion_,Vector3(RandomFloat(), RandomFloat(), RandomFloat()))); particle_vector_[particle_index_].color_ = start_color_ + (start_color_random_offset_ * RandomFloat(true)); return; }; } // no particle spawn because max particle count reached return; } void ParticleSystemComponent::Burst() { if (burst_count_ > max_spawn_per_frame_) { burst_count_ = max_spawn_per_frame_; } for (size_t i = 0; i < burst_count_; i++) { SpawnParticle(); } } float ParticleSystemComponent::RandomFloat(bool a_from_0_to_1) { if(!a_from_0_to_1)return (((float)(rand() % 1000)) / 1000.0f) - .5f; return (((float)(rand() % 1000)) / 1000.0f); } void ParticleSystemComponent::PostUpdate() { } void ParticleSystemComponent::SetTexture(const std::string & a_path) { texture_resource_ = GetResourceManager()->Load<Texture>(a_path); #ifdef EDITOR char buffer[256] = ""; const std::string& name = a_path; int name_length = name.length(); for (int i = 0; i < 256; ++i) { if (i < name_length) { texture_path_buffer_[i] = name.c_str()[i]; } else { texture_path_buffer_[i] = buffer[i]; } } #endif } std::vector<RenderDataStruct>* ParticleSystemComponent::OnBuildRenderData() { if(visible_) { #ifdef EDITOR if(!visible_editor_only_ || (visible_editor_only_ && !GetGame()->IsPlaying())) { #endif for(size_t i = 0; i < max_particles_; i++) { render_data_struct_vector_[i].modifier_.sprite_uv_offset_and_scale = modifiers_.sprite_uv_offset_and_scale; render_data_struct_vector_[i].texture_ = GetResourceManager()->Get<Texture>(texture_resource_); } return &render_data_struct_vector_; #ifdef EDITOR } #endif } return nullptr; } void ParticleSystemComponent::Inspect() { #ifdef EDITOR collapsing_header_open_ = false; collapsing_header_id_ = 0; if_one_collapsing_header_open_ = false; ImGuiTreeNodeFlags tree_node_flags_ = ImGuiTreeNodeFlags_Framed; if (ImGui::CollapsingHeader("Rendering", tree_node_flags_)) { ImGui::Text("Texture path"); ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue; ImGui::SameLine(0.f, 0.f); ImGui::Text(":"); ImGui::SameLine(0.f, 10.f); if (ImGui::InputText("TexturePath", texture_path_buffer_, IM_ARRAYSIZE(texture_path_buffer_), input_text_flags)) { SetTexture(texture_path_buffer_); } } ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() * 0.4f); ImGui::PushID("PushID part PS spawning"); StartCollapsingHeaderState(); if (ImGui::CollapsingHeader("Spawn", tree_node_flags_)) { collapsing_header_open_ = true; if_one_collapsing_header_open_ = true; if (collapsing_header_state_last_frame_ == collapsing_header_id_) { int newParticleSize = max_particles_; ImGui::DragInt("max particles", &newParticleSize, 1, 1, 10000); if (newParticleSize > max_particles_) { int grow = newParticleSize - max_particles_; for (size_t i = 0; i < grow; i++) { GrowPoolCount(); } } max_particles_ = newParticleSize; ImGui::DragInt("Max spawn per frame", &max_spawn_per_frame_); ImGui::Checkbox("Loop", &loop_); ImGui::Checkbox("Burst", &burst_); ImGui::Checkbox("Burst once", &burst_once_); ImGui::DragFloat("Burst time", &burst_time_); ImGui::DragInt("Burst count", &burst_count_, 1.0f, 0, max_spawn_per_frame_); ImGui::DragFloat("Spawn time", &spawn_time_, 0.001f, 0.00001f, 2.0f); ImGui::DragFloat("Particle life time ", &particle_life_time_, 0.01f, 0.0001f, 100.0f); ImGui::DragFloat("Particle life time random", &particle_life_time_random_, 0.01f, 0.0001f, 100.0f); ImGui::DragVector3("Spawn position", spawn_position_, 0.01f, -10000.0f, 10000.0f); ImGui::DragVector3("Spawn range", spawn_range_, 0.01f, -10000.0f, 10000.0f); ImGui::DragVector3("Spawn rotation", spawn_rotation_, 0.01f, -10000.0f, 10000.0f); ImGui::DragVector3("Spawn rotation random", random_spawn_rotion_, 0.01f, -10000.0f, 10000.0f); ImGui::ColorEditv4("Spawn color", start_color_); ImGui::ColorEditv4("Spawn color random offset", start_color_random_offset_); } } EndCollapsingHeaderState(); ImGui::PopID(); ImGui::PushID("PushID part Color ol"); StartCollapsingHeaderState(); if (ImGui::CollapsingHeader("Color over lifetime", tree_node_flags_)) { collapsing_header_open_ = true; if_one_collapsing_header_open_ = true; if (collapsing_header_state_last_frame_ == collapsing_header_id_) { ImGui::RadioButton("R", &color_over_lifetime_ui_state_, 0); ImGui::SameLine(); ImGui::RadioButton("G", &color_over_lifetime_ui_state_, 1); ImGui::SameLine(); ImGui::RadioButton("B", &color_over_lifetime_ui_state_, 2); ImGui::SameLine(); ImGui::RadioButton("A", &color_over_lifetime_ui_state_, 3); switch (color_over_lifetime_ui_state_) { case 0: ImGui::PushID("01PushID pcol01"); color_over_life_r_->Inspect("Color over lifetime red"); ImGui::PopID(); break; case 1: ImGui::PushID("02PushID pcol02"); color_over_life_g_->Inspect("Color over lifetime green"); ImGui::PopID(); break; case 2: ImGui::PushID("03PushID pcol03"); color_over_life_b_->Inspect("Color over lifetime blue"); ImGui::PopID(); break; case 3: ImGui::PushID("04PushID pcol04"); color_over_life_a_->Inspect("Color over lifetime alpha"); ImGui::PopID(); break; default: break; } } } EndCollapsingHeaderState(); ImGui::PopID(); ImGui::PushID("PushID part Angular momentum"); StartCollapsingHeaderState(); if (ImGui::CollapsingHeader("Angular momentum", tree_node_flags_)) { collapsing_header_open_ = true; if_one_collapsing_header_open_ = true; if (collapsing_header_state_last_frame_ == collapsing_header_id_) { ImGui::RadioButton("Angular momentum mode: constant", &angular_velocity_mode_, 0); ImGui::RadioButton("Angular momentum mode: Angular momentum lifetime", &angular_velocity_mode_, 1); ImGui::RadioButton("Angular momentum mode: Angular momentum lifetime random between curves", &angular_velocity_mode_, 2); if (angular_velocity_mode_ == 0) { ImGui::DragVector3("Angular velocity scale", spawn_angular_velocity_, 0.001f, 0.0f, 10000.0f); } ImGui::DragVector3("Angular velocity scale random offset", spawn_angular_velocity_random_, 0.001f, 0.0f, 10000.0f); if (angular_velocity_mode_ == 1) { angular_velocity_over_life_->Inspect("Angular velocity over lifetime"); } else if (angular_velocity_mode_ == 2) { angular_velocity_over_life_between_curves->Inspect("angel V lerp curves"); } } } EndCollapsingHeaderState(); ImGui::PopID(); ImGui::PushID("PushID part Velocity"); StartCollapsingHeaderState(); if (ImGui::CollapsingHeader("Velocity", tree_node_flags_)) { collapsing_header_open_ = true; if_one_collapsing_header_open_ = true; if (collapsing_header_state_last_frame_ == collapsing_header_id_) { //minVelocity ImGui::DragFloat("Minimum Velocity", &minimum_velocity_magnitude_,.001f,-100000.0f, 100000.0f); ImGui::RadioButton("Velocity mode: constant", &velocityMode, 0); ImGui::RadioButton("Velocity mode: velocity lifetime", &velocityMode, 1); ImGui::RadioButton("Velocity mode: velocity lifetime random between curves", &velocityMode, 2); if (velocityMode == 0) { ImGui::DragVector3("Velocity scale", spawn_velocity_, 0.001f, 0.0f, 10000.0f); } ImGui::DragVector3("Velocity scale random offset", spawn_velocity_random_, 0.001f, 0.0f, 10000.0f); if (velocityMode == 1) { velocity_over_life_->Inspect("velocity over lifetime"); } else if (velocityMode == 2) { velocity_over_life_between_curves->Inspect("V over L lerp curves");//velocity over lifetime random between 2 curves } } } EndCollapsingHeaderState(); ImGui::PopID(); ImGui::PushID("PushID part Scale momentum"); StartCollapsingHeaderState(); if (ImGui::CollapsingHeader("Scale", tree_node_flags_)) { collapsing_header_open_ = true; if_one_collapsing_header_open_ = true; if (collapsing_header_state_last_frame_ == collapsing_header_id_) { ImGui::RadioButton("scale mode: constant", &scaleMode, 0); ImGui::RadioButton("scale mode: size lifetime", &scaleMode, 1); if (scaleMode == 0) { ImGui::DragVector3("start scale", start_scale_, 0.01f, 0.0f, 10000.0f); } ImGui::DragVector3("start scale random offset", start_scale_random_, 0.01f, 0.0f, 10000.0f); if (scaleMode == 1) { size_over_life_->Inspect("size over lifetime"); } } } EndCollapsingHeaderState(); ImGui::PopID(); if(!if_one_collapsing_header_open_) { collapsing_header_state_ = -1; } collapsing_header_state_last_frame_ = collapsing_header_state_; #endif } void ParticleSystemComponent::StartCollapsingHeaderState() { collapsing_header_open_ = (collapsing_header_state_ == collapsing_header_id_) ? true : false; #ifdef EDITOR bool open_for_one_frame_ = (collapsing_header_state_last_frame_ == collapsing_header_id_); ImGui::SetNextTreeNodeOpen((open_for_one_frame_&&collapsing_header_open_)); #endif } void ParticleSystemComponent::EndCollapsingHeaderState() { collapsing_header_state_ = collapsing_header_open_ ? collapsing_header_id_ : collapsing_header_state_; collapsing_header_id_++; } }