initial commit

This commit is contained in:
2019-02-19 21:24:56 +01:00
commit 50fa7217ab
7 changed files with 553 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@@ -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/

270
animatedsprite.cpp Normal file
View File

@@ -0,0 +1,270 @@
#include "animatedsprite.hpp"
#include <array>
#include <cassert>
#include <fstream>
#include <sstream>
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<std::string, Animation> LoadFromFile(std::string const & filepath)
{
std::array<std::string, 9> const properties =
{
"frame-origin",
"frame-size",
"frame-step",
"frame-margin",
"frame-count",
"frame-time",
"reverse",
"loop",
"bounce"
};
std::unordered_map<std::string, Animation> 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<std::string, Animation> const & _animations,
std::string const & animation,
sf::Texture const & texture)
: sf::Sprite(texture),
animations(_animations),
currentAnimation(nullptr)
{
SetAnimation(animation);
}
}

65
animatedsprite.hpp Normal file
View File

@@ -0,0 +1,65 @@
#pragma once
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/System/Time.hpp>
#include <string>
#include <unordered_map>
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<std::string, Animation> LoadFromFile(std::string const & filepath);
class Sprite : public sf::Sprite
{
protected:
std::unordered_map<std::string, Animation> 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<std::string, Animation> const & _animations,
std::string const & animation,
sf::Texture const & texture);
};
}

6
test/makefile Normal file
View File

@@ -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

105
test/test.cpp Normal file
View File

@@ -0,0 +1,105 @@
#include "../animatedsprite.hpp"
#include <cstdio>
#include <SFML/Graphics.hpp>
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 <test.png> 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;
}

BIN
test/test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

73
test/testanimations.txt Normal file
View File

@@ -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