diff --git a/YuppleMayham/include/graphics/animation.h b/YuppleMayham/include/graphics/animation.h index 707ee0a..da14359 100644 --- a/YuppleMayham/include/graphics/animation.h +++ b/YuppleMayham/include/graphics/animation.h @@ -7,9 +7,10 @@ #include #include +#include "utility/xmlloader.h" #include "utility/direction.h" +#include "graphics/sprite.h" -class SpriteAtlas; class ResourceManager; class EventManager; @@ -18,8 +19,66 @@ class EventManager; // name of the animation, // type of animation ie. idle animation, directional, etc. // directory of the animation atlas containing the frames -struct AnimationData; +struct Animation { + const AnimationData *data; + SpriteAtlas *spriteAtlas; + + bool playing; + + float FPS; + unsigned int elapsedTime = 0; + unsigned int lastFrameTick = 0; + unsigned int currentFrame; + int cycles; + + Direction facingDir; + + void reset() { + elapsedTime = 0; + lastFrameTick = 0; + currentFrame = 0; + }; + + void bind() { + spriteAtlas->bind(); + } + + void tick() { + Uint32 currentTime = SDL_GetTicks(); + elapsedTime = currentTime - lastFrameTick; + if (elapsedTime >= 1000.0f / FPS) { + if (++currentFrame > spriteAtlas->size() - 1) { + currentFrame = 0; + cycles += 1; + } + lastFrameTick = currentTime; + } + } + + void draw() { + if (playing) { + tick(); + } else if (!data->directional) { + currentFrame = 0; + } + auto frame = (data->directional) ? + spriteAtlas->frame(currentFrame, facingDir) : + spriteAtlas->frame(currentFrame); + + spriteAtlas->bindFrame(&frame); + spriteAtlas->draw(); + } +}; + +struct AnimationComponent { + int entityID; + // anim-id animation + std::unordered_map> anims; + Animation *curAnim; +}; + +/* class Animation { public: @@ -64,44 +123,29 @@ private: void singleDraw(); void directionalDraw(Direction dir); }; +*/ // We will load our animation component with every loaded animation, // this will be the handler for every animation an entity uses -class AnimationSet : public std::enable_shared_from_this +class AnimationSystem { public: - AnimationSet(const int& entityid); - AnimationSet(const int& entityid, ResourceManager* resourceManager, std::vector animSet); - AnimationSet(const int& entityid, std::unordered_map> animations); + AnimationSystem(std::weak_ptr resourceManger, std::weak_ptr eventManager); + // animID is the first two elements in the ID /. The prefix + bool registerComponent(const int entityID, const std::string& animID); - Animation* operator [](std::string animType) { return anims[animType].get(); } - - void addAnimation(std::unique_ptr anim) { std::string type = anim->getType(); anims.try_emplace(type, std::move(anim)); } - void setAnimation(const std::string& animType) { curAnim = anims[animType].get(); } - - const int getEntityID() const { return entityid; } - const bool getDirectional() const { return isDirectional; } - - void bind(); + void update(); void draw(); - void play() { curAnim->play(); } - void stop() { curAnim->stop(); } - - void setFacingDir(Direction& dir) { facing = dir; } - - void attachEventManager(std::weak_ptr e); + ~AnimationSystem(); private: - int entityid; - bool isDirectional; - Direction facing = Direction::Down; - - std::unordered_map> anims; - Animation* curAnim; + void registerEvents(AnimationComponent *component); + std::vector> animComponents; std::weak_ptr eventManager; + std::weak_ptr resourceManager; }; #endif // _H_ANIMATION_H diff --git a/YuppleMayham/include/utility/resourcemanager.h b/YuppleMayham/include/utility/resourcemanager.h index 6f38edc..2c694e2 100644 --- a/YuppleMayham/include/utility/resourcemanager.h +++ b/YuppleMayham/include/utility/resourcemanager.h @@ -44,14 +44,13 @@ public: std::unique_ptr loadWeapon(const std::string &name, const unsigned weaponShaderID, const unsigned bulletShaderID); - std::shared_ptr loadAnimationSet(const std::string &name, - int entityid = 0); const unsigned loadShader(const std::string &name, const std::string &vertexPath, const std::string &fragPath); const SceneData *loadScene(const std::string &id); const TileSetData *loadTileSet(const std::string &name); + const std::vectorloadAnimationSet(const std::string &id); // Returns a NON-OWNING pointer to a shader by ID Shader *getShaderByID(unsigned int ID); diff --git a/YuppleMayham/src/graphics/animation.cpp b/YuppleMayham/src/graphics/animation.cpp index 9826fe0..a2bc445 100644 --- a/YuppleMayham/src/graphics/animation.cpp +++ b/YuppleMayham/src/graphics/animation.cpp @@ -1,10 +1,165 @@ #include "graphics/animation.h" -#include "graphics/sprite.h" #include "utility/xmlloader.h" #include "utility/resourcemanager.h" #include "utility/events.h" +#include "utility/logger.h" #include "util.h" +AnimationSystem::AnimationSystem(std::weak_ptr resMan, std::weak_ptr eventMan) +{ + eventManager = eventMan; + resourceManager = resMan; +} + +/* TODO: UPDATES AND DRAWS, maybe remove the draw entirely and forward specific frame data to the renderer?? */ + +void AnimationSystem::update() +{ + +} + +bool AnimationSystem::registerComponent(const int entityID, const std::string& animID) +{ + if (auto res = resourceManager.lock()) { + auto animSet = res->loadAnimationSet(animID); + if (animSet.empty()) { + LOG(ERROR, "No animations with prefix '{}' found!", animID); + return false; + } + auto animComp = std::make_unique(); + animComp->entityID = entityID; + for (const auto& data : animSet) { + auto atlas = res->loadSpriteAtlas(data->id, data->frameSize); + auto type = UTIL::get_type(data->id); + if (atlas == nullptr) continue; + + std::unique_ptr anim = std::make_unique(); + anim->spriteAtlas = atlas; + anim->data = data; + anim->playing = false; + anim->FPS = data->FPS; + + animComp->anims[type] = std::move(anim); + if (type == "idle") { + // Given this entity has an idle animation, we will have the entity animate off the hop. + animComp->curAnim->playing = true; + animComp->curAnim = animComp->anims[type].get(); + } + } + // If the set does not contain an idle animation we will just set the current animation to the top of the set + if (animComp->curAnim == nullptr) { + animComp->curAnim = animComp->anims[UTIL::get_type(animSet[0]->id)].get(); + if (animComp->curAnim == nullptr) { + LOG(ERROR, "Could not load any sprites for animation set '{}'", animID); + return false; + } + } + animComponents.push_back(std::move(animComp)); + registerEvents(animComponents.back().get()); + } else { + LOG(ERROR, "Could not obtain resource manager!", NULL); + return false; + } + + return true; +} + +void AnimationSystem::registerEvents(AnimationComponent *comp) +{ + if (auto event = eventManager.lock()) { + + event->subscribe([comp](const DirectionChangeEvent& e) { + if (comp) { + if (e.entityid == comp->entityID) { + Direction d = e.direction; + comp->curAnim->facingDir = d; + } + } + }); + + event->subscribe([comp](const EntityMoveEvent& e) { + if (comp) { + if (e.entityid == comp->entityID) + { + if (comp->curAnim->data->directional) + { + if (comp->anims["move"] != NULL) + comp->curAnim = comp->anims["move"].get(); + } + else + comp->curAnim->playing = true; + } + } + }); + + event->subscribe([comp](const EntityStopEvent& e) { + if (comp) { + if (e.entityid == comp->entityID) + { + if (comp->curAnim->data->directional) + { + if (comp->anims["idle"] != NULL) + comp->curAnim = comp->anims["idle"].get(); + } + else + comp->curAnim->playing = false; + } + } + }); + + event->subscribe([comp](const EntityReloadEvent& e) { + if (comp) { + if (e.entityid == comp->entityID) + { + if (comp->anims["reload"] != NULL) + { + comp->curAnim->reset(); + comp->curAnim = comp->anims["reload"].get(); + comp->curAnim->reset(); + } + } + } + }); + + event->subscribe([comp](const EntityFinishReloadEvent& e) { + if (comp) { + if (e.entityid == comp->entityID) + { + if (comp->anims["idle"] != NULL) { + comp->curAnim->reset(); + comp->curAnim = comp->anims["idle"].get(); + comp->curAnim->reset(); + } + } + } + }); + + event->subscribe([comp](const EntityFireEvent& e) { + if (comp) { + if (e.entityid == comp->entityID) + { + if (comp->anims["fire"] != NULL) + { + comp->curAnim = comp->anims["fire"].get(); + comp->curAnim->reset(); + float newFPS = (1000.f / e.fireDelay) * 15.f; + comp->curAnim->FPS = newFPS; + } + } + } + }); + + event->subscribe([comp](const AnimationFinishedEvent& e) { + if (comp) { + if (e.entityid == comp->entityID && e.animType == "fire") + { + if (comp->anims["idle"] != NULL) + comp->curAnim = comp->anims["idle"].get(); + } + } + }); +} + Animation::Animation(const AnimationData* animData, ResourceManager* resourceManager) : spriteAtlas(resourceManager->loadSpriteAtlas(animData->spriteAtlas, animData->frameSize, animData->directional)), FPS(animData->FPS), diff --git a/YuppleMayham/src/utility/resourcemanager.cpp b/YuppleMayham/src/utility/resourcemanager.cpp index 1fa9851..9e8769b 100644 --- a/YuppleMayham/src/utility/resourcemanager.cpp +++ b/YuppleMayham/src/utility/resourcemanager.cpp @@ -106,11 +106,8 @@ const SceneData *ResourceManager::loadScene(const std::string &id) { return xmlLoader->getSceneData(id); } -std::shared_ptr -ResourceManager::loadAnimationSet(const std::string &prefix, int entityid) { - auto animSetData = xmlLoader->getAnimationSet(prefix); - - return std::make_shared(entityid, this, animSetData); +const std::vector ResourceManager::loadAnimationSet(const std::string &prefix) { + return xmlLoader->getAnimationSet(prefix); } void ResourceManager::clearResources() { diff --git a/YuppleMayham/src/utility/xmlloader.cpp b/YuppleMayham/src/utility/xmlloader.cpp index 8091683..3acef60 100644 --- a/YuppleMayham/src/utility/xmlloader.cpp +++ b/YuppleMayham/src/utility/xmlloader.cpp @@ -220,17 +220,18 @@ bool XMLLoader::loadEntityData(const char *xmlFile, SceneData *out) { ERROR_LOG("Could not load position coordinates for entity. File: {}", xmlFile); e->QueryStringAttribute("weapon", &weaponName); - e->QueryStringAttribute("monster_id", &monsterDef); // If we find that the entity has a monster definition we will fill in the // defaults Having a monster definition means we must be animated by default // and we will prefer the animated default for now. - if (monsterDef != NULL && monsters[monsterDef] != NULL) { - // Setting up the defaults - data.animated = true; - data.monsterDef = monsterDef; - weaponID = monsters[monsterDef]->weapon.c_str(); - graphic = monsters[monsterDef]->anim.c_str(); - scriptID = monsters[monsterDef]->behaviour.c_str(); + if (e->QueryStringAttribute("monster_id", &monsterDef) == tinyxml2::XML_SUCCESS) { + if (monsters[monsterDef]) { + // Setting up the defaults + data.animated = true; + data.monsterDef = monsterDef; + weaponID = monsters[monsterDef]->weapon.c_str(); + graphic = monsters[monsterDef]->anim.c_str(); + scriptID = monsters[monsterDef]->behaviour.c_str(); + } } try { // So if we look for the animation tag and there is no monster definition