initial commit
This commit is contained in:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal 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
270
animatedsprite.cpp
Normal 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
65
animatedsprite.hpp
Normal 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
6
test/makefile
Normal 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
105
test/test.cpp
Normal 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
BIN
test/test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 289 B |
73
test/testanimations.txt
Normal file
73
test/testanimations.txt
Normal 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
|
||||||
Reference in New Issue
Block a user