commit 50fa7217abf92fd4d2b7d1fd67f41838bb5ca433 Author: Tijmen van Nesselrooij Date: Tue Feb 19 21:24:56 2019 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8604b38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +.vscode/ diff --git a/animatedsprite.cpp b/animatedsprite.cpp new file mode 100644 index 0000000..bf84071 --- /dev/null +++ b/animatedsprite.cpp @@ -0,0 +1,270 @@ +#include "animatedsprite.hpp" +#include +#include +#include +#include + +namespace Animation +{ + sf::IntRect Animation::GetFrame(unsigned frameNumber, bool const bounced) const + { + assert(frameNumber < frameCount); + + if(reverse != bounced) + frameNumber = frameCount - (frameNumber + 1); + unsigned const x = frameOrigin.x + (frameNumber * frameMargin.x) + (frameNumber * frameStep.x); + unsigned const y = frameOrigin.y + (frameNumber * frameMargin.y) + (frameNumber * frameStep.y); + + return sf::IntRect(x, y, frameSize.x, frameSize.y); + } + + Animation::Animation() + : frameOrigin(0, 0), + frameSize(0, 0), + frameStep(0, 0), + frameMargin(0, 0), + frameCount(0), + frameTime(0), + reverse(false), + loop(false), + bounce(false) + {} + + std::unordered_map LoadFromFile(std::string const & filepath) + { + std::array const properties = + { + "frame-origin", + "frame-size", + "frame-step", + "frame-margin", + "frame-count", + "frame-time", + "reverse", + "loop", + "bounce" + }; + + std::unordered_map animations; + + std::ifstream file(filepath); + if(!file.is_open()) + return animations; + + Animation candidate; + std::string candidateName; + while(file.good()) + { + std::string lineRaw; + std::getline(file, lineRaw); + if(lineRaw.size() == 0 || lineRaw[0] == '#') + continue; + + std::stringstream line(lineRaw); + std::string key; + line >> key; + + if(lineRaw[0] == '\t' && candidateName.size() > 0) + { + for(std::size_t i = 0; i < properties.size(); ++i) + { + if(properties[i].compare(key) == 0) + { + switch(i) + { + case 0: + line >> candidate.frameOrigin.x; + line >> candidate.frameOrigin.y; + break; + + case 1: + line >> candidate.frameSize.x; + line >> candidate.frameSize.y; + break; + + case 2: + line >> candidate.frameStep.x; + line >> candidate.frameStep.y; + break; + + case 3: + line >> candidate.frameMargin.x; + line >> candidate.frameMargin.y; + break; + + case 4: + line >> candidate.frameCount; + break; + + case 5: + line >> candidate.frameTime; + break; + + case 6: + candidate.reverse = true; + break; + + case 7: + candidate.loop = true; + break; + + case 8: + candidate.loop = true; + candidate.bounce = true; + break; + } + break; + } + } + } + else if(key.compare("animation") == 0) + { + if(candidateName.size() > 0) + { + animations[candidateName] = candidate; + } + candidate = Animation(); + line >> candidateName; + } + else + { + if(candidateName.size() > 0) + { + animations[candidateName] = candidate; + } + candidateName.resize(0); + } + } + file.close(); + + if(candidateName.size() > 0) + { + animations[candidateName] = candidate; + candidateName.resize(0); + } + + return animations; + } + + void Sprite::SetFrame() + { + assert(currentAnimation != nullptr); + + sf::IntRect textureRect = currentAnimation->GetFrame(currentFrame, bouncing); + if(isFlippedHorizontally) + { + textureRect.left += textureRect.width; + textureRect.width *= -1; + } + if(isFlippedVertically) + { + textureRect.top += textureRect.height; + textureRect.height *= -1; + } + setTextureRect(textureRect); + } + + bool Sprite::IsAnimationFinished() const + { + assert(currentAnimation != nullptr); + return currentFrame == (currentAnimation->frameCount - 1) && !currentAnimation->loop; + } + + void Sprite::SetHorizontalFlip(const bool v) + { + if(v != isFlippedHorizontally) + { + isFlippedHorizontally = v; + SetFrame(); + } + } + + void Sprite::SetVerticalFlip(const bool v) + { + if(v != isFlippedVertically) + { + isFlippedVertically = v; + SetFrame(); + } + } + + bool Sprite::IsFlippedHorizontally() const + { + return isFlippedHorizontally; + } + + bool Sprite::IsFlippedVertically() const + { + return isFlippedVertically; + } + + void Sprite::ResetAnimation() + { + currentFrame = 0U; + currentFrameTime = 0U; + bouncing = false; + SetFrame(); + } + + bool Sprite::SetAnimation(const std::string & name) + { + auto const candidate = animations.find(name); + if(candidate == animations.cend()) + { + return false; + } + + currentAnimation = &(candidate->second); + ResetAnimation(); + + return true; + } + + void Sprite::Update(const sf::Time & elapsed) + { + assert(currentAnimation != nullptr); + + if(IsAnimationFinished()) + { + return; + } + + currentFrameTime += elapsed.asMilliseconds(); + if(currentFrameTime < currentAnimation->frameTime) + { + return; + } + unsigned const steps = currentFrameTime / currentAnimation->frameTime; + currentFrameTime -= currentAnimation->frameTime * steps; + + currentFrame += steps; + if(currentFrame >= currentAnimation->frameCount) + { + currentFrame = currentFrame % currentAnimation->frameCount; + if(currentAnimation->bounce) + bouncing = !bouncing; + } + + SetFrame(); + } + + void Sprite::UpdateFromOther(const Sprite & parent) + { + if(currentFrame != parent.currentFrame) + { + currentFrame = parent.currentFrame; + bouncing = parent.bouncing; + SetFrame(); + } + } + + Sprite::Sprite(std::unordered_map const & _animations, + std::string const & animation, + sf::Texture const & texture) + : sf::Sprite(texture), + animations(_animations), + currentAnimation(nullptr) + { + SetAnimation(animation); + } +} diff --git a/animatedsprite.hpp b/animatedsprite.hpp new file mode 100644 index 0000000..7f7d2d4 --- /dev/null +++ b/animatedsprite.hpp @@ -0,0 +1,65 @@ +#pragma once +#include +#include +#include +#include + +namespace Animation +{ + struct Animation + { + sf::Vector2u frameOrigin; // offset in pixels + sf::Vector2u frameSize; // in pixels + sf::Vector2u frameStep; // in pixels + sf::Vector2u frameMargin; // in pixels + unsigned frameCount; + unsigned frameTime; // in milliseconds + bool reverse; + bool loop; + bool bounce; + + sf::IntRect GetFrame(unsigned const frameNumber, bool const bounced = false) const; + + Animation(); + }; + + std::unordered_map LoadFromFile(std::string const & filepath); + + class Sprite : public sf::Sprite + { + protected: + std::unordered_map const & animations; + Animation const * currentAnimation; + + unsigned currentFrame; + unsigned currentFrameTime; + bool bouncing; + bool isFlippedHorizontally; + bool isFlippedVertically; + + void SetFrame(); + + public: + virtual bool IsAnimationFinished() const; + + // This flip call is not free, so be careful with lots + // of repeated calls to this function + virtual void SetHorizontalFlip(bool const v); + // This flip call is not free, so be careful with lots + // of repeated calls to this function + virtual void SetVerticalFlip(bool const v); + + bool IsFlippedHorizontally() const; + bool IsFlippedVertically() const; + + void ResetAnimation(); + bool SetAnimation(const std::string & name); + + virtual void Update(sf::Time const & elapsed); + void UpdateFromOther(Sprite const & other); + + Sprite(std::unordered_map const & _animations, + std::string const & animation, + sf::Texture const & texture); + }; +} diff --git a/test/makefile b/test/makefile new file mode 100644 index 0000000..a1d5746 --- /dev/null +++ b/test/makefile @@ -0,0 +1,6 @@ +test.out: test.cpp ../animatedsprite.cpp + g++ $^ -lsfml-graphics -lsfml-window -lsfml-system -o $@ + +.PHONY: check +check: test.out + ./test.out \ No newline at end of file diff --git a/test/test.cpp b/test/test.cpp new file mode 100644 index 0000000..4a0435f --- /dev/null +++ b/test/test.cpp @@ -0,0 +1,105 @@ +#include "../animatedsprite.hpp" +#include +#include + +int main() +{ + sf::RenderWindow window(sf::VideoMode(800, 600), "AnimatedSprite Test"); + window.setVerticalSyncEnabled(true); + + sf::Texture texture; + if(!texture.loadFromFile("test.png")) + { + std::puts("Failed to load texture."); + return 1; + } + + auto const animations = Animation::LoadFromFile("testanimations.txt"); + if(animations.size() == 0) + { + std::puts("Test animations definition file was empty or read incorrectly."); + return 1; + } + + Animation::Sprite sprite(animations, "default", texture); + std::puts("Use the numerical keys to cycle through animations (1-7)"); + + sf::Clock sfclock; + bool good = true; + while(good) + { + sf::Event e; + while(window.pollEvent(e)) + { + switch(e.type) + { + case sf::Event::Closed: + good = false; + break; + + case sf::Event::KeyReleased: + switch(e.key.code) + { + case sf::Keyboard::Escape: + good = false; + break; + + case sf::Keyboard::Up: + sprite.SetHorizontalFlip(!sprite.IsFlippedHorizontally()); + break; + + case sf::Keyboard::Down: + sprite.SetVerticalFlip(!sprite.IsFlippedVertically()); + break; + + case sf::Keyboard::R: + sprite.ResetAnimation(); + break; + + case sf::Keyboard::Num1: + sprite.SetAnimation("default"); + break; + + case sf::Keyboard::Num2: + sprite.SetAnimation("default-reverse"); + break; + + case sf::Keyboard::Num3: + sprite.SetAnimation("default-fast"); + break; + + case sf::Keyboard::Num4: + sprite.SetAnimation("carousel"); + break; + + case sf::Keyboard::Num5: + sprite.SetAnimation("carousel-margin"); + break; + + case sf::Keyboard::Num6: + sprite.SetAnimation("carousel-reverse"); + break; + + case sf::Keyboard::Num7: + sprite.SetAnimation("carousel-bounce"); + break; + + default: + break; + } + + default: + break; + } + } + + sprite.Update(sfclock.restart()); + + window.clear(); + window.draw(sprite); + window.display(); + } + + window.close(); + return 0; +} \ No newline at end of file diff --git a/test/test.png b/test/test.png new file mode 100644 index 0000000..e45f473 Binary files /dev/null and b/test/test.png differ diff --git a/test/testanimations.txt b/test/testanimations.txt new file mode 100644 index 0000000..7e774d6 --- /dev/null +++ b/test/testanimations.txt @@ -0,0 +1,73 @@ +# ALL NUMBERS ARE >= 0 !!! +# animation name +# frame-origin x y +# frame-size x y +# frame-step x y +# frame-margin x y +# frame-count n +# frame-time n +# reverse +# loop +# bounce + +animation default + frame-origin 0 15 + frame-size 16 32 + frame-step 16 0 + frame-margin 0 0 + frame-count 4 + frame-time 32 + loop + +animation default-reverse + frame-origin 0 15 + frame-size 16 32 + frame-step 16 0 + frame-margin 0 0 + frame-count 4 + frame-time 32 + loop + reverse + +animation default-fast + frame-origin 0 15 + frame-size 16 32 + frame-step 16 0 + frame-margin 0 0 + frame-count 4 + frame-time 16 + loop + +animation carousel + frame-origin 0 15 + frame-size 16 32 + frame-step 2 0 + frame-margin 0 0 + frame-count 25 + frame-time 16 + +animation carousel-reverse + frame-origin 0 15 + frame-size 16 32 + frame-step 2 0 + frame-margin 0 0 + frame-count 25 + frame-time 16 + reverse + +animation carousel-margin + frame-origin 0 15 + frame-size 16 32 + frame-step 1 0 + frame-margin 1 0 + frame-count 25 + frame-time 16 + +animation carousel-bounce + frame-origin 0 15 + frame-size 16 32 + frame-step 2 0 + frame-margin 0 0 + frame-count 25 + frame-time 32 + bounce \ No newline at end of file