diff --git a/include/Engine/Input/FirstPersonInputController.h b/include/Engine/Input/FirstPersonInputController.h index 00f36ac7..d9b0d3e8 100644 --- a/include/Engine/Input/FirstPersonInputController.h +++ b/include/Engine/Input/FirstPersonInputController.h @@ -7,6 +7,7 @@ #include "../Game/Events/EDashAbility.h" #include "InputHandler.h" #include "Rendering/EAutoAnimationBlend.h" +#include "Rendering/ESetBlendWeight.h" template class FirstPersonInputController : public InputController @@ -45,7 +46,6 @@ protected: bool m_Crouching = false; //assault dash membervariables - needed to calculate the doubletap- and dashlogic double m_AssaultDashDoubleTapDeltaTime = 0.0; - double m_DashEffectResetTimer = 0.0; //i will let m_AssaultDashDoubleTapSensitivityTimer stay hardcoded, its not really a gamevariable (more an inputvariable), //and its very unlikely that someone wants to change that value const float m_AssaultDashDoubleTapSensitivityTimer = 0.25f; @@ -137,6 +137,7 @@ bool FirstPersonInputController::OnCommand(const Events::InputComm } aeb.RootNode = playerModel; aeb.Start = true; + aeb.SingleLevelBlend = true; m_EventBroker->Publish(aeb); } else if (val < 0) { Events::AutoAnimationBlend aeb; @@ -148,6 +149,7 @@ bool FirstPersonInputController::OnCommand(const Events::InputComm } aeb.RootNode = playerModel; aeb.Start = true; + aeb.SingleLevelBlend = true; aeb.Reverse = true; m_EventBroker->Publish(aeb); } else { @@ -169,6 +171,7 @@ bool FirstPersonInputController::OnCommand(const Events::InputComm aeb.Duration = 0.1; aeb.NodeName = "Right"; aeb.RootNode = playerModel; + aeb.SingleLevelBlend = true; aeb.Start = true; m_EventBroker->Publish(aeb); } else if (val < 0) { @@ -176,6 +179,7 @@ bool FirstPersonInputController::OnCommand(const Events::InputComm aeb.Duration = 0.1; aeb.NodeName = "Left"; aeb.RootNode = playerModel; + aeb.SingleLevelBlend = true; aeb.Start = true; m_EventBroker->Publish(aeb); } else { @@ -186,16 +190,28 @@ bool FirstPersonInputController::OnCommand(const Events::InputComm } } - if (glm::length2(m_Movement) > 0) { - m_Movement = glm::normalize(m_Movement); + + + if (glm::length2(m_Movement) < 0.1f) { + if (m_PlayerEntity.Valid()) { + EntityWrapper playerModel = m_PlayerEntity.FirstChildByName("PlayerModel"); + + if (playerModel.Valid()) { + Events::AutoAnimationBlend aeb; + aeb.Duration = 0.2; + aeb.NodeName = "Idle"; + aeb.RootNode = playerModel; + m_EventBroker->Publish(aeb); + } + } } else { if (m_PlayerEntity.Valid()) { EntityWrapper playerModel = m_PlayerEntity.FirstChildByName("PlayerModel"); if (playerModel.Valid()) { Events::AutoAnimationBlend aeb; - aeb.Duration = 0.1; - aeb.NodeName = "Idle"; + aeb.Duration = 0.2; + aeb.NodeName = "MovementBlend"; aeb.RootNode = playerModel; m_EventBroker->Publish(aeb); } @@ -203,6 +219,29 @@ bool FirstPersonInputController::OnCommand(const Events::InputComm } } + + if (glm::length2(m_Movement) > 0) { + m_Movement = glm::normalize(m_Movement); + + if (m_PlayerEntity.Valid()) { + EntityWrapper playerModel = m_PlayerEntity.FirstChildByName("PlayerModel"); + + if (playerModel.Valid()) { + glm::vec2 direction = glm::normalize(glm::vec2(m_Movement.x, m_Movement.z)); + + double weight = glm::abs(glm::dot(glm::vec2(1, 0), direction)); + LOG_INFO("Weight %f", weight); + Events::SetBlendWeight sbw; + sbw.NodeName = "MovementBlend"; + sbw.Weight = weight; + sbw.RootNode = playerModel; + m_EventBroker->Publish(sbw); + } + } + } + + + if (e.Command == "Forward" || e.Command == "Right") { if (e.Value != 0) { m_CurrentDirectionVector = e.Command == "Right" ? (e.Value > 0 ? "Right" : "Left") : (e.Value > 0 ? "Forward" : "Backward"); @@ -288,17 +327,10 @@ bool FirstPersonInputController::OnLockMouse(const Events::LockMou template void FirstPersonInputController::AssaultDashCheck(double dt, bool isJumping, double assaultDashCoolDownMaxTimer, double& assaultDashCoolDownTimer, EntityID playerID) { m_AssaultDashDoubleTapDeltaTime += dt; - m_DashEffectResetTimer += dt; assaultDashCoolDownTimer -= dt; //cooldown = assaultDashCoolDownMaxTimer sec, pretend the dash lasts 0.25 sec (for friction to do its work) if (assaultDashCoolDownTimer > (assaultDashCoolDownMaxTimer - 0.25f)) { m_PlayerIsDashing = true; - if (m_DashEffectResetTimer > 0.05) { - Events::DashAbility e; - e.Player = playerID; - m_EventBroker->Publish(e); - m_DashEffectResetTimer = 0.0; - } } else { m_PlayerIsDashing = false; } @@ -310,6 +342,7 @@ void FirstPersonInputController::AssaultDashCheck(double dt, bool assaultDashCoolDownTimer = assaultDashCoolDownMaxTimer; m_AssaultDashDoubleTapped = true; m_AssaultDashDoubleTapDeltaTime = 0.f; + Events::DashAbility e; e.Player = playerID; m_EventBroker->Publish(e); diff --git a/include/Engine/Rendering/AnimationSystem.h b/include/Engine/Rendering/AnimationSystem.h index 30c6fce5..0305e7c7 100644 --- a/include/Engine/Rendering/AnimationSystem.h +++ b/include/Engine/Rendering/AnimationSystem.h @@ -13,6 +13,8 @@ #include "../Core/EntityWrapper.h" #include "Rendering/AutoBlendQueue.h" #include "../Input/EInputCommand.h" +#include "../Core/EEntityDeleted.h" +#include "Rendering/ESetBlendWeight.h" #include "imgui/imgui.h" class AnimationSystem : public ImpureSystem @@ -28,6 +30,13 @@ private: EventRelay m_EAutoAnimationBlend; bool OnAutoAnimationBlend(Events::AutoAnimationBlend& e); + + EventRelay m_EEntityDeleted; + bool OnEntityDeleted(Events::EntityDeleted& e); + + EventRelay m_ESetBlendWeight; + bool OnSetBlendWeight(Events::SetBlendWeight& e); + std::unordered_map m_AutoBlendQueues; }; diff --git a/include/Engine/Rendering/AutoBlendQueue.h b/include/Engine/Rendering/AutoBlendQueue.h index 3c808408..71fe46d8 100644 --- a/include/Engine/Rendering/AutoBlendQueue.h +++ b/include/Engine/Rendering/AutoBlendQueue.h @@ -37,6 +37,8 @@ public: std::shared_ptr GetBlendTree(); AutoBlendQueue::AutoBlendJob& GetActiveBlendJob(); + + bool Empty() { return m_BlendQueue.empty(); } private: std::list m_BlendQueue; diff --git a/include/Engine/Rendering/BlendTree.h b/include/Engine/Rendering/BlendTree.h index 02f4a348..502a1cc9 100644 --- a/include/Engine/Rendering/BlendTree.h +++ b/include/Engine/Rendering/BlendTree.h @@ -75,7 +75,6 @@ public: std::vector GetFinalPose() { return m_FinalPose; } glm::mat4 GetBoneTransform(int boneID); bool IsValid() { return (m_Root == nullptr ? false : true); } - void PrintTree(); BlendTree::AutoBlendInfo AutoBlendStep(AutoBlendInfo blendInfo); @@ -84,16 +83,20 @@ public: EntityWrapper GetSubTreeRoot(std::string nodeName); + std::vector GetSingleLevelRoots(std::string name); + std::vector GetEntitesByName(std::string name); + + void SetWeightByName(std::string name, double weight); + private: Skeleton* m_Skeleton = nullptr; Node* m_Root = nullptr; std::vector m_FinalPose; std::map m_FinalBoneTransforms; - + std::vector FindNodesByName(std::string name); std::vector AccumulateFinalPose(); BlendTree::Node* FillTreeByName(Node* parentNode, std::string name, EntityWrapper parentEntity); - std::vector FindNodesByName(std::string name); void Blend(std::map& pose); }; diff --git a/include/Engine/Rendering/EAutoAnimationBlend.h b/include/Engine/Rendering/EAutoAnimationBlend.h index 29d3d612..86921f0f 100644 --- a/include/Engine/Rendering/EAutoAnimationBlend.h +++ b/include/Engine/Rendering/EAutoAnimationBlend.h @@ -18,7 +18,6 @@ struct AutoAnimationBlend : Event bool Reverse = false; bool Restart = false; bool SingleLevelBlend = false; - double Weight = -1.0; EntityWrapper AnimationEntity = EntityWrapper::Invalid; diff --git a/include/Engine/Rendering/ESetBlendWeight.h b/include/Engine/Rendering/ESetBlendWeight.h new file mode 100644 index 00000000..2a455d88 --- /dev/null +++ b/include/Engine/Rendering/ESetBlendWeight.h @@ -0,0 +1,20 @@ +#ifndef Events_SetBlendWeight_h__ +#define Events_SetBlendWeight_h__ + +#include "../Core/EventBroker.h" +#include "../Core/EntityWrapper.h" + +namespace Events +{ + +//Sets the blend weight for all nodes with "NodeName" +struct SetBlendWeight : Event +{ + EntityWrapper RootNode = EntityWrapper::Invalid; + std::string NodeName; + double Weight; +}; + +} + +#endif diff --git a/resources/Schema/Entities/DashEffect.xml b/resources/Schema/Entities/DashEffect.xml index 9dfdc14c..6f28784d 100644 --- a/resources/Schema/Entities/DashEffect.xml +++ b/resources/Schema/Entities/DashEffect.xml @@ -2,17 +2,12 @@ - 0.5 - - - 0 - 0.5 - - - + + + diff --git a/resources/Schema/Entities/Player.xml b/resources/Schema/Entities/Player.xml index 475acf78..16b7ccd1 100644 --- a/resources/Schema/Entities/Player.xml +++ b/resources/Schema/Entities/Player.xml @@ -875,7 +875,7 @@ RunF - 1 + 1.5 true @@ -899,7 +899,7 @@ StrafeRightF - 1 + 2 true @@ -911,7 +911,7 @@ StrafeLeftF - 1 + 2 true @@ -986,7 +986,7 @@ DashForwardF - 2 + 3 false @@ -998,7 +998,7 @@ DashBackwardF - 2 + 3 false @@ -1022,7 +1022,7 @@ DashLeftF - 2 + 3 false @@ -1034,7 +1034,7 @@ DashRightF - 2 + 3 false diff --git a/src/Engine/Rendering/AnimationSystem.cpp b/src/Engine/Rendering/AnimationSystem.cpp index 005778e7..be00a0f4 100644 --- a/src/Engine/Rendering/AnimationSystem.cpp +++ b/src/Engine/Rendering/AnimationSystem.cpp @@ -4,6 +4,8 @@ AnimationSystem::AnimationSystem(SystemParams params) : System(params) { EVENT_SUBSCRIBE_MEMBER(m_EAutoAnimationBlend, &AnimationSystem::OnAutoAnimationBlend); + EVENT_SUBSCRIBE_MEMBER(m_EEntityDeleted, &AnimationSystem::OnEntityDeleted); + EVENT_SUBSCRIBE_MEMBER(m_ESetBlendWeight, &AnimationSystem::OnSetBlendWeight); } void AnimationSystem::Update(double dt) @@ -122,16 +124,13 @@ void AnimationSystem::UpdateAnimations(double dt) } } - void AnimationSystem::UpdateWeights(double dt) { - for (auto& autoBlendQueue : m_AutoBlendQueues) { - - if(autoBlendQueue.second.HasActiveBlendJob()) { - AutoBlendQueue::AutoBlendJob& blendJob = autoBlendQueue.second.GetActiveBlendJob(); - - std::shared_ptr blendTree = autoBlendQueue.second.GetBlendTree(); + for (auto it = m_AutoBlendQueues.begin(); it != m_AutoBlendQueues.end(); ) { + if(it->second.HasActiveBlendJob()) { + AutoBlendQueue::AutoBlendJob& blendJob = it->second.GetActiveBlendJob(); + std::shared_ptr blendTree = it->second.GetBlendTree(); if (blendTree != nullptr) { if (blendJob.Duration != 0.0) { blendJob.BlendInfo.progress = glm::clamp(blendJob.CurrentTime / blendJob.Duration, 0.0, 1.0); @@ -140,10 +139,17 @@ void AnimationSystem::UpdateWeights(double dt) } blendJob.BlendInfo = blendTree->AutoBlendStep(blendJob.BlendInfo); + } else { + it = m_AutoBlendQueues.erase(it); + } + it++; + } else { + if (it->second.Empty()) { + it = m_AutoBlendQueues.erase(it); + } else { + it++; } - } - } } @@ -177,46 +183,163 @@ bool AnimationSystem::OnAutoAnimationBlend(Events::AutoAnimationBlend& e) return false; } + EntityWrapper subTreeRoot; - EntityWrapper subTreeRoot = blendTree->GetSubTreeRoot(e.NodeName); + if (e.SingleLevelBlend) { + subTreeRoot = blendTree->GetSubTreeRoot(e.NodeName); - if (!subTreeRoot.Valid()) { - return false; - } + if (!subTreeRoot.Valid()) { + return false; + } - AutoBlendQueue::AutoBlendJob abj; - abj.AnimationEntity = e.AnimationEntity; - abj.CurrentTime = 0.0; - abj.Delay = e.Delay; - abj.Duration = e.Duration; - abj.RootNode = e.RootNode; + AutoBlendQueue::AutoBlendJob abj; + abj.AnimationEntity = e.AnimationEntity; + abj.CurrentTime = 0.0; + abj.Delay = e.Delay; + abj.Duration = e.Duration; + abj.RootNode = e.RootNode; - abj.BlendInfo.NodeName = e.NodeName; - abj.BlendInfo.progress = 0.0; - abj.BlendInfo.Start = e.Start; - abj.BlendInfo.SingleBlend = e.SingleLevelBlend; - abj.BlendInfo.Weight = e.Weight; + abj.BlendInfo.NodeName = e.NodeName; + abj.BlendInfo.progress = 0.0; + abj.BlendInfo.Start = e.Start; + abj.BlendInfo.SingleBlend = e.SingleLevelBlend; + abj.BlendInfo.Weight = e.Weight; - EntityWrapper nodeEntity = subTreeRoot.FirstChildByName(e.NodeName); // more than one - if (nodeEntity.Valid()) { - if (nodeEntity.HasComponent("Animation")) { - const Skeleton::Animation* animation = skeleton->GetAnimation(nodeEntity["Animation"]["AnimationName"]); - (bool&)nodeEntity["Animation"]["Reverse"] = e.Reverse; + std::vector animationEntities = blendTree->GetEntitesByName(e.NodeName); // more than one + for(auto entity : animationEntities) + if (entity.Valid()) { + if (entity.HasComponent("Animation")) { + const Skeleton::Animation* animation = skeleton->GetAnimation(entity["Animation"]["AnimationName"]); + (bool&)entity["Animation"]["Reverse"] = e.Reverse; - if (e.Restart) { - if (animation != nullptr) { - if (e.Restart) { - if (e.Reverse) { - (double&)nodeEntity["Animation"]["Time"] = animation->Duration; - } else { - (double&)nodeEntity["Animation"]["Time"] = 0.0; + if (e.Restart) { + if (animation != nullptr) { + if (e.Restart) { + if (e.Reverse) { + (double&)entity["Animation"]["Time"] = animation->Duration; + } else { + (double&)entity["Animation"]["Time"] = 0.0; + } } } } } } + m_AutoBlendQueues[subTreeRoot].Insert(abj); + } else { + std::vector subtreeroots = blendTree->GetSingleLevelRoots(e.NodeName); + + for(auto entity : subtreeroots) { + subTreeRoot = entity; + + if (!subTreeRoot.Valid()) { + return false; + } + + AutoBlendQueue::AutoBlendJob abj; + abj.AnimationEntity = e.AnimationEntity; + abj.CurrentTime = 0.0; + abj.Delay = e.Delay; + abj.Duration = e.Duration; + abj.RootNode = e.RootNode; + + abj.BlendInfo.NodeName = e.NodeName; + abj.BlendInfo.progress = 0.0; + abj.BlendInfo.Start = e.Start; + abj.BlendInfo.SingleBlend = e.SingleLevelBlend; + abj.BlendInfo.Weight = e.Weight; + m_AutoBlendQueues[subTreeRoot].Insert(abj); + } + + std::vector animationEntities = blendTree->GetEntitesByName(e.NodeName); // more than one + for (auto entity : animationEntities) + if (entity.Valid()) { + if (entity.HasComponent("Animation")) { + const Skeleton::Animation* animation = skeleton->GetAnimation(entity["Animation"]["AnimationName"]); + (bool&)entity["Animation"]["Reverse"] = e.Reverse; + + if (e.Restart) { + if (animation != nullptr) { + if (e.Restart) { + if (e.Reverse) { + (double&)entity["Animation"]["Time"] = animation->Duration; + } else { + (double&)entity["Animation"]["Time"] = 0.0; + } + } + } + } + } + } } - m_AutoBlendQueues[subTreeRoot].Insert(abj); + + return true; } + +bool AnimationSystem::OnEntityDeleted(Events::EntityDeleted& e) +{ + EntityWrapper entity = EntityWrapper(m_World, e.DeletedEntity); + + if (entity.HasComponent("Model")) { + Model* model; + try { + model = ResourceManager::Load<::Model, true>(entity["Model"]["Resource"]); + } catch (const std::exception&) { + return false; + } + + Skeleton* skeleton = model->m_RawModel->m_Skeleton; + if (skeleton == nullptr) { + return false; + } + + if (skeleton->BlendTrees.find(entity) != skeleton->BlendTrees.end()) { + skeleton->BlendTrees.erase(entity); + } + + + } + +} + +bool AnimationSystem::OnSetBlendWeight(Events::SetBlendWeight& e) +{ + if (!e.RootNode.Valid()) { + return false; + } + + if (!e.RootNode.HasComponent("Model")) { + return false; + } + + Model* model; + try { + model = ResourceManager::Load<::Model, true>((std::string)e.RootNode["Model"]["Resource"]); + } catch (const std::exception&) { + return false; + } + + Skeleton* skeleton = model->m_RawModel->m_Skeleton; + if (skeleton == nullptr) { + return false; + } + + std::shared_ptr blendTree; + if (skeleton->BlendTrees.find(e.RootNode) != skeleton->BlendTrees.end()) { + blendTree = skeleton->BlendTrees.at(e.RootNode); + } else { + return false; + } + + if(e.Weight >= 0 && e.Weight <= 1) { + + blendTree->SetWeightByName(e.NodeName, e.Weight); + + return true; + } else { + return false; + } + +} diff --git a/src/Engine/Rendering/AutoBlendQueue.cpp b/src/Engine/Rendering/AutoBlendQueue.cpp index cafb7ee5..1c6907e8 100644 --- a/src/Engine/Rendering/AutoBlendQueue.cpp +++ b/src/Engine/Rendering/AutoBlendQueue.cpp @@ -75,7 +75,6 @@ void AutoBlendQueue::Insert(AutoBlendJob autoBlendJob) } } - m_BlendQueue.clear(); m_BlendQueue.push_back(blendNode); } @@ -124,7 +123,7 @@ bool AutoBlendQueue::HasActiveBlendJob() try { model = ResourceManager::Load<::Model, true>(blendJob.RootNode["Model"]["Resource"]); } catch (const std::exception&) { - m_BlendQueue.pop_front(); + //m_BlendQueue.pop_front(); return HasActiveBlendJob(); } diff --git a/src/Engine/Rendering/BlendTree.cpp b/src/Engine/Rendering/BlendTree.cpp index 29bbe7e6..1ecafc56 100644 --- a/src/Engine/Rendering/BlendTree.cpp +++ b/src/Engine/Rendering/BlendTree.cpp @@ -2,11 +2,8 @@ BlendTree::BlendTree(EntityWrapper ModelEntity, Skeleton* skeleton) { - m_Skeleton = skeleton; - - if (ModelEntity.HasComponent("Animation")) { const Skeleton::Animation* animation = skeleton->GetAnimation(ModelEntity["Animation"]["AnimationName"]); if (animation == nullptr) { @@ -270,7 +267,7 @@ BlendTree::AutoBlendInfo BlendTree::AutoBlendStep(AutoBlendInfo blendInfo) currentNode = currentNode->Parent; if (blendInfo.SingleBlend) { - break; + return blendInfo;; } } } else if(goalNodes.size() >= 2) { @@ -331,6 +328,9 @@ BlendTree::AutoBlendInfo BlendTree::AutoBlendStep(AutoBlendInfo blendInfo) lastNode = currentNode; currentNode = currentNode->Parent; + if (blendInfo.SingleBlend) { + return blendInfo; + } } } @@ -427,6 +427,55 @@ EntityWrapper BlendTree::GetSubTreeRoot(std::string nodeName) return subTreeRoots.front()->Entity; } + +std::vector BlendTree::GetSingleLevelRoots(std::string name) +{ + std::vector nodes = FindNodesByName(name); + std::vector entities; + + for (auto it = nodes.begin(); it != nodes.end(); it++) { + Node* currentNode = (*it)->Parent; + + if(currentNode->Entity.Valid()) { + entities.push_back(currentNode->Entity); + } + } + + return entities; +} + + +std::vector BlendTree::GetEntitesByName(std::string name) +{ + std::vector nodes = FindNodesByName(name); + std::vector entities; + + for (auto it = nodes.begin(); it != nodes.end(); it++) { + Node* currentNode = (*it); + + if (currentNode->Entity.Valid()) { + entities.push_back(currentNode->Entity); + } + } + + return entities; +} + + +void BlendTree::SetWeightByName(std::string name, double weight) +{ + std::vector nodes = FindNodesByName(name); + + for (auto node : nodes) { + EntityWrapper entity = node->Entity; + + if(entity.HasComponent("Blend")) { + entity["Blend"]["Weight"] = weight; + node->Weight = weight; + } + } +} + void BlendTree::Blend(std::map& pose) { Node* currentNode; diff --git a/src/Engine/Rendering/Skeleton.cpp b/src/Engine/Rendering/Skeleton.cpp index 592279ed..2883c53d 100644 --- a/src/Engine/Rendering/Skeleton.cpp +++ b/src/Engine/Rendering/Skeleton.cpp @@ -321,6 +321,8 @@ Skeleton::~Skeleton() for (auto &kv : Bones) { delete kv.second; } + + BlendTrees.clear(); } const Skeleton::Animation* Skeleton::GetAnimation(std::string name) diff --git a/src/Game/Systems/PlayerMovementSystem.cpp b/src/Game/Systems/PlayerMovementSystem.cpp index 3d897bf7..dbf3d889 100644 --- a/src/Game/Systems/PlayerMovementSystem.cpp +++ b/src/Game/Systems/PlayerMovementSystem.cpp @@ -443,18 +443,34 @@ bool PlayerMovementSystem::OnDashAbility(Events::DashAbility & e) } } } + + + /* EntityWrapper dashEffectModel; + dashEffectModel = playerModel.Clone(dashEffect); + player["Transform"].Copy(dashEffectModel["Transform"]); + + + dashEffectModel.AttachComponent("ExplosionEffect"); + dashEffectModel["ExplosionEffect"]["EndColor"] = (glm::vec4)playerModel["Model"]["Color"]; + ((glm::vec4&)dashEffectModel["ExplosionEffect"]["EndColor"]).w = 0.f; + (double&)dashEffectModel["ExplosionEffect"]["ExplosionDuration"] = dashEffect["Lifetime"]["Lifetime"]; + (glm::vec3&)dashEffectModel["ExplosionEffect"]["ExplosionOrigin"] = glm::vec3(0, 1, 0) + (0.2f * controller->Movement()); + + + + auto animationChildren = dashEffectModel.ChildrenWithComponent("Animation"); + + for (auto animationEntity : animationChildren) { + (bool&)animationEntity["Animation"]["Play"] = false; + }*/ } } } -/* - auto playerEntityAnimation = playerModel["Animation"]; - playerEntityModel.Copy(dashEffect["Model"]); - playerEntityAnimation.Copy(dashEffect["Animation"]); - dashEffect["ExplosionEffect"]["EndColor"] = (glm::vec4)playerEntityModel["Color"]; - ((glm::vec4&)dashEffect["ExplosionEffect"]["EndColor"]).w = 0.f; -*/ + + +