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 :
#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
#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() { }
#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
#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...
#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
#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); } } }
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
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; }
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!