Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Biblioteki C++

09. Sprite Sheet i Animacja

[lekcja]
W poprzednich rozdziałach poznaliśmy tekstury, sprajty, tekst oraz odmierzanie czasu. Teraz połączymy te elementy, aby stworzyć prostą animację postaci na podstawie jednego pliku graficznego, czyli tak zwanego sprite sheeta.

Sprite sheet to obraz zawierający kilka klatek animacji umieszczonych obok siebie. Zamiast wczytywać każdą klatkę jako osobny plik, możemy wczytać jeden obraz, a następnie wyświetlać tylko jego wybrany fragment. W SFML służy do tego funkcja setTextureRect(), która pozwala określić prostokątny obszar tekstury widoczny na sprajcie.

Funkcja setTextureRect() przyjmuje obiekt typu sf::IntRect. Obiekt ten zawiera pozycję oraz rozmiar fragmentu tekstury, który ma zostać wyświetlony. Dzięki temu możemy zmieniać aktualnie widoczną klatkę animacji, przesuwając prostokąt po sprite sheecie.

W tym przykładzie użyjemy również wcześniej poznanych klas sf::Clock oraz sf::Time, aby zmieniać klatkę animacji co określony czas. Dodatkowo wykorzystamy tekst do wyświetlania numeru aktualnej klatki.

W programie najpierw wczytujemy teksturę sprite_sheet.png, która zawiera wszystkie klatki animacji. Następnie tworzymy sprajta i ustawiamy mu początkowy fragment tekstury za pomocą funkcji setTextureRect().

Zmienna frameSize określa rozmiar pojedynczej klatki animacji. W naszym przykładzie każda klatka ma rozmiar 128x128 pikseli. Zmienna framesCount przechowuje liczbę wszystkich klatek znajdujących się w sprite sheecie.

Numer aktualnej klatki przechowujemy w zmiennej currentFrame. Aby przejść do następnej klatki, zwiększamy jej wartość o jeden. Operator modulo % sprawia, że po osiągnięciu ostatniej klatki animacja wraca z powrotem do klatki pierwszej.

Po obliczeniu numeru aktualnej klatki tworzymy obiekt sf::IntRect, który określa fragment tekstury do wyświetlenia. Pozycja x prostokąta zależy od numeru aktualnej klatki oraz szerokości pojedynczej klatki. Jeżeli currentFrame wynosi 0, wyświetlany jest fragment tekstury od pozycji x = 0. Jeżeli currentFrame wynosi 1, wyświetlany jest fragment od pozycji x = 128. Dla kolejnych klatek pozycja przesuwa się dalej o szerokość jednej klatki.

Do kontrolowania szybkości animacji używamy zegara. Program sprawdza, czy od ostatniej zmiany klatki minęło więcej niż 0.2 sekundy. Jeśli tak, animacja przechodzi do następnej klatki.

Na końcu aktualizujemy również tekst wyświetlany w oknie, aby pokazywał numer aktualnej klatki animacji.

W ten sposób możemy stworzyć prostą animację na podstawie jednego pliku graficznego. Jest to bardzo często używana technika w grach 2D, ponieważ pozwala przechowywać wiele klatek animacji w jednej teksturze i wygodnie wybierać, która z nich ma być aktualnie wyświetlana.

Sprite Sheet użyty w programie:

C/C++
#include <SFML/Graphics.hpp>
#include <iostream>

int main() {
   
sf::RenderWindow window = sf::RenderWindow( sf::VideoMode( sf::Vector2u( 800u, 600u ) ), "Sprite Sheet and Animation" );
   
   
// create a Font object
   
sf::Font font;
   
   
// load the font from the Windows Fonts directory
   
if( !font.openFromFile( "C:\\Windows\\Fonts\\arial.ttf" ) ) {
       
std::cout << "Failed to load font!" << std::endl;
       
return 0;
   
}
   
   
sf::Texture spriteSheetTexture; // create a texture to hold the sprite sheet
   
if( !spriteSheetTexture.loadFromFile( "sprite_sheet.png" ) ) { // load the sprite sheet from a file
       
std::cout << "Failed to load texture!" << std::endl;
       
return 0;
   
}
   
   
// create a text object to display the current frame
   
sf::Text frameText( font, "Frame: 0", 47u );
   
frameText.setFillColor( sf::Color::White );
   
frameText.setOutlineThickness( 4.f );
   
frameText.setOutlineColor( sf::Color( 127, 127, 127 ) );
   
frameText.setPosition( sf::Vector2f( 47.f, 47.f ) );
   
   
// animation variables
   
sf::Vector2i frameSize( 128, 128 );
   
int framesCount = 4;
   
   
// for animation
   
sf::Clock clock; // create a clock to measure time
   
sf::Time prevTime = clock.getElapsedTime();
   
int currentFrame = 0;
   
   
sf::Sprite sprite( spriteSheetTexture );
   
sprite.setTextureRect( sf::IntRect( sf::Vector2i( currentFrame * frameSize.x, 0 ), frameSize ) ); // display the first frame
   
sprite.setScale( sf::Vector2f( 2.f, 2.f ) );
   
sprite.setPosition( sf::Vector2f( 256.f, 160.f ) );
   
   
while( window.isOpen() ) {
       
       
while( const std::optional event = window.pollEvent() ) {
           
           
if( event->is < sf::Event::Closed >() )
               
 window.close();
           
       
}
       
       
sf::Time currentTime = clock.getElapsedTime(); // get the elapsed time
       
       
if(( currentTime - prevTime ).asSeconds() > 0.2f ) {
           
           
currentFrame =( currentFrame + 1 ) % framesCount;
           
           
sf::IntRect textureRect;
           
textureRect.position = sf::Vector2i( currentFrame * frameSize.x, 0 );
           
textureRect.size = frameSize;
           
sprite.setTextureRect( textureRect ); // set the texture rect to display the current frame
           
           
frameText.setString( "Frame: " + std::to_string( currentFrame ) ); // update the text to display the current frame
           
           
prevTime = currentTime;
       
}
       
       
window.clear( sf::Color::Black );
       
window.draw( sprite );
       
window.draw( frameText );
       
window.display();
   
}
}

Wynik programu:
Poprzedni dokument Następny dokument
08. Zegar i Odmierzanie Czasu Kurs STL, C++