Lazy Foo' Productions

SDL Forums external link SDL Tutorials 🐰SDL3 Tutorials🥚 Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

Animation

Animation screenshot

Last Updated: Oct 19th, 2024

Odds are you are going to want more than static images in your game. Here we'll be making a stick figure walk in place with basic animation.
Here is a set of frames of an animation on a sprite sheet:
foo

Now lets show one sprite right after another every 10th of a second in a way that shows I am not an animator (walk cycles are hard, people):
foo walking

That's how animating sprites works: showing one image after another to give the appearance of motion.
            //The quit flag
            bool quit{ false };

            //The event data
            SDL_Event e;
            SDL_zero( e );

            //Timer to cap frame rate
            LTimer capTimer;

            //Current animation frame
            int frame = -1;
Before we enter the main loop, we declare a variable to keep track of the current frame of animation.
                //Start frame time
                capTimer.start();

                //Get event data
                while( SDL_PollEvent( &e ) )
                {
                    //If event is quit type
                    if( e.type == SDL_EVENT_QUIT )
                    {
                        //End the main loop
                        quit = true;
                    }
                }

                //Go to next frame
                frame++;

                //Cycle animation
                constexpr int kWakingAnimationFrames = 4;
                constexpr int kWakingAnimationFramesPerSprite = 6;
                if( frame / kWakingAnimationFramesPerSprite >= kWakingAnimationFrames )
                {
                    frame = 0;
                }

After the event loop, we update the animation logic. We increment the frame counter every frame but we do not want the animation to change sprites every frame or it will be too fast.

Our application runs at 60 FPS but the animation runs at 10 FPS. So to convert the frame rates, we divide the frame counter by 6. Also, we want the animation to loop so when the animation goes past the 4th frame, we return the frame counter to 0.
                //Set sprite clips
                constexpr float kSpriteWidth = 64;
                constexpr float kSpriteHeight = 205;
                SDL_FRect spriteClips[ kWakingAnimationFrames ] = {
                    { kSpriteWidth * 0, 0.f, kSpriteWidth, kSpriteHeight },
                    { kSpriteWidth * 1, 0.f, kSpriteWidth, kSpriteHeight },
                    { kSpriteWidth * 2, 0.f, kSpriteWidth, kSpriteHeight },
                    { kSpriteWidth * 3, 0.f, kSpriteWidth, kSpriteHeight }
                };
Here we define the clip rectangles. They all have the same width/height and y position. The only difference between them is the x position. First sprite is at x = 0 and 0 * sprite width is still 0. Next one is one sprite width over, the one after that is two sprite widths over, and the last one is 3 sprite widths over.
                //Fill the background
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF,  0xFF );
                SDL_RenderClear( gRenderer );

                //Render current frame
                SDL_FRect* currentClip = &spriteClips[ frame / kWakingAnimationFramesPerSprite ];
                gSpriteSheetTexture.render( ( kScreenWidth - kSpriteWidth ) / 2, ( kScreenHeight - kSpriteHeight ) / 2, currentClip );

                //Update screen
                SDL_RenderPresent( gRenderer );

                //Cap frame rate
                constexpr Uint64 nsPerFrame = 1000000000 / kScreenFps; 
                Uint64 frameNs = capTimer.getTicksNS();
                if( frameNs < nsPerFrame )
                {
                    SDL_DelayNS( nsPerFrame - frameNs );
                }
In our rendering we get the current sprite clip and render it to the screen. This will cycle through our sprites to show the stick figure animating.

Addendum: Animation is a rabbit hole

Animation (completely ignoring the artistic side of it) is an incredible large field. There's concepts like skeletal animation, animation blending, morphing, animation channels, and such that are way beyond the scope of this tutorial but are key to any AAA game engine.

What is common to animation is that animations are sequences of values. Here we had a simple array of SDL_FRects to represent our sequence of values but in a real engine I would probably have an animation class with a getCurrentSprite( float currentTime ) method. With the animation encapsulated in a class, we can very easily do things like pause, change the animation speed, animation reversal, looping, etc which are common animation operations you see in game engines.

However, for a Nasty Tetris Project, this is overkill because you're probably not going to be using any complex animations. Doing your animations with just an array of clip rectangles and an integer frame counter should be good enough.