Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: How to handle animations with multiple layers THE RIGHT WAY?  (Read 1505 times)

0 Members and 1 Guest are viewing this topic.

Glocke

  • Sr. Member
  • ****
  • Posts: 289
  • Hobby Dev/GameDev
    • View Profile
Hi, I'm going to refactor my animation system. That's why I'm here! My game has the following claims to my animation system:
  • Common frame animations (clipping rect, origin pos, durations, nothing special)
  • Multiple sprite layers: I separated my assets to multiple layers (BodyLegs, BodyTorso, ArmorLegs, ArmorTorso, Helmet, Weapon, Offhand). Each animation (e.g. Attack) has always the same number of frames and always the same duration per frame - nevertheless which layer. This will easly synchronize all layers.
  • I also seperated Moving from other Actions to allow some kind of "Run'n'Gun" - aka shoot the enemy while yourself is moving
  • Additionally, movement should be looped until it's stopped from outside. Actions should be animated only once.

Previously, I was using Thor's Animation handling, but those multiple layers forced me to use lots of animators for one single entity. Also, because I'm heavily focused on Data-oriented design, I'd like to animate my entities in a tight loop. So (at least in my opinion) Thor's Animation handling isn't what I'm looking for. Also, because I want to the same number of frames and duration across multiple layers, another solution might be more suitable.

Here is what my current approach looks like. The code is just typed in without smashing the compiler on it ;D So it might be broken. A word about utils::enum_map: I wrote that thin wrapper around std::array using some std::numeric_limits-magic in order to be easily able to organize enum-value-keyed stuff without using std::(unordered_)map. In fact, its API is working very similar and the example code should be very clear (at least I think :D )... So, here's my code:

// ----------------------------------------------------------------------------
// common.hpp

enum class Action {
        Idle=0, Use, Attack, Shoot, Cast, Die
};

enum class Layer {
        // Responsible for moving
        BodyLegs=0, ArmorLegs,
        // Responsible for actions
        BodyTorso, ArmorTorso, Helmet, Weapon, Offhand
};

struct Frame {
        sf::IntRect clipping;
        sf::Vector2f origin;
};

// ----------------------------------------------------------------------------
// graphics.hpp

struct RenderComponent {
        utils::enum_map<Layer, sf::Sprite> layers;
};

void apply_position(RenderComponent& data, sf::Vector2f const & pos) {
        // apply position to all layers
        for (auto& pair: data.layers) {
                pair.second.setPosition(pos);
        }
}

void apply_animation(RenderComponent& data, utils::enum_map<Layer, Frame> const & frames) {
        // apply animation to all layers
        for (auto const & pair: frames) {
                auto& sprite = data.layers[pair.first];
                sprite.setTextureRect(pair.second.clipping);
                sprite.setOrigin(pair.second.origin);
        }
}

void draw_sprite(RenderTarget& target, RenderComponent const & data) {
        // draw all layers
        for (auto const & pair: data.layers) {
                target.draw(pair.second);
        }
}

// ----------------------------------------------------------------------------
// animation.hpp

// hold a single frame and a duration for _all_ layers
struct AnimationFrame {
        utils::enum_map<Layer, Frame> frames;
        sf::Time duration;
};

// holds multiple frames
struct EntireAnimation {
        std::vector<AnimationFrame> frames;
};

// holds entire animation configuration: frames per action, frames for movement
struct SpriteAnimation {
        utils::enum_map<Action, EntireAnimation> actions;
        EntireAnimation move;
};

struct AnimationComponent {
        SpriteAnimation const * animation; // non-owning ptr, owned by external system
        bool move;
        Action action;
        std::size_t move_index, action_index; // current frame indices
        sf::Time move_time, action_time; // current frame times
};

void update(std::size_t& index, sf::Time& time, EntireAnimation& animation,
                bool loop, bool& updated, bool& finished) {
        // skip if frame over
        finished = false;
        updated = false;
        while (time >= animation.frames[index].duration) {
                time -= animation.frames[index].duration;
                ++index;
                updated = true;
                if (index >= animation.frames.size() && ) {
                        if (!loop) {
                                // animation finished
                                index = 0u;
                                time = sf::Time::Zero;
                                finished = true;
                                break;
                        } else {
                                // restart animation
                                index = 0u;
                        }
                }
        }
}

void animate(AnimationComponent& AnimationComponent, sf::Time elapsed) {
        if (AnimationComponent.animation == nullptr) {
                return;
        }
        auto const & ani = *(AnimationComponent.animation);
        bool move_updated, action_updated, finished;
        // animate movement if moving
        if (AnimationComponent.move) {
                AnimationComponent.move_time += elapsed;
                update(
                        AnimationComponent.move_index, AnimationComponent.move_time,
                        ani.move, false, move_updated, finished
                );
        }
        // animate any action
        AnimationComponent.action_time += elapsed;
        update(
                AnimationComponent.action_index, AnimationComponent.action_time,
                ani[AnimationComponent.action], true, action_udpated finished
        );
        if (finished) {
                // stop action
                AnimationComponent.action = Action::Idle;
        }
        if (move_updated || action_updated) {
                // apply animation to the corresponding graphics component
        }
}
 

I skipped the "lets-notify-the-graphics-system-about-the-changes"-part because it's out of scope here :) Can anyone recommend this or a different approach? Or does someone has an idea how to solve this with Thor? (I'm not completly sure whether Thor can help here or not).

This solution isn't perfect either: I smashed _all_ layers into one AnimationFrame, so moving is forced to have the same number of frames and duration as e.g. attacking (which is not necessary). But else this example code would be much larger :-\

Greetzs,
Glocke
Current project: Racod's Lair - Rogue-inspired Coop Action RPG

 

anything