Kojirion

Home Projects Links About RSS

Move animation and sliding console

When developing with SFML, one will often wish to animate moving a shape or sprite from one point to another. Rather than hand-writing the code, the animation module of Thor library may be employed. A tutorial by the library’s author is available here - however, note that the following post makes use of the v2.1 API.

All it takes is to create a function object that will move/set the position of a sf::Transformable according to a float between 0 and 1 tracking the animation’s progress. In order to reuse the animation liberally, it is best that it contains no mutable state, hence a possible design:

struct MoveAnimation{
    MoveAnimation(const sf::Vector2f& from, const sf::Vector2f& to):
        from(from),
        direction(to-from)
    {

    }

    void operator()(sf::Transformable& animated, float progress){
        auto pos = from + progress*direction;
        animated.setPosition(pos);
    }

    const sf::Vector2f from;
    const sf::Vector2f direction;
};

This will simply linearly interpolate between the two points. The animation may then be set up as follows:

thor::AnimationMap<sf::Transformable, std::string> animationMap;
thor::Animator<sf::Transformable, std::string> animator(animationMap);

auto from = shape.getPosition();
auto to = sf::Vector2f(400.f, 400.f);

MoveAnimation move(from, to);
auto duration = sf::seconds(1.f);

animationMap.addAnimation("Move", move, duration);
animator.play() << "Move";

then applied in the ‘update’ section of the main loop:

auto frameTime = clock.restart();
animator.update(frameTime);
animator.animate(shape);

where shape is a sf::RectangleShape that will be moved between the two points. The full source is available here.

Generalizing

However, it will often be desirable to animate entities other than sf::Transformable, which may not feature a setPosition function. In order to accomplish this, the operator() may be templated and the setting of the new position delegated to a free function:

template <typename Animated>
void operator()(Animated& animated, float progress){
    auto pos = from + progress*direction;
    SetPosition(animated, pos);
}

This in turn is a template function, which may be specialized for sf::Transformable, as well as any other class:

template <class Item>
void SetPosition(Item&, const sf::Vector2f&);

template <>
void SetPosition(sf::Transformable& transformable, const sf::Vector2f& pos){
    transformable.setPosition(pos);
}

The animation may then be used in a manner exactly identical to the previous (source).

Sliding console

To animate a different entity, such as a SFGUI widget, it suffices to add a specialization of the SetPosition function.

template <>
void SetPosition(sfg::Widget::Ptr& widget, const sf::Vector2f& pos){
    widget->SetPosition(pos);
}

Then the animation could be used to animate moving widgets around, for example sliding in a console in from the top of the screen:

animation

This is achieved by setting up two animations to slide in and out:

thor::AnimationMap<sfg::Widget::Ptr, std::string> animationMap;
thor::Animator<sfg::Widget::Ptr, std::string> animator(animationMap);

auto height = console->GetAllocation().height;
sf::Vector2f initialPos(0.f, -height); //place just out of screen
sf::Vector2f activePos(0.f, 0.f);

MoveAnimation slideDown(initialPos, activePos),
              slideUp(activePos, initialPos);
auto duration = sf::seconds(0.5f);

animationMap.addAnimation("SlideDown", slideDown, duration);
animationMap.addAnimation("SlideUp", slideUp, duration);

The code to toggle the console could then look like this (using Thor.Input to handle the keypress - tutorial):

bool moving = false;

actionMap["ToggleConsole"] = thor::Action(sf::Keyboard::F2, thor::Action::PressOnce);
system.connect0("ToggleConsole", [&animator, console, &moving] {
    if (moving)
        return;
    if (console->IsLocallyVisible()){
        moving = true;
        animator.play() << "SlideUp" << thor::Playback::notify([console, &moving]{
            console->Show(false);
            moving = false;
        });
    }else{
        console->Show(true);
        moving = true;
        animator.play() << "SlideDown" << thor::Playback::notify([&moving]{
            moving = false;
        });
    }
});

In this example, the intention is to ignore the keypress if the console is in the process of sliding in or out, hence the moving flag to track this.
Furthermore, the console will be set to hidden when out of view and its visibility status is used to determine whether to slide it in or out. In order to update the visibility and the moving flag when the animations are finished, the callback mechanism offered by Thor.Animation is employed.

The full source code for this example is available here.