I'm attempting to write an animation system on top of SFML, and I've come up with a design where I can wrap sf::Drawable objects into a RenderComponent class, and the RenderComponent class can be given an Animation object at any time, which will overwrite a previous animation if one exists. Here is what I'm looking for.
- Am I using the std::unique_ptr correctly/optimally?
- Should I be using a pointer to store the Animation?
- Is my method of settings the animation (with a variadic template) too complicated, and is there a better way?
- I would normally separate the code into header and implementation, but for the brevity, I am uploading it in pure headers. Ignore that please.
- Any general advice.
Here is the code:
Animation base class
class Animation {
typedef std::chrono::high_resolution_clock hrc;
private:
std::chrono::time_point<hrc> m_now;
protected:
unsigned int m_us; // us for microseconds
unsigned int m_endUs;
void UpdateTime() {
auto end = hrc::now();
auto diff = end - m_now;
m_now = end;
auto msDuration = std::chrono::duration_cast<std::chrono::microseconds>(diff);
m_us += (unsigned int)msDuration.count();
}
public:
Animation() {
m_us = 0;
m_now = hrc::now();
}
bool finished() {
return m_endUs <= m_us;
}
virtual bool Update(sf::Sprite& spr) = 0;
};
Animation child class
class FadeIn : public Animation {
public:
FadeIn(int ms) {
m_endUs = ms * 1000;
}
// Updates the sprite based on the timeline, and returns if the animation is over
virtual bool Update(sf::Sprite& spr) {
UpdateTime();
if (finished()) return true;
sf::Color color = spr.getColor();
color.a = (int)((float)m_us / m_endUs * 255);
spr.setColor(color);
return false;
}
};
Render Component
class RenderComponent {
private:
sf::Texture m_texDefault;
std::unique_ptr<Animation> m_animationPtr;
public:
RenderComponent() { }
RenderComponent(sf::Drawable* element, sf::Vector2u size) {
sf::RenderTexture rt;
rt.create((unsigned int)size.x, (unsigned int)size.y);
rt.draw(*element);
m_texDefault = rt.getTexture();
}
template <typename T, typename... Args>
void SetAnimation(Args... args) {
m_animationPtr = std::make_unique<T>(args...);
}
void draw(sf::RenderTarget* target) {
sf::Sprite sprite;
sprite.setTexture(m_texDefault);
// Handle animation and set pointer to null if done
if (m_animationPtr) {
if (m_animationPtr.get()->Update(sprite)) {
m_animationPtr = nullptr;
}
sf::Color c = sprite.getColor();
}
target->draw(sprite);
}
};
A helper function
sf::Vector2u floatRectToVec2u(sf::FloatRect r) {
sf::Vector2u vec;
vec.x = (unsigned int)ceil(r.width);
vec.y = (unsigned int)ceil(r.height);
return vec;
auto start = std::chrono::high_resolution_clock::now();
}
Main function
int main()
{
sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
sf::CircleShape shape(100.f);
shape.setFillColor(sf::Color::Green);
RenderComponent circle(&shape, floatRectToVec2u(shape.getGlobalBounds()));
circle.SetAnimation<FadeIn>(1000);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
circle.draw(&window);
window.display();
}
return 0;
}