Voilà ma classe d'animation utilisé avec SFML. Elle a pour but premier d'être flexible et facile à adapté à notre code source. Par contre, elle utilise la classe PausableClock et la classe dérivée LayerAnim utilise la classe Layer
Cette classe est la base de l'animation, elle gère le changement de frame ainsi que la gestion du temps (pause, arrêt et jouer). Son changement de frame est codé de manière que si il y a un temps plus grand que le temps normal entre 2 frames, il passe le nombre de frame. Par exemple, si j'ai 0.5 sec entre chaque frame et que j'attends 2 sec avant de l'afficher, ce sera le 5e frame qui sera affiché et non le 2e. Attention, la méthode update() met le frame selon le temps, ainsi si vous appeler la méthode nextFrame() et que ensuite vous appelez update(), vous n'aurez plus le même frame si ce n'est pas lui qui est supposé être affiché selon le temps. Pour une telle utilisation vous devrez mettre l'animation en pause afin d'afficher les frames que vous voulez. Cette classe est virtuel, vous devrez soit la dériver ou utilisé une des classes qui suit.
#ifndef ANIM_H #define ANIM_H #include <SFML/System.hpp> #include <core/PausableClock.h> /*! * \class Anim * Implémente la gestion des animation selon le temps. */ class Anim { public: Anim(void); virtual ~Anim(void); //!Change au prochain frame d'animation void nextFrame(); //!Change au frame d'animation défini par 'count' virtual void setFrame(const unsigned int &count); //!Change au premier frame animation void reset(); //!Retourne le frame d'animation courante unsigned int currentFrame() const; //!Définis si l'animation est en boucle (choice=true) void loop(const bool &choice); //!Retourne si l'animation est joué en boucle bool isLoop() const; //!Joue l'animation void play(); //!Arrete l'animation et remet le compteur à zéro void stop(); //!Met l'animation en pause et laisse le compteur où il en est. void pause(); //!Retourne true si l'animation joue bool isPlaying() const; //!Définis le délais en seconde entre chaque frame. void setDelay(const float &delay); //!Retourne le délai entre chaque frame float delay() const; //!Met à jour l'animation virtual void update(); //!Retourne le nombre de frame de l'animation virtual unsigned int getSize() const=0; protected: //!Timer de l'animation core::PausableClock m_time; private: //!Frame courant de l'animation unsigned int m_frameCount; //!Delai en seconde entre chaque animation float m_delay; //!Si l'animation est en boucle bool m_isLoop; //!Si l'animation est en train de jouer bool m_play; }; #endif
#include "Anim.h" Anim::Anim(void) : m_time() { m_frameCount=0; m_delay=0.f; m_isLoop=true; m_play=true; } Anim::~Anim(void) { } void Anim::nextFrame() { if(currentFrame()==getSize()-1) { setFrame(0); if(!isLoop()) stop(); } else setFrame(currentFrame()+1); } void Anim::setFrame(const unsigned int &count) { if(count<getSize()) m_frameCount=count; else m_frameCount=0; } void Anim::reset() { stop(); play(); } void Anim::loop(const bool &choice) { m_isLoop=choice; } void Anim::play() { m_play = true; m_time.Play(); } void Anim::stop() { Anim::m_play = false; setFrame(0); m_time.Stop(); } void Anim::pause() { Anim::m_play = false; m_time.Pause(); } bool Anim::isPlaying() const { return m_play; } void Anim::setDelay(const float &delay) { m_delay=delay; } float Anim::delay() const { return m_delay; } unsigned int Anim::currentFrame() const { return m_frameCount; } bool Anim::isLoop() const { return m_isLoop; } void Anim::update() { if(isPlaying()) { if(delay()) { unsigned int frameCount = (unsigned int)(m_time.GetElapsedTime()/delay()); if(!isLoop() && frameCount>getSize()) stop(); else { frameCount = frameCount % getSize(); setFrame(frameCount); } } else nextFrame(); } }
Voilà un exemple d'utilisation de Anim en utilisant les fonctionnalité de Layer où chaque couche est un frame (anim[0] est le premier frame, etc.).
#ifndef LAYERANIM_H #define LAYERANIM_H #include "Anim.h" #include "Layer.h" /* * Animation où chaque frame est repésenter par une couche de Layer. */ class LayerAnim : public Anim, public Layer { public: LayerAnim(void); //!Retourne le nombre de frame de l'animation virtual unsigned int getSize() const; protected: virtual void Render(sf::RenderTarget& Target) const; }; #endif
#include "LayerAnim.h" LayerAnim::LayerAnim(void) { } unsigned int LayerAnim::getSize() const { return size(); } void LayerAnim::Render(sf::RenderTarget& Target) const { Anim* th = const_cast<LayerAnim*>(this); th->update(); Target.Draw(*at(currentFrame())); }
Voilà un court exemple d'utilisation:
int main() { LayerAnim anim; anim.setDelay(1.0f); sf::Sprite frame1; sf::Sprite frame2; sf::Sprite frame3; anim.push_back(frame1); anim.push_back(frame2); anim.push_back(frame3); while(1) { ... app.Draw(anim); } }
Cette classe d'animation utilise un image avec tout les frames et la découpe automatiquement.
#ifndef IMGANIM_H #define IMGANIM_H #include <SFML/Graphics.hpp> #include "Anim.h" /*! * \class ImgAnim * Comme Anim à la diférence près que la classe utilise une image avec tout les sprites * au lieu des couches d'une layer. */ class ImgAnim : public Anim, public sf::Sprite { public: /* * \param img Image avec les sprites * \param rows Nombre de frame par animation * \param line Nombre de ligne d'animation * \param leReste Voir la documentation de sf::Sprite */ ImgAnim(const sf::Image &Img, const unsigned int &nbFrame, const unsigned int &line=1, const sf::Vector2f &Position=sf::Vector2f(0, 0), const sf::Vector2f &Scale=sf::Vector2f(1, 1), float Rotation=0.f, const sf::Color &Col=sf::Color(255, 255, 255, 255)); //!Définis les dimension d'un frame void setFrameDim(const unsigned int &w, const unsigned int &h); //!Retourne la dimension d'un frame sf::IntRect frameDim() const; //!Définis le décalage de l'animation sur l'image (permet d'avoir plusieurs anim sur une seule image) void setOffset(const unsigned int &x, const unsigned int &y); //!Retourne un rectangle avec l'offset sf::IntRect offset() const; //!Définis la ligne animé courante void setAnimRow(const unsigned int &row); //!Retourne la ligne animé courante int animRow() const; //!Définis le frame courant virtual void setFrame(const unsigned int &count); //!Met à jour le rectangle d'animation void refreshSubRect(); //!Définis le nombre de frame dans l'animation void setSize(const unsigned int &size); //!Retourne le nombre de frame virtual unsigned int getSize() const; protected: virtual void Render(sf::RenderTarget& Target) const; private: //!Représente le nombre de frame dans l'animation unsigned int m_size; //!Ligne d'animation courante unsigned int m_animRow; //!Décalage à gauche du frameset unsigned int m_xOffset; //!Décalage en haut du frameset unsigned int m_yOffset; }; #endif
#include "ImgAnim.h" ImgAnim::ImgAnim(const sf::Image &Img, const unsigned int &nbFrame, const unsigned int &line, const sf::Vector2f &Position, const sf::Vector2f &Scale,float Rotation, const sf::Color &Col) : sf::Sprite(Img,Position,Scale,Rotation,Col) { m_animRow=0; //Le constructeur par défaut prend en compte qu'il n'y a aucun offset SetSubRect(sf::IntRect(0,0,Img.GetWidth()/nbFrame,Img.GetHeight()/line)); m_xOffset=0; m_yOffset=0; m_size=nbFrame; } void ImgAnim::setAnimRow(const unsigned int &row) { m_animRow=row; refreshSubRect(); } int ImgAnim::animRow() const { return m_animRow; } void ImgAnim::setFrameDim(const unsigned int &w, const unsigned int &h) { sf::IntRect tRect = GetSubRect(); SetSubRect(sf::IntRect(tRect.Left,tRect.Top,tRect.Left+w,tRect.Top+h)); } sf::IntRect ImgAnim::frameDim() const { sf::IntRect tRect = GetSubRect(); return sf::IntRect(0,0,tRect.GetWidth(),tRect.GetHeight()); } void ImgAnim::setOffset(const unsigned int &x, const unsigned int &y) { m_xOffset=x; m_yOffset=y; refreshSubRect(); } sf::IntRect ImgAnim::offset() const { return sf::IntRect(0,0,m_xOffset,m_yOffset); } int unsigned ImgAnim::getSize() const { return m_size; } void ImgAnim::setSize(const unsigned int &size) { m_size=size; } void ImgAnim::setFrame(const unsigned int &count) { Anim::setFrame(count); refreshSubRect(); } void ImgAnim::refreshSubRect() { sf::IntRect tRect = GetSubRect(); SetSubRect(sf::IntRect(tRect.GetWidth()*currentFrame()+m_xOffset, tRect.GetHeight()*m_animRow+m_yOffset, tRect.GetWidth()*(currentFrame()+1)+m_xOffset, tRect.GetHeight()*m_animRow+tRect.GetHeight()+m_yOffset)); } void ImgAnim::Render(sf::RenderTarget& Target) const { ImgAnim* th = const_cast<ImgAnim*>(this); th->update(); sf::Sprite::Render(Target); }
Voilà un exemple d'utilisation avec un spriteset comme celui-ci:
//Charge l'image de zelda sf::Image imgZelda; imgZelda.LoadFromFile("zelda_sprite.png"); imgZelda.SetSmooth(false); imgZelda.CreateMaskFromColor(sf::Color(255,0,255)); //Créer 4 animation de 8 frame chaque à partir de l'image imgZelda ImgAnim zelda(imgZelda,8,4); while(1) { const sf::Input& Input = App.GetInput(); if(Input.IsKeyDown(sf::Key::Left) ) { zelda.Move(-SPEED,0.f); zelda.setAnimRow(3); zelda.play(); } if(Input.IsKeyDown(sf::Key::Right) ) { zelda.Move(SPEED,0.f); zelda.setAnimRow(1); zelda.play(); } if(Input.IsKeyDown(sf::Key::Up)) { zelda.Move(0.f,-SPEED); zelda.setAnimRow(2); zelda.play(); } if(Input.IsKeyDown(sf::Key::Down)) { zelda.Move(0.f,SPEED); zelda.setAnimRow(0); zelda.play(); } if(!Input.IsKeyDown(sf::Key::Down) && !Input.IsKeyDown(sf::Key::Up) && !Input.IsKeyDown(sf::Key::Right) && !Input.IsKeyDown(sf::Key::Left)) { zelda.stop(); } App.Draw(zelda); }
Si votre image contient plusieurs frameset, le 2e et 3e arguments du constructeur sont inutile puisque qu'ils ne tiennent pas compte d'un quelconque offset, laissez leur valeur à 1. Pour bien procédé, commencer par donner la position de votre frameset sur l'image avec setOffset(). Ensuite, donnez la dimension de vos frames avec setFrameDim(). Finalement, donnez le nombre de frame par animation avec setSize().