Revamped animation system

Smoother reloading
Added idle atlas for player
This commit is contained in:
Ethan Adams 2024-08-17 18:06:13 -04:00
parent 90a843946a
commit 846f85f1f6
28 changed files with 836 additions and 302 deletions

View file

@ -0,0 +1,6 @@
<animations>
<animation name="bubble_idle_anim" type="idle">
<FPS>2</FPS>
<sprite path="sprites/bubbleoAtlas64.png" frameSize="64.0"/>
</animation>
</animations>

View file

@ -0,0 +1,10 @@
<animations>
<animation name="machine_gun_idle_anim" type="idle">
<FPS>4</FPS>
<sprite path="sprites/machineGunAtlas256.png" frameSize="256.0"/>
</animation>
<animation name="machine_gun_reload_anim" type="reload">
<FPS>2</FPS>
<sprite path="sprites/pistolAtlas256.png" frameSize="256.0"/>
</animation>
</animations>

View file

@ -0,0 +1,6 @@
<animations>
<animation name="pistol_idle_anim" type="idle">
<FPS>4</FPS>
<sprite path="sprites/pistolAtlas256.png" frameSize="256.0"/>
</animation>
</animations>

View file

@ -0,0 +1,11 @@
<animations directional="true">
<animation name="player_move_anim" type="move">
<FPS>7</FPS>
<sprite path="sprites/player3AtlasMove64.png" frameSize="64.0"/>
</animation>
<animation name="player_idle_anim" type="idle">
<FPS>5</FPS>
<sprite path="sprites/player3AtlasIdle64.png" frameSize="64.0"/>
</animation>
</animations>

View file

@ -0,0 +1,6 @@
<animations>
<animation name="tmp_enemy_move_anim" type="move">
<FPS>7</FPS>
<sprite path="sprites/player2Atlas.png" frameSize="128.0"/>
</animation>
</animations>

View file

@ -35,18 +35,18 @@
</map> </map>
<entities> <entities>
<player x="7" y="5" weapon="shotGun"> <player x="7" y="5" weapon="shotGun">
<sprite file="sprites/player3Atlas.png" framesize="64.0" directional="true"/> <animation name="player_anim"/>
</player> </player>
<entity x="10" y="3" weapon="pistolGun"> <entity x="10" y="3" weapon="pistolGun">
<sprite file="sprites/player3Atlas.png" frameSize="64.0" directional="true"/> <animation name="player_anim"/>
<script file="scripts/ai/grunt_behaviour.lua"/> <script file="scripts/ai/grunt_behaviour.lua"/>
</entity> </entity>
<entity x="6" y="3" weapon="pistolGun"> <entity x="6" y="3" weapon="pistolGun">
<sprite file="sprites/player2Atlas.png" frameSize="128.0"/> <animation name="tmp_enemy_anim"/>
<script file="scripts/ai/grunt_behaviour.lua"/> <script file="scripts/ai/grunt_behaviour.lua"/>
</entity> </entity>
<entity x="5" y="3" weapon="pistolGun"> <entity x="5" y="3" weapon="pistolGun">
<sprite file="sprites/player2Atlas.png" frameSize="128.0"/> <animation name="tmp_enemy_anim"/>
<script file="scripts/ai/grunt_behaviour.lua"/> <script file="scripts/ai/grunt_behaviour.lua"/>
</entity> </entity>
</entities> </entities>

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<weapons> <weapons>
<weapon name="bubbleGun" fireSpeed="100.0"> <weapon name="bubbleGun" fireSpeed="100.0" maxAmmo="40000" clipSize="1000">
<bullet sprite="sprites/bubbleoAtlas64.png" animated="true" frameSize="64.0"> <bullet anim="bubble_anim">
<spread>60</spread> <spread>60</spread>
<speed>20.0</speed> <speed>20.0</speed>
<drop>250.0</drop> <drop>250.0</drop>
@ -11,13 +11,13 @@
</bullet> </bullet>
</weapon> </weapon>
<weapon name="shotGun" fireSpeed="750.0"> <weapon name="shotGun" fireSpeed="750.0" maxAmmo="64" clipSize="4">
<script file="scripts/weapons/shotgun_script.lua"/> <script file="scripts/weapons/shotgun_script.lua"/>
<sprite file="sprites/machineGunAtlas256.png" animated="true" frameSize="256.0"> <animation name="machine_gun_anim">
<size x="55.0" y="55.0"/> <size x="55.0" y="55.0"/>
<offset x="-30.0" y="0.0"/> <offset x="-30.0" y="0.0"/>
</sprite> </animation>
<bullet sprite="sprites/bullet.png" animated="false" frameSize="64.0"> <bullet sprite="sprites/bullet.png">
<spread>50</spread> <spread>50</spread>
<speed>60.0</speed> <speed>60.0</speed>
<drop>550.0</drop> <drop>550.0</drop>
@ -26,12 +26,12 @@
</bullet> </bullet>
</weapon> </weapon>
<weapon name="machineGun" fireSpeed="50.0"> <weapon name="machineGun" fireSpeed="50.0" maxAmmo="512" clipSize="64">
<sprite file="sprites/machineGunAtlas256.png" animated="true" frameSize="256.0"> <animation name="machine_gun_anim">
<size x="55.0" y="55.0"/> <size x="55.0" y="55.0"/>
<offset x="-30.0" y="0.0"/> <offset x="-30.0" y="0.0"/>
</sprite> </animation>
<bullet sprite="sprites/bullet.png" animated="false" frameSize="64.0"> <bullet sprite="sprites/bullet.png">
<spread>20</spread> <spread>20</spread>
<speed>60.0</speed> <speed>60.0</speed>
<drop>950.0</drop> <drop>950.0</drop>
@ -40,12 +40,12 @@
</bullet> </bullet>
</weapon> </weapon>
<weapon name="pistolGun" fireSpeed="750.0"> <weapon name="pistolGun" fireSpeed="750.0" maxAmmo="512" clipSize="21">
<sprite file="sprites/pistolAtlas256.png" animated="true" frameSize="256.0"> <animation name="pistol_anim">
<size x="30.0" y="30.0"/> <size x="30.0" y="30.0"/>
<offset x="0.0" y="0.0"/> <offset x="0.0" y="0.0"/>
</sprite> </animation>
<bullet sprite="sprites/bullet.png" animated="false" frameSize="64.0"> <bullet sprite="sprites/bullet.png">
<spread>5.0</spread> <spread>5.0</spread>
<speed>50.0</speed> <speed>50.0</speed>
<drop>500.0</drop> <drop>500.0</drop>

View file

@ -67,7 +67,7 @@ add_executable (YuppleMayham
"include/utility/raycaster.h" "include/utility/raycaster.h"
"include/utility/ftfont.h" "include/utility/ftfont.h"
"include/utility/direction.h" "include/utility/direction.h"
) "include/graphics/animation.h" "src/graphics/animation.cpp")
add_custom_command(TARGET YuppleMayham PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/Resources/ $<TARGET_FILE_DIR:YuppleMayham>) add_custom_command(TARGET YuppleMayham PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/Resources/ $<TARGET_FILE_DIR:YuppleMayham>)

View file

@ -4,6 +4,7 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#include <SDL_timer.h>
#include "graphics/shader.h" #include "graphics/shader.h"
@ -19,7 +20,9 @@ public:
position(glm::vec3(0.0f)), position(glm::vec3(0.0f)),
scale(glm::vec3(1.0f)), scale(glm::vec3(1.0f)),
rotation(0.0f), rotation(0.0f),
speed(20.0f) {}; speed(20.0f),
entityid(SDL_GetTicks())
{};
virtual ~Entity() {}; virtual ~Entity() {};
void setPosition(const glm::vec3& position); void setPosition(const glm::vec3& position);
@ -39,6 +42,7 @@ public:
const glm::vec2 getFacingDir() const { return glm::vec2(cos(glm::radians(rotation)), sin(glm::radians(rotation))); } const glm::vec2 getFacingDir() const { return glm::vec2(cos(glm::radians(rotation)), sin(glm::radians(rotation))); }
const glm::vec3 getCenter() const { return glm::vec3(position.x + (0.5f * scale.x), position.y + (0.5f * scale.y), 0.0f); } const glm::vec3 getCenter() const { return glm::vec3(position.x + (0.5f * scale.x), position.y + (0.5f * scale.y), 0.0f); }
const bool getIsMoving() const { return isMoving; } const bool getIsMoving() const { return isMoving; }
const int getEntityID() const { return entityid; }
const std::shared_ptr<PhysicsComponent> getPhysicsComponent() const { return physics; } const std::shared_ptr<PhysicsComponent> getPhysicsComponent() const { return physics; }
// TODO: right now there is no default behavior, but eventually the Entity class will be expanded to handle physics // TODO: right now there is no default behavior, but eventually the Entity class will be expanded to handle physics
@ -53,9 +57,12 @@ protected:
float rotation; float rotation;
float speed; float speed;
int entityid;
std::shared_ptr<PhysicsComponent> physics; std::shared_ptr<PhysicsComponent> physics;
bool isMoving = false; bool isMoving = false;
bool wasMoving = false;
bool isRotatable = true; bool isRotatable = true;
bool flipped = false; bool flipped = false;

View file

@ -19,7 +19,7 @@ class EventManager;
class GameActor : public Entity class GameActor : public Entity
{ {
public: public:
GameActor(const std::shared_ptr<Shader>& shader) : Entity(shader) { actorid = SDL_GetTicks(); } GameActor(const std::shared_ptr<Shader>& shader) : Entity(shader) {}
~GameActor(); ~GameActor();
void addComponent(std::shared_ptr<Component> component); void addComponent(std::shared_ptr<Component> component);
@ -28,8 +28,7 @@ public:
void update(float deltaTime) override; void update(float deltaTime) override;
void render(const std::shared_ptr<Camera>& camera) override; void render(const std::shared_ptr<Camera>& camera) override;
const std::optional<std::shared_ptr<Weapon>>& getHeldWeapon() const; const std::optional<std::shared_ptr<Weapon>> getHeldWeapon() const;
const int getActorID() const { return actorid; }
void setRotation(const float& rotation) override; void setRotation(const float& rotation) override;
@ -52,7 +51,6 @@ private:
using component_vector_t = std::vector<std::shared_ptr<Component>>; using component_vector_t = std::vector<std::shared_ptr<Component>>;
using weapon_vector_t = std::vector<std::shared_ptr<Weapon>>; using weapon_vector_t = std::vector<std::shared_ptr<Weapon>>;
int actorid;
component_vector_t components; component_vector_t components;
weapon_vector_t weapons; weapon_vector_t weapons;
size_t currentWeaponIndex = 0; size_t currentWeaponIndex = 0;

View file

@ -5,6 +5,7 @@
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <vector> #include <vector>
class Component; class Component;
class AnimationSet;
class Camera; class Camera;
class Bullet : public Entity class Bullet : public Entity

View file

@ -30,6 +30,7 @@ public:
Weapon(const WeaponData* data, const std::shared_ptr<Shader>& weaponShader, const std::shared_ptr<Shader>& bulletShader, ResourceManager* resourceManager); Weapon(const WeaponData* data, const std::shared_ptr<Shader>& weaponShader, const std::shared_ptr<Shader>& bulletShader, ResourceManager* resourceManager);
void setWielder(const std::shared_ptr<GameActor>& wielder) { this->wielder = wielder; } void setWielder(const std::shared_ptr<GameActor>& wielder) { this->wielder = wielder; }
void toggleInfiniteAmmo() { infiniteAmmo = !infiniteAmmo; }
void wield() { wielded = true; } void wield() { wielded = true; }
void putaway() { wielded = false; } void putaway() { wielded = false; }
@ -37,16 +38,17 @@ public:
void attachScript(const std::shared_ptr<WeaponScript>& script); void attachScript(const std::shared_ptr<WeaponScript>& script);
void hookEventManager(const std::shared_ptr<EventManager>& eventManager); void hookEventManager(const std::shared_ptr<EventManager>& eventManager);
void shoot(); void shoot();
void reload();
void update(float deltaTime); void update(float deltaTime);
void render(const std::shared_ptr<Camera>& camera); void render(const std::shared_ptr<Camera>& camera);
struct BulletData { struct BulletData {
glm::vec3 origin; glm::vec3 origin;
glm::vec2 direction; glm::vec2 direction;
float mass; float mass = 1.f;
float sizeMod; float sizeMod = 1.f;
float speedMod; float speedMod = 1.f;
float dropMod; float dropMod = 1.f;
}; };
void onHitCallback(std::shared_ptr<GameActor> target, std::shared_ptr<PhysicsComponent> bullet, const glm::vec2& normal); void onHitCallback(std::shared_ptr<GameActor> target, std::shared_ptr<PhysicsComponent> bullet, const glm::vec2& normal);
@ -66,6 +68,12 @@ private:
glm::vec2 weaponSize; glm::vec2 weaponSize;
glm::vec2 weaponOffset; glm::vec2 weaponOffset;
// TODO: Add reloading
// weaponMag will be unused for now, but we will do a reload animation and reload the weapon eventually
Uint16 weaponMag, weaponMagSize, weaponAmmo;
bool reloading = false;
bool wasReloading = false;
bool infiniteAmmo = false;
float bulletSpeed; float bulletSpeed;
float bulletDrop; float bulletDrop;

View file

@ -0,0 +1,102 @@
#ifndef _H_ANIMATION_H
#define _H_ANIMATION_H
#include <memory>
#include <SDL_timer.h>
#include <string>
#include <unordered_map>
#include "utility/direction.h"
class SpriteAtlas;
class ResourceManager;
class EventManager;
// Each entity will contain animation data,
// this data will hold:
// name of the animation,
// type of animation ie. idle animation, directional, etc.
// directory of the animation atlas containing the frames
struct AnimationData;
class Animation
{
public:
Animation(const std::shared_ptr<AnimationData>& animData, ResourceManager* resourceManager);
std::string getName() const { return animName; }
std::string getType() const { return animType; }
void bind();
void draw();
void draw(Direction dir);
void play() { isPlaying = true; }
void stop() { isPlaying = false; }
const bool getPlaying() const { return isPlaying; }
const bool getDirectional() const { return isDirectional; }
const int getCycles() const { return cycles; }
private:
std::string animName;
std::string animType;
std::shared_ptr<SpriteAtlas> spriteAtlas;
Uint32 elapsedTime = 0;
Uint32 lastFrameTick = 0;
float FPS;
int currentFrame;
int cycles;
bool isDirectional;
bool isPlaying;
// this ticks our frame forward according to ticks passed
void frameTick();
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:
AnimationSet(const int& entityid);
AnimationSet(const int& entityid, ResourceManager* resourceManager, std::vector<std::shared_ptr<AnimationData>> animSet);
AnimationSet(const int& entityid, const std::unordered_map<std::string, std::shared_ptr<Animation>>& animations);
std::shared_ptr<Animation>& operator [](std::string animType) { return anims[animType]; }
void addAnimation(const std::shared_ptr<Animation> anim) { anims.try_emplace(anim->getType(), anim); }
void setAnimation(const std::string& animType) { curAnim = anims[animType]; }
const int getEntityID() const { return entityid; }
const bool getDirectional() const { return isDirectional; }
void bind();
void draw();
void play() { curAnim->play(); }
void stop() { curAnim->stop(); }
void setFacingDir(Direction& dir) { facing = dir; }
void attachEventManager(const std::shared_ptr<EventManager>& e);
private:
int entityid;
bool isDirectional;
Direction facing = Direction::Down;
std::unordered_map<std::string, std::shared_ptr<Animation>> anims;
std::shared_ptr<Animation> curAnim;
std::shared_ptr<EventManager> eventManager;
};
#endif // _H_ANIMATION_H

View file

@ -23,8 +23,6 @@ public:
void bind(); void bind();
virtual void draw() = 0; virtual void draw() = 0;
virtual void play() = 0;
virtual void idle() = 0;
virtual std::shared_ptr<Sprite> clone() const = 0; virtual std::shared_ptr<Sprite> clone() const = 0;
protected: protected:
@ -42,8 +40,6 @@ public:
~SpriteStatic(); ~SpriteStatic();
void draw() override; void draw() override;
void play() override { /*unused*/ }
void idle() override { /*unused*/ }
std::shared_ptr<Sprite> clone() const override { std::shared_ptr<Sprite> clone() const override {
return std::make_shared<SpriteStatic>(*this); return std::make_shared<SpriteStatic>(*this);
@ -66,73 +62,54 @@ private:
}; };
class SpriteAnimated : public Sprite class SpriteAtlas : public Sprite
{ {
public: public:
// let's keep texture grids squares, saves everyone time...
SpriteAnimated(const char* textureAtlasPath, float frameSize);
~SpriteAnimated();
void draw() override;
void play() override;
void idle() override;
std::shared_ptr<Sprite> clone() const override {
return std::make_shared<SpriteAnimated>(*this);
}
bool isPlaying = false;
private:
void SetupAnimated(float frameSize);
struct VertexIDs { struct VertexIDs {
unsigned VAO, VBO; unsigned VAO, VBO;
}; };
unsigned EBO; // let's keep texture grids squares, saves everyone time...
SpriteAtlas(const char* textureAtlasPath, float frameSize, bool isDirectional = false);
// Vertex array buffer IDs ~SpriteAtlas();
std::vector<VertexIDs> ids;
int currentFrame = 0;
float FPS = 7.5f;
Uint32 lastFrameTick = 0;
};
class SpriteDirectionalAnimated : public Sprite
{
public:
SpriteDirectionalAnimated(const char* textureAtlasPath, float frameSize);
~SpriteDirectionalAnimated();
void bindFrame(VertexIDs* f);
// draw current frame
void draw() override; void draw() override;
void play() override; VertexIDs frame(int index, Direction dir = Direction::None) {
void idle() override; if (dir == Direction::None) return singleFrame(index);
return dirFrames(dir, index);
std::shared_ptr<Sprite> clone() const override {
return std::make_shared<SpriteDirectionalAnimated>(*this);
} }
Direction direction = Direction::Down; size_t size() { return (isDirectional) ? directionalIDs.size() : singleIDs.size(); }
void setDirection(Direction& dir) { direction = dir; }
bool isPlaying = false; std::shared_ptr<Sprite> clone() const override {
return std::make_shared<SpriteAtlas>(*this);
}
private: private:
void Setup(float frameSize); void Setup(float frameSize);
void SetupDirectional(float frameSize);
struct VertexIDs {
unsigned VAO, VBO;
};
unsigned EBO; unsigned EBO;
bool isDirectional;
std::unordered_map<Direction, VertexIDs> idleIDs; VertexIDs* curFrame;
std::unordered_map<Direction, std::vector<VertexIDs>> walkingIDs;
int currentFrame = 0; VertexIDs singleFrame(int index) const {
float FPS = 7.5f; return (index < 0 || index >= singleIDs.size()) ? singleIDs[0] : singleIDs[index];
Uint32 lastFrameTick = 0; }
// return the frame in the direction at the selected index
VertexIDs dirFrames(Direction dir, int index) const {
return (index < 0 || index >= directionalIDs.at(dir).size()) ? directionalIDs.at(dir)[0] : directionalIDs.at(dir)[index];
}
// Vertex array buffer IDs, if the sprite atlas is non-directional
std::vector<VertexIDs> singleIDs;
// Vertex ids if the sprite atlas is directional
std::unordered_map<Direction, std::vector<VertexIDs>> directionalIDs;
}; };
#endif // _H_SPRITE_H #endif // _H_SPRITE_H

View file

@ -4,6 +4,7 @@
#include "graphics/mesh.h" #include "graphics/mesh.h"
#include "graphics/sprite.h" #include "graphics/sprite.h"
#include "graphics/shader.h" #include "graphics/shader.h"
#include "graphics/animation.h"
#include "gameplay/ai.h" #include "gameplay/ai.h"
#include "events.h" #include "events.h"
@ -21,6 +22,7 @@ public:
virtual void idle() = 0; virtual void idle() = 0;
int ownerid = 0; int ownerid = 0;
private:
std::shared_ptr<EventManager> eventManager; std::shared_ptr<EventManager> eventManager;
}; };
@ -37,6 +39,7 @@ public:
void play() override { /*unused*/ } void play() override { /*unused*/ }
void idle() override { /*unused*/ } void idle() override { /*unused*/ }
~MeshComponent() { mesh->~Mesh(); } ~MeshComponent() { mesh->~Mesh(); }
private: private:
@ -48,33 +51,16 @@ class SpriteComponent : public Component
{ {
public: public:
SpriteComponent(std::shared_ptr<Sprite> sprite) : sprite(sprite), Component(nullptr) {} SpriteComponent(std::shared_ptr<Sprite> sprite) : sprite(sprite), Component(nullptr) {}
SpriteComponent(std::shared_ptr<Sprite> sprite, const std::shared_ptr<EventManager>& eventManager) :
Component(eventManager), sprite(sprite) {
auto directionalSprite = std::dynamic_pointer_cast<SpriteDirectionalAnimated>(sprite);
if (directionalSprite)
{
eventManager->subscribe("OnDirectionChange", [sprite, this](std::shared_ptr<Event> e) {
auto directionEvent = std::static_pointer_cast<DirectionChangeEvent>(e);
auto directionSprite = std::static_pointer_cast<SpriteDirectionalAnimated>(sprite);
if (directionEvent->actorid == ownerid)
directionSprite->setDirection(directionEvent->direction);
});
}
};
void bind() override { void bind() override {
if (sprite) sprite->bind(); if (sprite) sprite->bind();
} }
void update() override {} void update() override { /*unused*/ }
void render() override { void render() override {
if (sprite) sprite->draw(); if (sprite) sprite->draw();
} }
void play() override { void play() override { /*unused*/ }
if (sprite) sprite->play(); void idle() override { /*unused*/ }
}
void idle() override {
if (sprite) sprite->idle();
}
std::shared_ptr<Sprite>& getSprite() { return sprite; } std::shared_ptr<Sprite>& getSprite() { return sprite; }
@ -84,6 +70,42 @@ private:
std::shared_ptr<Sprite> sprite; std::shared_ptr<Sprite> sprite;
}; };
class AnimationComponent : public Component
{
public:
AnimationComponent(std::shared_ptr<AnimationSet> animSet) : animSet(animSet), Component(nullptr) {};
AnimationComponent(std::shared_ptr<AnimationSet> animSet, const std::shared_ptr<EventManager>& eventManager) :
animSet(animSet), Component(nullptr)
{
this->animSet->attachEventManager(eventManager);
}
void bind() override {
if (animSet)
animSet->bind();
}
void update() override {/*unused*/}
void render() override {
if (animSet)
animSet->draw();
}
void play() override {
if (animSet)
animSet->play();
}
void idle() override {
if (animSet)
animSet->stop();
}
std::shared_ptr<AnimationSet>& getAnimationSet() { return animSet; }
~AnimationComponent() { }
private:
std::shared_ptr<AnimationSet> animSet;
};
class ShaderComponent : public Component class ShaderComponent : public Component
{ {
public: public:

View file

@ -5,6 +5,7 @@
enum class Direction { enum class Direction {
None = 0,
Down = 1, Down = 1,
Right = 2, Right = 2,
Left = 3, Left = 3,

View file

@ -44,10 +44,46 @@ public:
class DirectionChangeEvent : public Event { class DirectionChangeEvent : public Event {
public: public:
DirectionChangeEvent(const int actorid, Direction direction) : actorid(actorid), direction(direction) {} DirectionChangeEvent(const int entityid, Direction direction) : entityid(entityid), direction(direction) {}
std::string getType() const override { return "OnDirectionChange"; } std::string getType() const override { return "OnDirectionChange"; }
Direction direction; Direction direction;
int actorid; int entityid;
};
class EntityMoveEvent : public Event {
public:
EntityMoveEvent(const int entityid) : entityid(entityid) {}
std::string getType() const override { return "OnEntityMove"; }
int entityid;
};
class EntityStopEvent : public Event {
public:
EntityStopEvent(const int entityid) : entityid(entityid) {}
std::string getType() const override { return "OnEntityStop"; }
int entityid;
};
class EntityReloadEvent : public Event {
public:
EntityReloadEvent(const int entityid) : entityid(entityid) {}
std::string getType() const override { return "OnEntityReload"; }
int entityid;
};
class EntityFinishReloadEvent : public Event {
public:
EntityFinishReloadEvent(const int entityid) : entityid(entityid) {}
std::string getType() const override { return "OnEntityFinishReload"; }
int entityid;
};
class AnimationFinishedEvent : public Event {
public:
AnimationFinishedEvent(const int entityid, const std::string animType) : entityid(entityid), animType(animType) {}
std::string getType() const override { return "OnAnimationFinished"; }
std::string animType;
int entityid;
}; };
class EventManager { class EventManager {

View file

@ -9,9 +9,11 @@
#include <cassert> #include <cassert>
class Sprite; class Sprite;
class SpriteAtlas;
class Shader; class Shader;
class Weapon; class Weapon;
class Script; class Script;
class AnimationSet;
class AIScript; class AIScript;
class WeaponScript; class WeaponScript;
class TileSet; class TileSet;
@ -25,10 +27,10 @@ public:
{ {
xmlLoader->loadWeapons("weapons"); xmlLoader->loadWeapons("weapons");
xmlLoader->loadScenes("scenes"); xmlLoader->loadScenes("scenes");
xmlLoader->loadAnimations("animations");
}; };
std::shared_ptr<Sprite> loadSpriteAnimated (const std::string& path, float frameSize); std::shared_ptr<SpriteAtlas> loadSpriteAtlas (const std::string& path, float frameSize, bool isDirectional = false);
std::shared_ptr<Sprite> loadSpriteDirAnimated (const std::string& path, float frameSize);
std::shared_ptr<Sprite> loadSpriteStatic (const std::string& path); std::shared_ptr<Sprite> loadSpriteStatic (const std::string& path);
std::shared_ptr<AIScript> loadAIScript (const std::string& path); std::shared_ptr<AIScript> loadAIScript (const std::string& path);
std::shared_ptr<WeaponScript> loadWeaponScript (const std::string& path); std::shared_ptr<WeaponScript> loadWeaponScript (const std::string& path);
@ -37,6 +39,7 @@ public:
std::shared_ptr<Shader> loadShader (const std::string& name, const std::string& vertexPath, const std::string& fragPath); std::shared_ptr<Shader> loadShader (const std::string& name, const std::string& vertexPath, const std::string& fragPath);
std::shared_ptr<Weapon> loadWeapon (const std::string& name, std::shared_ptr<Shader> weaponShader, std::shared_ptr<Shader> bulletShader); std::shared_ptr<Weapon> loadWeapon (const std::string& name, std::shared_ptr<Shader> weaponShader, std::shared_ptr<Shader> bulletShader);
std::shared_ptr<SceneData> loadScene (const std::string& id); std::shared_ptr<SceneData> loadScene (const std::string& id);
std::shared_ptr<AnimationSet> loadAnimationSet(const std::string& name, int entityid = 0);
void clearResources(); void clearResources();

View file

@ -10,12 +10,11 @@ struct Tile;
struct EntityData { struct EntityData {
bool isPlayer; bool isPlayer;
bool animated;
int x = 0, y = 0; int x = 0, y = 0;
std::string sprite; std::string graphic;
float frameSize = 0.f;
std::string weapon = "pistolGun"; std::string weapon = "pistolGun";
std::string script; std::string script;
bool isDirectional = false;
}; };
struct MapData { struct MapData {
@ -37,26 +36,44 @@ struct SceneData {
struct WeaponData { struct WeaponData {
std::string name; std::string name;
float fireSpeed = 250.0f; float fireSpeed = 250.0f;
int clipSize = 21;
int maxAmmo = 512;
std::string script = ""; std::string script = "";
std::string sprite; std::string graphic;
std::string animSet;
bool animated = false; bool animated = false;
float frameSize;
float sizeX = 50.f, sizeY = 50.f; float sizeX = 50.f, sizeY = 50.f;
float offsetX = 0.f, offsetY = 0.f; float offsetX = 0.f, offsetY = 0.f;
float bulletFrameSize;
float bulletSizeX = 50.f, bulletSizeY = 50.f; float bulletSizeX = 50.f, bulletSizeY = 50.f;
std::string bulletSprite; std::string bulletGraphic;
bool bulletAnimated = false; bool bulletAnimated = false;
float bulletSpread = 1.0f, bulletSpeed = 3.0f, bulletDrop = 500.f; float bulletSpread = 1.0f, bulletSpeed = 3.0f, bulletDrop = 500.f;
float modMin = 0.5f, modMax = 1.0f; float modMin = 0.5f, modMax = 1.0f;
}; };
struct AnimationData {
std::string name;
std::string type;
std::string spriteAtlas;
// Each entity will have a set of animations,
// this animation set is meant to keep each group
// of animations within their set.
// The actual set name is based on the file the animation
// is held in.
std::string animSet;
bool directional = false;
bool oneShot;
float FPS = 1.f;
float frameSize;
};
class XMLLoader class XMLLoader
{ {
public: public:
XMLLoader() {} XMLLoader() {}
bool loadScenes(const char* sceneFolder); bool loadScenes(const char* sceneFolder);
bool loadWeapons(const char* weaponFolder); bool loadWeapons(const char* weaponFolder);
bool loadAnimations(const char* animationFolder);
const std::shared_ptr<SceneData> getSceneData(const std::string& id) const { const std::shared_ptr<SceneData> getSceneData(const std::string& id) const {
try { try {
@ -66,15 +83,39 @@ public:
return nullptr; return nullptr;
} }
} }
const std::shared_ptr<AnimationData> getAnimationData(const std::string& name) const {
try {
return animations.at(name);
}
catch (std::exception&) {
return nullptr;
}
}
// return a full set of animations, may need further optimization.
// one idea is when loading animations we create a seperate map that holds each set by their reference, so we can just do a simple,
// hash table lookup.
std::vector<std::shared_ptr<AnimationData>> getAnimationSet(const std::string& set) const {
std::vector<std::shared_ptr<AnimationData>> animSet;
animSet.reserve(animations.size());
for (const auto& [name, anim] : animations) {
if (anim->animSet == set) animSet.push_back(anim);
}
animSet.shrink_to_fit();
return animSet;
}
const WeaponData* getWeaponDataByName(const char* name) const; const WeaponData* getWeaponDataByName(const char* name) const;
void clearData() { scenes.clear(); weaponData.clear(); } void clearData() { scenes.clear(); weaponData.clear(); animations.clear(); }
protected: protected:
bool loadXmlScene(const char* xmlFile, SceneData* out); bool loadXmlScene(const char* xmlFile, SceneData* out);
bool loadMap(const char* xmlFile, SceneData* out); bool loadMap(const char* xmlFile, SceneData* out);
bool loadEntityData(const char* xmlFile, SceneData* out); bool loadEntityData(const char* xmlFile, SceneData* out);
private: private:
std::unordered_map<std::string, std::shared_ptr<SceneData>> scenes; std::unordered_map<std::string, std::shared_ptr<SceneData>> scenes;
std::unordered_map<std::string, std::shared_ptr<AnimationData>> animations;
std::vector<WeaponData> weaponData; std::vector<WeaponData> weaponData;
}; };

View file

@ -10,11 +10,11 @@ GameActor::~GameActor() { }
void GameActor::addComponent(std::shared_ptr<Component> component) void GameActor::addComponent(std::shared_ptr<Component> component)
{ {
component->ownerid = actorid; component->ownerid = entityid;
components.push_back(component); components.push_back(component);
} }
const std::optional<std::shared_ptr<Weapon>>& GameActor::getHeldWeapon() const const std::optional<std::shared_ptr<Weapon>> GameActor::getHeldWeapon() const
{ {
return (weapons.empty() || currentWeaponIndex >= weapons.size()) ? std::nullopt : std::make_optional(weapons[currentWeaponIndex]); return (weapons.empty() || currentWeaponIndex >= weapons.size()) ? std::nullopt : std::make_optional(weapons[currentWeaponIndex]);
} }
@ -23,6 +23,7 @@ void GameActor::pickupWeapon(std::shared_ptr<Weapon> weapon)
{ {
weapon->setWielder(std::shared_ptr<GameActor>(this)); weapon->setWielder(std::shared_ptr<GameActor>(this));
weapons.push_back(weapon); weapons.push_back(weapon);
// wield the newly picked up weapon.
getHeldWeapon().value()->putaway(); getHeldWeapon().value()->putaway();
currentWeaponIndex = weapons.size() - 1; currentWeaponIndex = weapons.size() - 1;
getHeldWeapon().value()->wield(); getHeldWeapon().value()->wield();
@ -40,7 +41,7 @@ void GameActor::setRotation(const float& rotation)
if (!isRotatable && eventManager) { if (!isRotatable && eventManager) {
Direction newDir = getDirectionFromRotation(rotation); Direction newDir = getDirectionFromRotation(rotation);
if (getDirectionFromRotation(this->rotation) != newDir) if (getDirectionFromRotation(this->rotation) != newDir)
eventManager->notify(std::make_shared<DirectionChangeEvent>(actorid, newDir)); eventManager->notify(std::make_shared<DirectionChangeEvent>(entityid, newDir));
} }
this->rotation = rotation; this->rotation = rotation;
updateModelMatrix(); updateModelMatrix();
@ -54,24 +55,36 @@ void GameActor::update(float deltaTime)
component->update(); component->update();
for (auto& weapon : weapons) for (auto& weapon : weapons)
weapon->update(deltaTime); weapon->update(deltaTime);
if (eventManager)
{
if (isMoving && !wasMoving)
{
eventManager->notify(std::make_shared<EntityMoveEvent>(entityid));
wasMoving = true;
}
else if (!isMoving && wasMoving)
{
eventManager->notify(std::make_shared<EntityStopEvent>(entityid));
wasMoving = false;
}
}
isMoving = false;
} }
void GameActor::render(const std::shared_ptr<Camera>& camera) void GameActor::render(const std::shared_ptr<Camera>& camera)
{ {
Entity::render(camera); Entity::render(camera);
// regular loop through components, but if the component returns true to kill, we erase it.
// Components are always assumed to be smart pointers!
for (auto& component : components) for (auto& component : components)
{ {
if (isMoving)
component->play();
else
component->idle();
component->bind(); component->bind();
component->render(); component->render();
} }
for (auto& weapon : weapons) for (auto& weapon : weapons)
weapon->render(camera); weapon->render(camera);
isMoving = false;
} }
void GameActor::moveUp() { if (physics) physics->rigidBody.applyForce(glm::vec3( 0.f,-1.f, 0.f), 1500.25f); isMoving = true; } void GameActor::moveUp() { if (physics) physics->rigidBody.applyForce(glm::vec3( 0.f,-1.f, 0.f), 1500.25f); isMoving = true; }
@ -108,7 +121,7 @@ void GameActor::followMouse(const MouseState& mouse_state)
float newRotation = glm::degrees(glm::atan(direction.y, direction.x)); float newRotation = glm::degrees(glm::atan(direction.y, direction.x));
if (getDirectionFromRotation(rotation) != getDirectionFromRotation(newRotation)) if (getDirectionFromRotation(rotation) != getDirectionFromRotation(newRotation))
if (eventManager) if (eventManager)
eventManager->notify(std::make_shared<DirectionChangeEvent>(actorid, getDirectionFromRotation(newRotation))); eventManager->notify(std::make_shared<DirectionChangeEvent>(entityid, getDirectionFromRotation(newRotation)));
//setRotation(glm::degrees(glm::atan(direction.y, direction.x))); //setRotation(glm::degrees(glm::atan(direction.y, direction.x)));
this->rotation = newRotation; this->rotation = newRotation;
} }

View file

@ -8,6 +8,7 @@
#include "graphics/tile.h" #include "graphics/tile.h"
#include "graphics/sprite.h" #include "graphics/sprite.h"
#include "graphics/animation.h"
#include "utility/component.h" #include "utility/component.h"
#include "utility/xmlloader.h" #include "utility/xmlloader.h"
@ -57,22 +58,25 @@ void Scene::loadDebugShooterScene()
for (EntityData entityData : sceneData->entities) for (EntityData entityData : sceneData->entities)
{ {
auto entity = std::make_shared<GameActor>(playerShader); auto entity = std::make_shared<GameActor>(playerShader);
std::shared_ptr<Sprite> entitySprite;
// Directional is the kind of sprite sheet we are using, in this case for directional, I have the sprite sheet handle the rotations // Directional is the kind of sprite sheet we are using, in this case for directional, I have the sprite sheet handle the rotations
// instead of just rotating the object, this makes it look quite a bit better from the end user perspective. // instead of just rotating the object, this makes it look quite a bit better from the end user perspective.
if (entityData.isDirectional) if (entityData.animated)
{ {
entitySprite = resourceManager->loadSpriteDirAnimated(entityData.sprite, entityData.frameSize); auto entityAnimation = resourceManager->loadAnimationSet(entityData.graphic, entity->getEntityID());
// because we don't want to have the engine rotate the object based on the entities rotation, // because we don't want to have the engine rotate the object based on the entities rotation,
// we set the this value to false so we no longer rotate the object. // we set the this value to false so we no longer rotate the object.
if (entityAnimation->getDirectional())
entity->setRotatable(false); entity->setRotatable(false);
entity->addComponent(std::make_shared<AnimationComponent>(entityAnimation, eventManager));
} }
else else
entitySprite = resourceManager->loadSpriteAnimated(entityData.sprite, entityData.frameSize); {
auto entitySprite = resourceManager->loadSpriteStatic(entityData.graphic);
entity->addComponent(std::make_shared<SpriteComponent>(entitySprite));
}
auto defaultWeapon = resourceManager->loadWeapon("pistolGun", weaponShader, bubbleShader); auto defaultWeapon = resourceManager->loadWeapon("pistolGun", weaponShader, bubbleShader);
auto entityWeapon = resourceManager->loadWeapon(entityData.weapon, weaponShader, bubbleShader); auto entityWeapon = resourceManager->loadWeapon(entityData.weapon, weaponShader, bubbleShader);
entity->addComponent(std::make_shared<SpriteComponent>(entitySprite, eventManager));
entity->pickupWeapon(defaultWeapon); entity->pickupWeapon(defaultWeapon);
entity->pickupWeapon(entityWeapon); entity->pickupWeapon(entityWeapon);
entity->hookEventManager(eventManager); entity->hookEventManager(eventManager);
@ -80,7 +84,7 @@ void Scene::loadDebugShooterScene()
entity->setScale(glm::vec3(mapData.tileSize, mapData.tileSize, 1.f)); entity->setScale(glm::vec3(mapData.tileSize, mapData.tileSize, 1.f));
entity->addPhysicsComponent( entity->addPhysicsComponent(
physicsEngine->createObject(entity->getActorID(), physicsEngine->createObject(entity->getEntityID(),
entity->getPosition(), entity->getPosition(),
49.0, 49.0,
PhysicsComponent::Collider::Shape::Circle, PhysicsComponent::Collider::Shape::Circle,
@ -107,7 +111,7 @@ void Scene::loadDebugShooterScene()
entity->addComponent(std::make_shared<AIComponent>(ai)); entity->addComponent(std::make_shared<AIComponent>(ai));
} }
} }
entities.emplace(entity->getActorID(), entity); entities.emplace(entity->getEntityID(), entity);
} }
physicsEngine->loadCollisionMap(map->getCollisionMap(), sceneData->map.tileSize); physicsEngine->loadCollisionMap(map->getCollisionMap(), sceneData->map.tileSize);

View file

@ -12,8 +12,6 @@
#include "utility/script.h" #include "utility/script.h"
// TODO: Regular clean up, make this mess readable! // TODO: Regular clean up, make this mess readable!
// TODO: Attach script to weapons, create custom shoot scripts, allowing the addition of shotguns and other whacky things
// TODO: Finally just allow the XMLLoader to read the weaponscript supplied and load it into the weapon
Weapon::Weapon(const WeaponData* data, const std::shared_ptr<Shader>& weaponShader, const std::shared_ptr<Shader>& bulletShader, ResourceManager* resourceManager) Weapon::Weapon(const WeaponData* data, const std::shared_ptr<Shader>& weaponShader, const std::shared_ptr<Shader>& bulletShader, ResourceManager* resourceManager)
: :
@ -21,25 +19,28 @@ Weapon::Weapon(const WeaponData* data, const std::shared_ptr<Shader>& weaponShad
bulletShader (bulletShader), bulletShader (bulletShader),
weaponSize (glm::vec2(data->sizeX, data->sizeY)), weaponSize (glm::vec2(data->sizeX, data->sizeY)),
weaponOffset (glm::vec2(data->offsetX, data->offsetY)), weaponOffset (glm::vec2(data->offsetX, data->offsetY)),
weaponMagSize (data->clipSize),
weaponMag (data->clipSize),
weaponAmmo (data->maxAmmo),
bulletDrop (data->bulletDrop), bulletDrop (data->bulletDrop),
bulletSpeed (data->bulletSpeed), bulletSpeed (data->bulletSpeed),
bulletSize (glm::vec2(data->bulletSizeX, data->bulletSizeY)), bulletSize (glm::vec2(data->bulletSizeX, data->bulletSizeY)),
bulletSpread (std::make_shared<UTIL::RandomGenerator>(-data->bulletSpread, data->bulletSpread)), bulletSpread (std::make_shared<UTIL::RandomGenerator>(-data->bulletSpread, data->bulletSpread)),
bulletModifer (std::make_shared<UTIL::RandomGenerator>(data->modMin, data->modMax)), bulletModifer (std::make_shared<UTIL::RandomGenerator>(data->modMin, data->modMax)),
fireSpeed (data->fireSpeed), fireSpeed (data->fireSpeed),
bulletManager (std::make_shared<BulletManager>()), bulletManager (std::make_shared<BulletManager>())
bulletSprite (data->bulletAnimated ?
std::make_shared<SpriteComponent>(resourceManager->loadSpriteAnimated(data->bulletSprite, data->bulletFrameSize))
: std::make_shared<SpriteComponent>(resourceManager->loadSpriteStatic(data->bulletSprite)))
{ {
if (data->bulletAnimated)
bulletSprite = std::make_shared<AnimationComponent>(resourceManager->loadAnimationSet(data->bulletGraphic, entityid));
else
bulletSprite = std::make_shared<SpriteComponent>(resourceManager->loadSpriteStatic(data->bulletGraphic));
if (data->animated) if (data->animated)
{ {
addComponent(std::make_shared<SpriteComponent>(resourceManager->loadSpriteAnimated(data->sprite, data->frameSize))); addComponent(std::make_shared<AnimationComponent>(resourceManager->loadAnimationSet(data->graphic, entityid)));
for (auto& c : components)
c->play();
} }
else else
addComponent(std::make_shared<SpriteComponent>(resourceManager->loadSpriteStatic(data->sprite))); addComponent(std::make_shared<SpriteComponent>(resourceManager->loadSpriteStatic(data->graphic)));
this->setScale(glm::vec3(weaponSize.x, weaponSize.y, 1.0f)); this->setScale(glm::vec3(weaponSize.x, weaponSize.y, 1.0f));
}; };
@ -47,18 +48,38 @@ void Weapon::addComponent(const std::shared_ptr<Component>& comp) {
components.push_back(comp); components.push_back(comp);
} }
void Weapon::reload()
{
// TODO: Create reload event that will be captured by the gun animation set, to start the reloading animation
if (eventManager)
{
eventManager->notify(std::make_shared<EntityReloadEvent>(entityid));
reloading = true;
if (weaponAmmo < weaponMagSize) {
weaponMag = weaponAmmo;
weaponAmmo = 0;
}
else {
weaponMag = weaponMagSize;
weaponAmmo -= weaponMagSize;
}
}
}
void Weapon::shoot() void Weapon::shoot()
{ {
if (wielder) if (wielder)
{ {
Uint32 currentTime = SDL_GetTicks(); Uint32 currentTime = SDL_GetTicks();
if (currentTime - lastFireTime >= fireSpeed) if (weaponMag <= 0 && !reloading) reload();
if (currentTime - lastFireTime >= fireSpeed && !reloading && weaponMag > 0)
{ {
if (!weaponScript || !weaponScript->lua["onShoot"].valid()) if (!weaponScript || !weaponScript->lua["onShoot"].valid())
{ {
// create bullet using this generated data // create bullet using this generated data
BulletData b = genBulletData(); BulletData b = genBulletData();
createBullet(b); createBullet(b);
weaponMag -= 1;
} }
else { else {
auto result = weaponScript->lua["onShoot"](); auto result = weaponScript->lua["onShoot"]();
@ -67,6 +88,7 @@ void Weapon::shoot()
sol::error err = result; sol::error err = result;
std::cerr << "lua error: " << err.what() << std::endl; std::cerr << "lua error: " << err.what() << std::endl;
} }
weaponMag -= 1;
} }
lastFireTime = currentTime; lastFireTime = currentTime;
} }
@ -76,6 +98,28 @@ void Weapon::shoot()
void Weapon::hookEventManager(const std::shared_ptr<EventManager>& eventManager) void Weapon::hookEventManager(const std::shared_ptr<EventManager>& eventManager)
{ {
this->eventManager = eventManager; this->eventManager = eventManager;
for (auto& component : components)
{
auto animComponent = std::dynamic_pointer_cast<AnimationComponent>(component);
if (animComponent != nullptr)
{
animComponent->getAnimationSet()->attachEventManager(eventManager);
}
}
this->eventManager->subscribe("OnAnimationFinished", [&, this](std::shared_ptr<Event> e) {
auto animFinished = std::static_pointer_cast<AnimationFinishedEvent>(e);
if (animFinished->entityid == entityid && animFinished->animType == "reload")
{
if (reloading)
{
reloading = false;
wasReloading = true;
}
}
});
bulletManager->hookEventManager(eventManager); bulletManager->hookEventManager(eventManager);
} }
@ -110,6 +154,11 @@ void Weapon::update(float deltaTime)
for (auto& component : components) for (auto& component : components)
component->update(); component->update();
if (eventManager && wasReloading)
{
wasReloading = false;
eventManager->notify(std::make_shared<EntityFinishReloadEvent>(entityid));
}
} }
bulletManager->update(deltaTime); bulletManager->update(deltaTime);
} }
@ -121,6 +170,7 @@ void Weapon::render(const std::shared_ptr<Camera>& camera)
{ {
for (auto& component : components) for (auto& component : components)
{ {
component->play();
component->bind(); component->bind();
component->render(); component->render();
} }
@ -185,9 +235,9 @@ Weapon::BulletData Weapon::genBulletData()
void Weapon::createBullet(const Weapon::BulletData& data) void Weapon::createBullet(const Weapon::BulletData& data)
{ {
auto bullet = std::make_shared<Bullet>(wielder->getActorID(), bulletShader, data.origin, data.direction, bulletSpeed, bulletDrop, bulletSize); auto bullet = std::make_shared<Bullet>(wielder->getEntityID(), bulletShader, data.origin, data.direction, bulletSpeed, bulletDrop, bulletSize);
bullet->addComponent(bulletSprite); bullet->addComponent(bulletSprite);
bullet->addPhysicsComponent(std::make_shared<PhysicsComponent>(PhysicsComponentFactory::makeBullet(wielder->getActorID(), data.origin, data.mass, bulletSize.x / 2))); bullet->addPhysicsComponent(std::make_shared<PhysicsComponent>(PhysicsComponentFactory::makeBullet(wielder->getEntityID(), data.origin, data.mass, bulletSize.x / 2)));
bullet->getPhysicsComponent()->rigidBody.velocity += bulletSpeed * glm::vec3(data.direction.x, data.direction.y, 0.f) / data.mass; bullet->getPhysicsComponent()->rigidBody.velocity += bulletSpeed * glm::vec3(data.direction.x, data.direction.y, 0.f) / data.mass;
if (eventManager) if (eventManager)
@ -195,3 +245,22 @@ void Weapon::createBullet(const Weapon::BulletData& data)
bulletManager->addBullet(bullet); bulletManager->addBullet(bullet);
} }
/*
!| SLATED FOR REMOVAL |!
// Swap the reload animation and the regular weapon animation
void Weapon::swapSprites()
{
//std::swap(weaponSprites.first, weaponSprites.second);
}
void Weapon::checkAndFinishReload()
{
if (weaponSprites.first->kill() && reloading) {
weaponSprites.first->idle();
weaponSprites.first->reset();
std::swap(weaponSprites.first, weaponSprites.second);
reloading = false;
}
}
*/

View file

@ -0,0 +1,181 @@
#include "graphics/animation.h"
#include "graphics/sprite.h"
#include "utility/xmlloader.h"
#include "utility/resourcemanager.h"
#include "utility/events.h"
Animation::Animation(const std::shared_ptr<AnimationData>& animData, ResourceManager* resourceManager) :
animName(animData->name),
animType(animData->type),
FPS(animData->FPS),
currentFrame(0),
cycles(0),
isDirectional(animData->directional),
isPlaying(isDirectional),
spriteAtlas(resourceManager->loadSpriteAtlas(animData->spriteAtlas, animData->frameSize, animData->directional))
{}
void Animation::bind()
{
spriteAtlas->bind();
}
void Animation::draw()
{
singleDraw();
}
void Animation::draw(Direction dir)
{
directionalDraw(dir);
}
void Animation::singleDraw()
{
if (isPlaying)
{
frameTick();
auto frame = spriteAtlas->frame(currentFrame);
spriteAtlas->bindFrame(&frame);
spriteAtlas->draw();
}
else
{
auto frame = spriteAtlas->frame(0);
spriteAtlas->bindFrame(&frame);
spriteAtlas->draw();
}
}
void Animation::directionalDraw(Direction dir)
{
if (isPlaying)
{
frameTick();
}
auto frame = spriteAtlas->frame(currentFrame, dir);
spriteAtlas->bindFrame(&frame);
spriteAtlas->draw();
}
void Animation::frameTick()
{
Uint32 currentTime = SDL_GetTicks();
elapsedTime = currentTime - lastFrameTick;
if (elapsedTime >= 1000.0f / FPS)
{
if (++currentFrame > spriteAtlas->size() - 1)
{
currentFrame = 0;
cycles += 1;
}
lastFrameTick = currentTime;
}
}
AnimationSet::AnimationSet(const int& entityid) : entityid(entityid), isDirectional(false) {};
AnimationSet::AnimationSet(const int& entityid, ResourceManager* resourceManager, std::vector<std::shared_ptr<AnimationData>> animSet) : entityid(entityid)
{
for (const auto& anim : animSet)
{
anims.try_emplace(anim->type, std::make_shared<Animation>(anim, resourceManager));
if (anim->type == "idle")
curAnim = anims[anim->type];
}
// if we don't have an idle animation, we are just gonna set the current animation to the top of the set
if (curAnim == nullptr)
curAnim = anims[animSet[0]->type];
isDirectional = curAnim->getDirectional();
}
AnimationSet::AnimationSet(const int& entityid, const std::unordered_map<std::string, std::shared_ptr<Animation>>& animations) : entityid(entityid), anims(animations)
{
for (const auto& [type, anim] : animations)
{
curAnim = anim;
if (type == "idle")
{
break;
}
}
isDirectional = curAnim->getDirectional();
}
void AnimationSet::attachEventManager(const std::shared_ptr<EventManager>& e)
{
eventManager = e;
eventManager->subscribe("OnDirectionChange", [this](std::shared_ptr<Event> e) {
auto directionEvent = std::static_pointer_cast<DirectionChangeEvent>(e);
if (directionEvent->entityid == entityid)
setFacingDir(directionEvent->direction);
});
eventManager->subscribe("OnEntityMove", [this](std::shared_ptr<Event> e) {
auto moveEvent = std::static_pointer_cast<EntityMoveEvent>(e);
if (moveEvent->entityid == entityid)
{
if (isDirectional)
{
if (anims["move"] != NULL)
curAnim = anims["move"];
}
else
play();
}
});
eventManager->subscribe("OnEntityStop", [this](std::shared_ptr<Event> e) {
auto stopEvent = std::static_pointer_cast<EntityStopEvent>(e);
if (stopEvent->entityid == entityid)
{
if (isDirectional)
{
if (anims["idle"] != NULL)
curAnim = anims["idle"];
}
else
stop();
}
});
eventManager->subscribe("OnEntityReload", [this](std::shared_ptr<Event> e) {
auto reloadEvent = std::static_pointer_cast<EntityReloadEvent>(e);
if (reloadEvent->entityid == entityid)
{
if (anims["reload"] != NULL)
curAnim = anims["reload"];
}
});
eventManager->subscribe("OnEntityFinishReload", [this](std::shared_ptr<Event> e) {
auto reloadEvent = std::static_pointer_cast<EntityFinishReloadEvent>(e);
if (reloadEvent->entityid == entityid)
{
if (anims["idle"] != NULL)
curAnim = anims["idle"];
}
});
}
void AnimationSet::bind()
{
curAnim->bind();
}
void AnimationSet::draw()
{
int lastCycle = curAnim->getCycles();
if (curAnim->getDirectional())
curAnim->draw(facing);
else
curAnim->draw();
// Sending animation events
if (eventManager)
{
// If the animation has cycled, we send this event after every cycle
if ((curAnim->getCycles() - lastCycle) > 0)
eventManager->notify(std::make_shared<AnimationFinishedEvent>(entityid, curAnim->getType()));
}
}

View file

@ -59,16 +59,21 @@ SpriteStatic::~SpriteStatic()
glDeleteVertexArrays(1, &VAO); glDeleteVertexArrays(1, &VAO);
} }
SpriteAnimated::SpriteAnimated(const char* textureAtlasPath, float frameSize) SpriteAtlas::SpriteAtlas(const char* textureAtlasPath, float frameSize, bool isDirectional) : isDirectional(isDirectional), curFrame(nullptr)
{ {
EBO = 0; EBO = 0;
texture = new Texture(); texture = new Texture();
texture->loadTexture(textureAtlasPath); texture->loadTexture(textureAtlasPath);
if (texture) if (texture)
SetupAnimated(frameSize); {
if (!isDirectional)
Setup(frameSize);
else
SetupDirectional(frameSize);
}
} }
void SpriteAnimated::SetupAnimated(float frameSize) void SpriteAtlas::Setup(float frameSize)
{ {
int width = texture->getWidth(); int width = texture->getWidth();
int height = texture->getHeight(); int height = texture->getHeight();
@ -110,57 +115,44 @@ void SpriteAnimated::SetupAnimated(float frameSize)
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0); glBindVertexArray(0);
ids.push_back({ VAO, VBO }); singleIDs.push_back({ VAO, VBO });
} }
} }
} }
void SpriteAnimated::draw() void SpriteAtlas::bindFrame(VertexIDs* f)
{ {
if (isPlaying) curFrame = f;
{
Uint32 currentTime = SDL_GetTicks();
if (currentTime - lastFrameTick >= 1000.f / FPS)
{
if (++currentFrame > ids.size() - 1)
currentFrame = 0;
lastFrameTick = currentTime;
}
glBindVertexArray(ids[currentFrame].VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
else
{
glBindVertexArray(ids[0].VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
} }
void SpriteAnimated::play() { isPlaying = true; } void SpriteAtlas::draw()
void SpriteAnimated::idle() { isPlaying = false; } {
glBindVertexArray(curFrame->VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
SpriteAnimated::~SpriteAnimated() SpriteAtlas::~SpriteAtlas()
{ {
delete texture; delete texture;
glDeleteBuffers(1, &EBO); glDeleteBuffers(1, &EBO);
for (VertexIDs idSet : ids) for (VertexIDs idSet : singleIDs)
{ {
glDeleteBuffers(1, &idSet.VBO); glDeleteBuffers(1, &idSet.VBO);
glDeleteVertexArrays(1, &idSet.VAO); glDeleteVertexArrays(1, &idSet.VAO);
} }
auto deleteBufferList = [](std::pair <Direction, std::vector<VertexIDs>> items) {
for (VertexIDs v : items.second) {
glDeleteBuffers(1, &v.VBO);
glDeleteVertexArrays(1, &v.VAO);
}
};
std::for_each(directionalIDs.begin(), directionalIDs.end(), deleteBufferList);
singleIDs.clear();
directionalIDs.clear();
} }
SpriteDirectionalAnimated::SpriteDirectionalAnimated(const char* textureAtlas, float frameSize) void SpriteAtlas::SetupDirectional(float frameSize)
{
texture = new Texture();
texture->loadTexture(textureAtlas);
if (texture)
Setup(frameSize);
}
void SpriteDirectionalAnimated::Setup(float frameSize)
{ {
int width = texture->getWidth(); int width = texture->getWidth();
int height = texture->getHeight(); int height = texture->getHeight();
@ -174,10 +166,10 @@ void SpriteDirectionalAnimated::Setup(float frameSize)
for (int col = 0; col < frameCols; col++) for (int col = 0; col < frameCols; col++)
{ {
Direction dir; Direction dir;
// top of the spritesheet is the entities idle directions // Directional spritesheets are stored, with an animation for each row,
// then each row is a 4 frame walk cycle of each direction // on the first row we are looking down, then right etc...
if (row == 0) dir = (Direction)(col + 1); // refer to utility/direction.h to each index
else dir = (Direction)(row); dir = (Direction)(row + 1); // note we add one, since dir: 0 is None, this is for non-directional sprites
float left = (col) * (frameSize / width); float left = (col) * (frameSize / width);
float right = (col + 1) * (frameSize / width); float right = (col + 1) * (frameSize / width);
@ -209,56 +201,7 @@ void SpriteDirectionalAnimated::Setup(float frameSize)
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0); glBindVertexArray(0);
if (row == 0) directionalIDs[dir].push_back({ VAO, VBO });
idleIDs.emplace(std::pair<Direction, VertexIDs>(dir, { VAO, VBO }));
else
walkingIDs[dir].push_back({ VAO, VBO });
} }
} }
} }
void SpriteDirectionalAnimated::draw()
{
if (isPlaying)
{
Uint32 currentTime = SDL_GetTicks();
if (currentTime - lastFrameTick >= 1000.f / FPS)
{
if (++currentFrame > walkingIDs[direction].size() - 1)
currentFrame = 0;
lastFrameTick = currentTime;
}
glBindVertexArray(walkingIDs[direction][currentFrame].VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
else
{
glBindVertexArray(idleIDs[direction].VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
}
void SpriteDirectionalAnimated::play() { isPlaying = true; }
void SpriteDirectionalAnimated::idle() { isPlaying = false; }
SpriteDirectionalAnimated::~SpriteDirectionalAnimated()
{
delete texture;
glDeleteBuffers(1, &EBO);
auto deleteBuffers = [](std::pair<Direction, VertexIDs> item) {
glDeleteBuffers(1, &item.second.VBO);
glDeleteVertexArrays(1, &item.second.VAO);
};
auto deleteBufferList = [](std::pair <Direction, std::vector<VertexIDs>> items) {
for (VertexIDs v : items.second) {
glDeleteBuffers(1, &v.VBO);
glDeleteVertexArrays(1, &v.VAO);
}
};
std::for_each(idleIDs.begin(), idleIDs.end(), deleteBuffers);
std::for_each(walkingIDs.begin(), walkingIDs.end(), deleteBufferList);
idleIDs.clear();
walkingIDs.clear();
}

View file

@ -5,23 +5,19 @@
#include "utility/script.h" #include "utility/script.h"
#include "graphics/tiletype.h" #include "graphics/tiletype.h"
#include "graphics/shader.h" #include "graphics/shader.h"
#include "graphics/animation.h"
#include "gameplay/weapons/weapons.h" #include "gameplay/weapons/weapons.h"
std::shared_ptr<Sprite> ResourceManager::loadSpriteAnimated(const std::string& path, float frameSize) std::shared_ptr<SpriteAtlas> ResourceManager::loadSpriteAtlas(const std::string& path, float frameSize, bool isDirectional)
{ {
auto iterator = sprites.find(path); auto iterator = sprites.find(path);
if (iterator != sprites.end()) if (iterator != sprites.end())
return iterator->second; return std::dynamic_pointer_cast<SpriteAtlas>(iterator->second);
auto sprite = std::make_shared<SpriteAnimated>(path.c_str(), frameSize); auto sprite = std::make_shared<SpriteAtlas>(path.c_str(), frameSize, isDirectional);
sprites[path] = sprite; sprites[path] = sprite;
return sprite; return sprite;
} }
std::shared_ptr<Sprite> ResourceManager::loadSpriteDirAnimated(const std::string& path, float frameSize)
{
return std::make_shared<SpriteDirectionalAnimated>(path.c_str(), frameSize);
}
std::shared_ptr<Sprite> ResourceManager::loadSpriteStatic(const std::string& path) std::shared_ptr<Sprite> ResourceManager::loadSpriteStatic(const std::string& path)
{ {
auto iterator = sprites.find(path); auto iterator = sprites.find(path);
@ -78,6 +74,12 @@ std::shared_ptr<SceneData> ResourceManager::loadScene(const std::string& id)
return xmlLoader->getSceneData(id); return xmlLoader->getSceneData(id);
} }
std::shared_ptr<AnimationSet> ResourceManager::loadAnimationSet(const std::string& name, int entityid)
{
auto animSetData = xmlLoader->getAnimationSet(name);
return std::make_shared<AnimationSet>(entityid, this, animSetData);
}
// this will need some work, but for now where there is only one weapon type we can work with it // this will need some work, but for now where there is only one weapon type we can work with it
// TODO: Allow different weapon types! // TODO: Allow different weapon types!
/* /*

View file

@ -127,50 +127,72 @@ bool XMLLoader::loadEntityData(const char* xmlFile, SceneData* out)
tinyxml2::XMLElement* playerElement = entities->FirstChildElement("player"); tinyxml2::XMLElement* playerElement = entities->FirstChildElement("player");
if (playerElement == NULL) if (playerElement == NULL)
return false; return false;
int x, y;
float frameSize = 64.f; EntityData playData;
bool isDirectional = false; const char *graphic, *weaponName = "pistolGun";
const char* spritePath, * weaponName = "pistolGun";
if (playerElement->QueryIntAttribute("x", &x) != tinyxml2::XML_SUCCESS || if (playerElement->QueryIntAttribute("x", &playData.x) != tinyxml2::XML_SUCCESS ||
playerElement->QueryIntAttribute("y", &y) != tinyxml2::XML_SUCCESS) playerElement->QueryIntAttribute("y", &playData.y) != tinyxml2::XML_SUCCESS)
return false; return false;
playerElement->QueryStringAttribute("weapon", &weaponName); playerElement->QueryStringAttribute("weapon", &weaponName);
tinyxml2::XMLElement* anim = playerElement->FirstChildElement("animation");
if (anim == NULL)
{
playData.animated = false;
tinyxml2::XMLElement* sprite = playerElement->FirstChildElement("sprite"); tinyxml2::XMLElement* sprite = playerElement->FirstChildElement("sprite");
if (sprite == NULL) if (sprite == NULL)
return false; return false;
if (sprite->QueryStringAttribute("file", &spritePath) != tinyxml2::XML_SUCCESS) if (sprite->QueryStringAttribute("file", &graphic) != tinyxml2::XML_SUCCESS)
return false; return false;
sprite->QueryFloatAttribute("framesize", &frameSize); }
sprite->QueryBoolAttribute("directional", &isDirectional); else
out->entities.push_back({ true, x, y, spritePath, frameSize, weaponName, "", isDirectional}); {
playData.animated = true;
if (anim->QueryStringAttribute("name", &graphic) != tinyxml2::XML_SUCCESS)
return false;
}
playData.isPlayer = true;
playData.graphic = graphic;
playData.weapon = weaponName;
out->entities.push_back(playData);
// Adding every other entity... // Adding every other entity...
// TODO: Add npcs to game and enable their use with XMLLoader // TODO: Add npcs to game and enable their use with XMLLoader
for (tinyxml2::XMLElement* e = entities->FirstChildElement("entity"); e != NULL; e = e->NextSiblingElement("entity")) for (tinyxml2::XMLElement* e = entities->FirstChildElement("entity"); e != NULL; e = e->NextSiblingElement("entity"))
{ {
int x, y;
float frameSize = 64.f; EntityData data;
const char* spritePath, * weaponName = "pistolGun", * scriptPath; const char *graphic, *weaponName = "pistolGun", *scriptPath;
bool isDirectional = false; if (e->QueryIntAttribute("x", &data.x) != tinyxml2::XML_SUCCESS ||
if (e->QueryIntAttribute("x", &x) != tinyxml2::XML_SUCCESS || e->QueryIntAttribute("y", &data.y) != tinyxml2::XML_SUCCESS)
e->QueryIntAttribute("y", &y) != tinyxml2::XML_SUCCESS)
return false; return false;
e->QueryStringAttribute("weapon", &weaponName); e->QueryStringAttribute("weapon", &weaponName);
tinyxml2::XMLElement* anim = e->FirstChildElement("animation");
if (anim == NULL)
{
data.animated = false;
tinyxml2::XMLElement* sprite = e->FirstChildElement("sprite"); tinyxml2::XMLElement* sprite = e->FirstChildElement("sprite");
if (sprite == NULL) if (sprite == NULL)
return false; continue;
if (sprite->QueryStringAttribute("file", &spritePath) != tinyxml2::XML_SUCCESS) if (sprite->QueryStringAttribute("file", &graphic) != tinyxml2::XML_SUCCESS)
return false;
sprite->QueryFloatAttribute("frameSize", &frameSize);
sprite->QueryBoolAttribute("directional", &isDirectional);
tinyxml2::XMLElement* script = e->FirstChildElement("script");
if (script == NULL || script->QueryStringAttribute("file", &scriptPath))
{
out->entities.push_back({ false, x, y, spritePath, frameSize, weaponName, "", isDirectional });
continue; continue;
} }
out->entities.push_back({ false, x, y, spritePath, frameSize, weaponName, scriptPath, isDirectional }); else
{
data.animated = true;
if (anim->QueryStringAttribute("name", &graphic) != tinyxml2::XML_SUCCESS)
continue;
}
tinyxml2::XMLElement* script = e->FirstChildElement("script");
script->QueryStringAttribute("file", &scriptPath);
data.isPlayer = false;
data.graphic = graphic;
data.script = scriptPath;
data.weapon = weaponName;
out->entities.push_back(data);
} }
return true; return true;
@ -207,11 +229,13 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
{ {
// populate weapon data into this temp buffer, then push it into our list of weapons // populate weapon data into this temp buffer, then push it into our list of weapons
WeaponData data; WeaponData data;
const char* name, * sprite, * bulletSprite, * script; const char *name, *graphic, *bulletGraphic, *script;
// Getting top level weapon data, attribs in the weapon Node // Getting top level weapon data, attribs in the weapon Node
if (weapon->QueryStringAttribute("name", &name) != tinyxml2::XML_SUCCESS) if (weapon->QueryStringAttribute("name", &name) != tinyxml2::XML_SUCCESS)
continue; continue;
weapon->QueryFloatAttribute("fireSpeed", &data.fireSpeed); weapon->QueryFloatAttribute("fireSpeed", &data.fireSpeed);
weapon->QueryIntAttribute("maxAmmo", &data.maxAmmo);
weapon->QueryIntAttribute("clipSize", &data.clipSize);
// Getting script file if Node exists // Getting script file if Node exists
tinyxml2::XMLElement* wepScript = weapon->FirstChildElement("script"); tinyxml2::XMLElement* wepScript = weapon->FirstChildElement("script");
if (wepScript) if (wepScript)
@ -219,13 +243,31 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
else else
script = ""; script = "";
// Getting weapon sprite information, held in the sprite node // Getting weapon sprite information, held in the sprite node
tinyxml2::XMLElement* wepSprite = weapon->FirstChildElement("sprite"); tinyxml2::XMLElement* anim = weapon->FirstChildElement("animation");
if (wepSprite) if (anim)
{ {
wepSprite->QueryStringAttribute("file", &sprite); data.animated = true;
wepSprite->QueryBoolAttribute("animated", &data.animated); if (anim->QueryStringAttribute("name", &graphic) != tinyxml2::XML_SUCCESS)
if (data.animated) continue;
wepSprite->QueryFloatAttribute("frameSize", &data.frameSize);
if (anim->ChildElementCount() != 0)
{
tinyxml2::XMLElement* size = anim->FirstChildElement("size");
size->QueryFloatAttribute("x", &data.sizeX);
size->QueryFloatAttribute("y", &data.sizeY);
tinyxml2::XMLElement* offset = anim->FirstChildElement("offset");
offset->QueryFloatAttribute("x", &data.offsetX);
offset->QueryFloatAttribute("y", &data.offsetY);
}
}
else
{
tinyxml2::XMLElement* wepSprite = weapon->FirstChildElement("sprite");
if (wepSprite == NULL)
continue;
wepSprite->QueryStringAttribute("file", &graphic);
data.animated = false;
if (wepSprite->ChildElementCount() != 0) if (wepSprite->ChildElementCount() != 0)
{ {
tinyxml2::XMLElement* size = wepSprite->FirstChildElement("size"); tinyxml2::XMLElement* size = wepSprite->FirstChildElement("size");
@ -236,31 +278,32 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
offset->QueryFloatAttribute("y", &data.offsetY); offset->QueryFloatAttribute("y", &data.offsetY);
} }
} }
else {
// If we don't have a sprite element, default to pistol
sprite = "sprites/pistolAtlas256.png";
data.animated = true;
data.frameSize = 256.f;
data.sizeX = 30.f;
data.sizeY = 30.f;
}
// getting bullet information held in the bullet node and each child node held therein // getting bullet information held in the bullet node and each child node held therein
tinyxml2::XMLElement* bullet = weapon->FirstChildElement("bullet"); tinyxml2::XMLElement* bullet = weapon->FirstChildElement("bullet");
if (bullet->QueryStringAttribute("sprite", &bulletSprite) != tinyxml2::XML_SUCCESS || if (bullet->FindAttribute("anim"))
bullet->QueryBoolAttribute( "animated", &data.bulletAnimated) != tinyxml2::XML_SUCCESS || {
bullet->FirstChildElement("size")->QueryFloatAttribute("x", &data.bulletSizeX) != tinyxml2::XML_SUCCESS || data.bulletAnimated = true;
if (bullet->QueryStringAttribute("anim", &bulletGraphic) != tinyxml2::XML_SUCCESS)
continue;
}
else
{
data.bulletAnimated = false;
if (bullet->QueryStringAttribute("sprite", &bulletGraphic) != tinyxml2::XML_SUCCESS)
continue;
}
if (bullet->FirstChildElement("size")->QueryFloatAttribute("x", &data.bulletSizeX) != tinyxml2::XML_SUCCESS ||
bullet->FirstChildElement("size")->QueryFloatAttribute("y", &data.bulletSizeY) != tinyxml2::XML_SUCCESS) bullet->FirstChildElement("size")->QueryFloatAttribute("y", &data.bulletSizeY) != tinyxml2::XML_SUCCESS)
continue; continue;
data.name = name; data.name = name;
data.script = script; data.script = script;
data.sprite = sprite; data.graphic = graphic;
data.bulletSprite = bulletSprite; data.bulletGraphic = bulletGraphic;
if (data.animated)
if (bullet->QueryFloatAttribute("frameSize", &data.bulletFrameSize) != tinyxml2::XML_SUCCESS)
continue;
tinyxml2::XMLElement* spread = bullet->FirstChildElement("spread"); tinyxml2::XMLElement* spread = bullet->FirstChildElement("spread");
tinyxml2::XMLElement* speed = bullet->FirstChildElement("speed"); tinyxml2::XMLElement* speed = bullet->FirstChildElement("speed");
tinyxml2::XMLElement* drop = bullet->FirstChildElement("drop"); tinyxml2::XMLElement* drop = bullet->FirstChildElement("drop");
@ -282,3 +325,47 @@ bool XMLLoader::loadWeapons(const char* weaponFolder)
} }
return (!weaponData.empty()); return (!weaponData.empty());
} }
bool XMLLoader::loadAnimations(const char* animationFolder)
{
std::filesystem::path folder(animationFolder);
if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder))
return false;
for (auto& file : std::filesystem::directory_iterator(folder))
{
if (!file.path().has_extension() || !file.path().has_filename() || !file.exists() || file.is_directory())
continue;
tinyxml2::XMLDocument doc;
if (doc.LoadFile(file.path().generic_string().c_str()) != tinyxml2::XML_SUCCESS)
continue;
tinyxml2::XMLElement* anims = doc.FirstChildElement("animations");
bool directional = false;
anims->QueryBoolAttribute("directional", &directional);
if (anims == NULL)
continue;
for (tinyxml2::XMLElement* e = anims->FirstChildElement("animation"); e != NULL; e = e->NextSiblingElement("animation"))
{
AnimationData animData;
const char *name, *type, *spriteAtlas;
if (e->QueryStringAttribute("name", &name) != tinyxml2::XML_SUCCESS ||
e->QueryStringAttribute("type", &type) != tinyxml2::XML_SUCCESS)
continue;
tinyxml2::XMLElement* fps = e->FirstChildElement("FPS");
fps->QueryFloatText(&animData.FPS);
tinyxml2::XMLElement* sprite = e->FirstChildElement("sprite");
if (sprite->QueryStringAttribute("path", &spriteAtlas) != tinyxml2::XML_SUCCESS ||
sprite->QueryFloatAttribute("frameSize", &animData.frameSize) != tinyxml2::XML_SUCCESS)
continue;
animData.name = name;
animData.type = type;
animData.spriteAtlas = spriteAtlas;
animData.animSet = file.path().stem().string();
animData.directional = directional;
animations.try_emplace(animData.name, std::make_shared<AnimationData>(animData));
}
}
return true;
}