Frame, Anim, Animated : Classes pour gérer vos sprites animés

:!: La traduction de cette page est trouvable sur le wiki anglais. J'y ai apporté quelques modifications. Je vous conseille d'aller jeter quand même un œil. ;-)Hiura 2008/11/25 14:19

Voilà trois petites classes vous permettant de gérer l'animation d'un sprite à la manière d'un gif.

La classe Frame: Un pointeur sur une image assortie du SubRect qui va avec.
La classe Anim: Une animation. C'est en fait un simple vecteur de frames. Cette classe peut-être gérée comme une ressource, elle est à l' “Animated” ce que la “sf::Image” est au “sf::Sprite”.
La classe Animated: Un simple “sf::Sprite”, munit de quelques fonctions supplémentaires (SetAnim, SetFrameTime, Play, Pause, Stop etc.)

Le code :

Frame.hpp

#ifndef ZIGO_FRAME
#define ZIGO_FRAME
 
#include <SFML/Graphics.hpp>
 
// Une Frame est composée d'un pointeur sur une image, d'un SubRect et d'une couleur
// La couleur par défaut d'une Frame est le blanc.
class Frame
{
    public:
    // Par défaut
    Frame(const sf::Color& NewColor = sf::Color::White);
 
    // Par copie
    Frame(const Frame& Cpy);
 
    // Image et Rect
    Frame(sf::Image* NewImage, const sf::Rect<int>& NewRect, const sf::Color& NewColor = sf::Color::White);
 
    // Image (Le Rect est au dimension de l'image)
    Frame(sf::Image* NewImage, const sf::Color& NewColor = sf::Color::White);
 
    // destructeur
    virtual ~Frame();
 
    // Accès public à l'image, au Rect et à la couleur
    sf::Image* Image;
 
    sf::Rect<int> Rect;
 
    sf::Color Color;
};
 
#endif // ZIGO_FRAME

Frame.cpp

#include "Frame.hpp"
 
// Par défaut
Frame::Frame(const sf::Color& NewColor)
{
    Image = NULL;
    Color = NewColor;
}
 
// Par copie
Frame::Frame(const Frame& Cpy)
{
    Image = Cpy.Image;
    Rect = Cpy.Rect;
    Color = Cpy.Color;
}
 
// Image et Rect
Frame::Frame(sf::Image* NewImage, const sf::Rect<int>& NewRect, const sf::Color& NewColor)
{
    Image = NewImage;
    Rect = NewRect;
    Color = NewColor;
}
 
// Image (Le Rect est au dimension de l'image)
Frame::Frame(sf::Image* NewImage, const sf::Color& NewColor)
{
    Image = NewImage;
    if (Image != NULL)
        Rect = sf::Rect<int>(0, 0, Image->GetWidth(), Image->GetHeight());
 
    Color = NewColor;
}
 
// destructeur
Frame::~Frame()
{
 
}

Anim.hpp

#ifndef ZIGO_ANIM
#define ZIGO_ANIM
 
#include <vector>
 
#include "Frame.hpp"
 
// La classe animation n'est qu'un 'vector' de Frame
class Anim
{
    public:
 
    // par défaut
    Anim();
 
    // destructeur
    virtual ~Anim();
 
    // Par copie
    Anim(const Anim& Cpy);
 
    // Ajouter une Frame
    void PushFrame(const Frame& NewFrame);
 
    // Nombre de Frame(s)
    size_t Size() const;
 
    // Accès a la frame numéro N
    Frame& operator [] (size_t N);
 
    // Plus tard, nous pourrons ajouter différentes fonctions liées au fait qu'une 'Anim' est une ressource
    // Par exemple : LoadFromFile, SaveToFile etc...
 
    private:
    std::vector< Frame > myFrame;
 
};
#endif

Anim.cpp

#include "Anim.hpp"
 
// Par défaut
Anim::Anim()
{
 
}
 
// déstructeur
Anim::~Anim()
{
 
}
 
// Par copie
Anim::Anim(const Anim& Cpy)
{
    myFrame = Cpy.myFrame;
}
 
// Ajouter une frame
void Anim::PushFrame(const Frame& NewFrame)
{
    myFrame.push_back(NewFrame);
}
 
// Nombre de frame(s)
size_t Anim::Size() const
{
    return myFrame.size();
}
 
// Accès a la frame numéro N
Frame& Anim::operator [] (size_t N)
{
    return myFrame[N];
}
 
 
 
// Plus tard, nous pourrons ajouter différentes fonctions liées au fait qu'une 'Anim' est une ressource
// Par exemple : LoadFromFile, SaveToFile etc...

Animated.hpp

#ifndef ZIGO_ANIMATED
#define ZIGO_ANIMATED
 
#include "Anim.hpp"
 
// Un sprite animé
 
// Il est composé de :
 
// Le temps écoulé entre chaque frame
// Un pointeur sur l'animation qu'il doit lire
 
// Des fonctions de lecture :
// Play, Pause, Stop, Loop
class Animated : public sf::Sprite
{
    public:
 
    // Par Copie
    Animated(const Animated& Cpy);
 
    // Par défault
    Animated(bool Play = false, bool Loop = true, float Time = 0.f);
 
    // Directement avec une Anim
    Animated(Anim* NewAnim, bool Play = false, bool Loop = true, float Time = 0.f);
 
    // Destructeur
    virtual ~Animated();
 
    // Comme 'SetImage', sauf qu'on lui fournit l'Anim
    void SetAnim(Anim* NewAnim);
 
    // Retourne un pointeur sur l'anim
    Anim* GetAnim();
 
    // Passer à l'image numéro X
    void SetFrame(int Frame);
 
    // Retourne la Frame courante
    int GetCurrentFrame();
 
    // Fixer le temps entre chaques Frame
    void SetFrameTime(float Time);
 
    // Retourne le temps entre chaques Frame
    float GetFrameTime();
 
    // Jouer en boucle ?
    void SetLoop(bool Loop);
 
    // Jouer en boucle ?
    bool IsLoop();
 
    // Met en pause la lecture
    void Pause();
 
    // Relance la lecture
    void Play();
 
    // Met en pause la lecture, et 'rembobine'
    void Stop();
 
    // Est en pause ?
    bool IsPaused();
 
    // Est stoppé ?
    bool IsStoped();
 
    // Fonction à appeler à chaque tour de boucle, prend le temps
    // écoulé depuis le dernier appel à la fonction en paramètre
    void anim(float ElapsedTime);
 
    private:
    float myTime;
    float myElapsedTime;
    bool Paused;
    bool myLoop;
    Anim* myAnim;
    int myCurrentFrame;
};
 
#endif

Animated.cpp

#include "Animated.hpp"
 
// Par Copie
Animated::Animated(const Animated& Cpy) :
sf::Sprite(Cpy)
{
    myCurrentFrame = Cpy.myCurrentFrame;
    myTime = Cpy.myTime;
    myElapsedTime = Cpy.myElapsedTime;
    Paused = Cpy.Paused;
    myAnim = Cpy.myAnim;
    myLoop = Cpy.myLoop;
}
 
// Par défault
Animated::Animated(bool Play, bool Loop, float Time)
{
    myAnim = NULL;
    myCurrentFrame = 0;
    myTime = Time;
    myElapsedTime = Time;
    Paused = !Play;
    myLoop = Loop;
}
 
// Directement avec une Anim
Animated::Animated(Anim* NewAnim, bool Play, bool Loop, float Time)
{
    myTime = Time;
    myElapsedTime = Time;
    Paused = !Play;
    myLoop = Loop;
    myAnim = NewAnim;
 
    SetFrame(0);
}
 
// Destructeur
Animated::~Animated()
{
 
}
 
// Comme 'SetImage', sauf qu'on lui fournit l'Anim
void Animated::SetAnim(Anim* NewAnim)
{
    myAnim = NewAnim;
 
    SetFrame(0);
}
 
// Retourne un pointeur sur l'anim
Anim* Animated::GetAnim()
{
    return myAnim;
}
 
// Passer à l'image numéro X
void Animated::SetFrame(int Frame)
{
    if ( myAnim != NULL)
    {
        if (myAnim->Size() > 0)
        {
            if ((*myAnim)[Frame].Image != NULL)
                SetImage(*((*myAnim)[Frame].Image));
 
            SetSubRect((*myAnim)[Frame].Rect);
 
            SetColor((*myAnim)[Frame].Color);
 
            myCurrentFrame = Frame;
        }
    }
}
 
//Retourne La frame courante
int Animated::GetCurrentFrame()
{
    return myCurrentFrame;
}
 
// Fixer le temps entre chaques Frame
void Animated::SetFrameTime(float Time)
{
    myTime = Time;
}
 
// retourne le temps entre chaques Frame
float Animated::GetFrameTime()
{
    return myTime;
}
 
// Jouer en boucle ?
void Animated::SetLoop(bool Loop)
{
    myLoop = Loop;
}
 
// Jouer en boucle ?
bool Animated::IsLoop()
{
    return myLoop;
}
 
// Met en pause la lecture
void Animated::Pause()
{
    Paused = true;
}
 
// Relance la lecture
void Animated::Play()
{
    Paused = false;
}
 
// Met en pause la lecture, et 'rembobine'
void Animated::Stop()
{
    SetFrame(0);
    myElapsedTime = myTime;
    Paused = true;
}
 
// Est En pause ?
bool Animated::IsPaused()
{
    return Paused;
}
 
// Est Stoppé ?
bool Animated::IsStoped()
{
    return (Paused && (myCurrentFrame == 0) && (myElapsedTime == myTime));
}
 
// Fonction à appeler à chaque tours de boucle, prend le temps
// écoulé depuis le dernier appel à la fonction en paramètre
void Animated::anim(float ElapsedTime)
{
    // Si il n'est pas en pause et que l'animation est valide
    if (!Paused && myAnim != NULL)
    {
        // on retranche le temps écoulé a notre compteur
        myElapsedTime -= ElapsedTime;
 
        // Si Le temps entre chaque frame est atteint
        if (myElapsedTime <= 0.f)
        {
            // On réinitialise notre compteur
            myElapsedTime = myTime;
 
            // On passe a la frame suivante
            if (myCurrentFrame + 1 < myAnim->Size())
                myCurrentFrame++;
            else
            {
                // Ou on a la premiere
                if (myLoop)
                    myCurrentFrame = 0;
                else
                {
                    // Si le mode Loop est désactivé, on stop l'animation
                    Stop();
                }
            }
 
            // On change la frame
            SetFrame(myCurrentFrame);
        }
    }
}

Utilisation

Comme pour un sf::Sprite et son sf::Image, Vous devrez créer un 'Animated' et son (ou ses) 'Anim'. On applique l' “Anim” à l' “Animated” avec la fonction 'SetAnim' (prend l'adresse de l'Anim), puis on la joue (Play), on la met en pause (Pause), on la Stop (Stop), on change le temps entre chaque Frame (SetFrameTime) etc. 'Animated' est un sprite, on peut donc appeler n'importe quelle fonction membre de sf::Sprite.

Une “Anim” est un vecteur composé de “Frames”, que l'on doit donc définir une à une. Pour ajouter une frame, il faut appeler la fonction 'PushFrame(…)'. Pour accéder aux frames, utiliser simplement l'opérateur [] (comme pour n'importe quel vecteur ou tableau).

Exemple :

// On charge une image
sf::Image myImage;
myImage.LoadFromeFile("Animation.bmp");
 
// On définit une animation
Anim myAnim;
myAnim.PushFrame( Frame(&myImage, sf::Rect<int>(0, 0, 20, 20) ) ); // Premier segment de l'image : En haut a gauche
myAnim.PushFrame( Frame(&myImage, sf::Rect<int>(20, 0, 40, 20) ) ); // Second : en haut a droite
myAnim.PushFrame( Frame(&myImage, sf::Rect<int>(0, 20, 20, 40) ) ); // Troisième : En bas a gauche
myAnim.PushFrame( Frame(&myImage, sf::Rect<int>(20, 20, 40, 40) ) ); // Quatrième : en bas a droite
 
// On crée un "Animated"
Animated myAnimated;
 
// On le définit
myAnimated.SetAnim(&myAnim);
myAnimated.Pause();
myAnimated.SetLoop(true);
 
// 0.2 secondes s'écoule entre chaque frame
myAnimated.SetFrameTime(0.2f);
 
// On le place
myAnimated.SetPosition(300, 200);
 
// Nous n'aurons plus qu'à appeler "myAnimated.anim(FrameTime)" a chaques tour de boucle
// pour que notre Sprite s'anime a la manière d'un Gif

Exemple

On utilise cette image (désolé de la mocheté de la chose, c'est juste pour l'exemple!):

Le code:

#include "Animated.hpp"
 
int main()
{
 
    sf::RenderWindow Window(sf::VideoMode(800, 600, 32), "Test");
 
 
    // On charge l'image qui contient nos animations
    sf::Image CharacterAnim;
    CharacterAnim.LoadFromFile("character.png");
    CharacterAnim.CreateMaskFromColor(sf::Color::Black);
 
    // On crée 4 animations (haut, bas, gauche, droite)
    Anim GoUp, GoRight, GoDown, GoLeft;
 
    // On définit nos animations frame par frame, en fournissant l'image et le SubRect
    GoUp.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(0, 0, 45, 64)));
    GoUp.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(45, 0, 90, 64)));
    GoUp.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(90, 0, 135, 64)));
    GoUp.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(135, 0, 190, 64)));
 
    GoRight.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(0, 64, 45, 128)));
    GoRight.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(45, 64, 90, 128)));
    GoRight.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(90, 64, 135, 128)));
    GoRight.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(135, 64, 190, 128)));
 
    GoDown.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(0, 128, 45, 192)));
    GoDown.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(45, 128, 90, 192)));
    GoDown.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(90, 128, 135, 192)));
    GoDown.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(135, 128, 190, 192)));
 
    GoLeft.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(0, 192, 45, 256)));
 
    GoLeft.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(45, 192, 90, 256)));
    GoLeft.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(90, 192, 135, 256)));
    GoLeft.PushFrame(Frame(&CharacterAnim, sf::Rect<int>(135, 192, 190, 256)));
 
    // On crée un sprite animé avec par défaut l'animation 'GoUp', en pause, mode Loop On, Et 0.1 seconde entre chaque frame
    Animated MyCharacter(&GoUp, false, true, 0.1f);
    MyCharacter.SetCenter(20, 32);
    MyCharacter.SetPosition(400, 300);
 
    // La boucle habituelle
    sf::Event Event;
    bool isRunning = true;
    while (isRunning)
    {
        while (Window.GetEvent(Event))
        {
            switch(Event.Type)
            {
                case sf::Event::Closed:
                isRunning = false;
                break;
 
                case sf::Event::KeyReleased:
                switch(Event.Key.Code)
                {
                    case sf::Key::Escape:
                    isRunning = false;
                    break;
                }
 
                break;
            }
        }
 
 
        // On teste les déplacements
 
        // Gauche :
        if (Window.GetInput().IsKeyDown(sf::Key::Up))
        {
 
            // On applique l'anim si besoin est
            if (MyCharacter.GetAnim() != &GoUp)
                MyCharacter.SetAnim(&GoUp);
 
            // On déplace le sprite
            MyCharacter.Move(0, -100*Window.GetFrameTime());
            if(MyCharacter.IsPaused())
                MyCharacter.Play();
        }
 
        else if (Window.GetInput().IsKeyDown(sf::Key::Down))
        {
            if (MyCharacter.GetAnim() != &GoDown)
                MyCharacter.SetAnim(&GoDown);
            MyCharacter.Move(0, 100*Window.GetFrameTime());
            if(MyCharacter.IsPaused())
                MyCharacter.Play();
        }
 
        else if (Window.GetInput().IsKeyDown(sf::Key::Left))
        {
            if (MyCharacter.GetAnim() != &GoLeft)
                MyCharacter.SetAnim(&GoLeft);
            MyCharacter.Move(-100*Window.GetFrameTime(), 0);
            if(MyCharacter.IsPaused())
                MyCharacter.Play();
        }
 
        else if (Window.GetInput().IsKeyDown(sf::Key::Right))
        {
            if (MyCharacter.GetAnim() != &GoRight)
                MyCharacter.SetAnim(&GoRight);
 
            MyCharacter.Move(100*Window.GetFrameTime(), 0);
            if(MyCharacter.IsPaused())
                MyCharacter.Play();
        }
        else
        {
            // Si le perso arrette de se déplacer, on Stop l'animation
            if (!MyCharacter.IsStoped())
                MyCharacter.Stop();
        }
 
        // On appelle la fonction d'animation a chaque tours en donnant le temps écoulé
        MyCharacter.anim(Window.GetFrameTime());
 
        // Et on dessine
        Window.Draw(MyCharacter);
 
        Window.Display();
    }
 
    return EXIT_SUCCESS;
}

Commentaires

A l'avenir, je pense implémenter des fonctions de lecture/écriture sur le disque pour la classe “Anim”, mais si quelqu'un veut le faire, ou ajouter quoi que ce soit d'autre, toute modification est la bienvenue.

Si vous avez des suggestions n'hésitez pas!

 
fr/sources/frame_anim_animated.txt · Last modified: 2010/04/10 15:23 by hiura
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki