Skip to content

Animation SystemΒ #30

@LyeZinho

Description

@LyeZinho

🎬 Animation System

Fase: 4 β€” O CΓ©rebro
Namespace: Caffeine::Animation
Arquivo: src/animation/AnimationSystem.hpp
Status: πŸ“… Planejado
RF: RF4.9


VisΓ£o Geral

Sistema de animaΓ§Γ£o 2D com clipes de sprite e state machine. Cada entidade tem um componente Animator que gerencia a state machine de animaΓ§Γ£o.

Fase 5 adiciona skeletal animation (bones, skinning, blend trees) β€” ver docs/fase5/skeletal-animation.md.


API Planejada

namespace Caffeine::Animation {

// ============================================================================
// @brief  Clipe de animaΓ§Γ£o β€” sequΓͺncia de frames de sprite sheet.
// ============================================================================
struct AnimationClip {
    FixedString<32>     name;
    u32                 fps          = 12;
    std::vector<Rect2D> frames;       // regiΓ΅es na textura/atlas
    bool                loop         = true;
    f32 duration() const { return (f32)frames.size() / (f32)fps; }
};

// ============================================================================
// @brief  TransiΓ§Γ£o entre estados de animaΓ§Γ£o.
// ============================================================================
struct AnimationTransition {
    FixedString<32>       toState;
    std::function<bool()> condition;   // quando esta condiΓ§Γ£o Γ© verdade, transiciona
    f32                   blendTime   = 0.1f;  // segundos de crossfade
    bool                  hasExitTime = false;  // true = espera clip terminar
};

// ============================================================================
// @brief  Estado de animaΓ§Γ£o (nΓ³ na state machine).
// ============================================================================
struct AnimationState {
    FixedString<32>                  name;
    const AnimationClip*             clip        = nullptr;
    f32                              speed       = 1.0f;
    std::vector<AnimationTransition> transitions;
};

// ============================================================================
// @brief  Componente de animaΓ§Γ£o da entidade.
//
//  ContΓ©m a state machine e estado atual.
//  O AnimationSystem itera sobre todas as entidades com este componente.
// ============================================================================
struct Animator {
    HashMap<FixedString<32>, AnimationState> states;
    FixedString<32>  currentState;
    FixedString<32>  previousState;
    f32              timeInState    = 0.0f;
    f32              blendWeight    = 1.0f;   // para crossfade (0 β†’ 1)
    f32              playbackScale  = 1.0f;   // multiplicador de velocidade
    bool             paused         = false;

    // Eventos de animaΓ§Γ£o (frame especΓ­fico dispara callback)
    std::vector<std::pair<u32, FixedString<32>>> frameEvents;
    std::function<void(const FixedString<32>&)>  onFrameEvent;
};

// ============================================================================
// @brief  Sistema de animaΓ§Γ£o ECS.
//
//  Por frame:
//  1. Verifica condiΓ§Γ΅es de transiΓ§Γ£o
//  2. AvanΓ§a timeInState
//  3. Calcula frame atual do clipe
//  4. Atualiza Sprite.srcRect com o frame correto
//  5. Dispara frameEvents se frame atingido
// ============================================================================
class AnimationSystem : public ECS::ISystem {
public:
    void update(ECS::World& world, f64 dt) override;
    i32  priority() const override { return 200; }
    const char* name() const override { return "Animation"; }

    // API imperativa (ΓΊtil para gameplay code)
    void play(ECS::Entity e, const char* stateName,
              f32 blendTime = 0.1f);
    void pause(ECS::Entity e);
    void resume(ECS::Entity e);
    void setSpeed(ECS::Entity e, f32 speed);
    bool isPlaying(ECS::Entity e, const char* stateName) const;

private:
    void evaluateTransitions(ECS::Entity e, Animator& anim, f64 dt);
    void advanceFrame(ECS::Entity e, Animator& anim, Sprite& sprite, f64 dt);
    void checkFrameEvents(ECS::Entity e, Animator& anim);
};

}  // namespace Caffeine::Animation

State Machine Visual

           jump == true
idle ──────────────────► jump_rise
  β”‚                           β”‚
  │◄───────────────────────────  apex (vel.y < 0.1)
  β”‚     land                  β–Ό
  │◄──────────────── jump_fall
  β”‚
  β”‚  moveX != 0
  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ί walk
  β”‚
  β”‚  attack btn
  └────────────────► attack_1 ──► attack_2 (combo)

Exemplos de Uso

// ── Setup do Animator ─────────────────────────────────────────
Animator anim;

AnimationClip idleClip;
idleClip.name = "idle";
idleClip.fps  = 8;
idleClip.frames = { {0,0,64,64}, {64,0,64,64} };
idleClip.loop = true;

AnimationClip walkClip;
walkClip.name = "walk";
walkClip.fps  = 12;
walkClip.frames = { {128,0,64,64}, {192,0,64,64}, {256,0,64,64}, {320,0,64,64} };
walkClip.loop = true;

AnimationState idleState { "idle", &idleClip };
idleState.transitions.push_back({
    .toState   = "walk",
    .condition = [&]() { return input.axisValue(Axis::MoveX) != 0.0f; }
});

AnimationState walkState { "walk", &walkClip };
walkState.transitions.push_back({
    .toState   = "idle",
    .condition = [&]() { return input.axisValue(Axis::MoveX) == 0.0f; }
});

anim.states["idle"] = idleState;
anim.states["walk"] = walkState;
anim.currentState   = "idle";

world.add<Animator>(player, std::move(anim));

// ── Registrar sistema ─────────────────────────────────────────
world.registerSystem<AnimationSystem>();

// ── Controle programΓ‘tico ─────────────────────────────────────
auto* animSys = world.getSystem<AnimationSystem>();
animSys->play(player, "attack_1", 0.0f);  // sem blend (combos)

// ── Frame events ──────────────────────────────────────────────
world.get<Animator>(player)->frameEvents.push_back({3, "attack_hit"});
world.get<Animator>(player)->onFrameEvent = [](const FixedString<32>& evt) {
    if (evt == "attack_hit") damageEnemiesInRange();
};

DecisΓ΅es de Design

DecisΓ£o Justificativa
State machine LΓ³gica de animaΓ§Γ£o separada da lΓ³gica de gameplay
condition como lambda FlexΓ­vel sem overhead de parsing
blendTime por transiΓ§Γ£o Crossfade suave por padrΓ£o
Frame events Sincronizar gameplay com quadros especΓ­ficos da animaΓ§Γ£o
priority = 200 Roda apΓ³s PhysicsSystem (100) e MovementSystem (150)

CritΓ©rio de AceitaΓ§Γ£o

  • advanceFrame() < 0.1ms para 100 entidades animadas
  • 60fps com 100 entidades animadas simultaneamente
  • TransiΓ§Γ΅es com blendTime suaves (sem pop visual)
  • Frame events disparados no frame correto

DependΓͺncias


ReferΓͺncias

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions