The last tutorial we took our chance at making a Tic-Tac-Toe game. Hopefully most of you were successful in getting it to work. If not, don't fret, you'll get the hang of all of this eventually.

In this tutorial we are going to take our hand at SDL Animation. As before, we'll be building on top of the previous SDL lessons (but not including the Tic Tac Toe one). So lets get started.

We'll be creating a new class to handle Animation, and in the next tutorial we will create a class to handle Entities. Please keep in mind that these two things are seperate, and while I know they could be one class, I don't wish to take that approach. So please hold back your criticism.

Create two new files (I am sure you are seeing a pattern now, hopefully), called CAnimation.h and CAnimation.cpp. Eventually, our CEntity class will inherit this class, but for now we'll be testing through an object we create later. And before we get started add the include directive within CApp.h (before #include "CEvent.h" is just fine).
#include "CAnimation.h"
Now open up CAnimation.h and lets start coding. Add the following basic class structure to your file.
#ifndef _CANIMATION_H_
    #define _CANIMATION_H_

#include <SDL.h>

class CAnimation {
    private:
        int    CurrentFrame;

        int     FrameInc;

    private:
        int     FrameRate; //Milliseconds

        long    OldTime;

    public:
        int    MaxFrames;

        bool    Oscillate;

    public:
        CAnimation();

        void OnAnimate();

    public:
        void SetFrameRate(int Rate);

        void SetCurrentFrame(int Frame);

        int GetCurrentFrame();
};

#endif
Now, open up CAnimation.cpp and add this code:
#include "CAnimation.h"

CAnimation::CAnimation() {
    CurrentFrame    = 0;
    MaxFrames       = 0;
    FrameInc        = 1;

    FrameRate       = 100; //Milliseconds
    OldTime         = 0;

    Oscillate       = false;
}

void CAnimation::OnAnimate() {
    if(OldTime + FrameRate > SDL_GetTicks()) {
        return;
    }

    OldTime = SDL_GetTicks();

    CurrentFrame += FrameInc;

    if(Oscillate) {
        if(FrameInc > 0) {
            if(CurrentFrame >= MaxFrames) {
                FrameInc = -FrameInc;
            }
        }else{
            if(CurrentFrame <= 0) {
                FrameInc = -FrameInc;
            }
        }
    }else{
        if(CurrentFrame >= MaxFrames) {
            CurrentFrame = 0;
        }
    }
}

void CAnimation::SetFrameRate(int Rate) {
    FrameRate = Rate;
}

void CAnimation::SetCurrentFrame(int Frame) {
    if(Frame < 0 || Frame >= MaxFrames) return;

    CurrentFrame = Frame;
}

int CAnimation::GetCurrentFrame() {
    return CurrentFrame;
}
Some explanation on what this class is all about now. There is one basic element of animation that we need to handle, that is the current frame of the animation. Take the Yoshi image below for example (we'll be using him in this tutorial). You'll notice we have 8 frames of Yoshi on one image. Each frame would then be labeled 0, 1, 2 from Top to Bottom.



Remember the second tutorial where we create a function to draw part of an image? Well, if we take that function in conjunction with the frame of the animation, voila!

So the first variable I want you to see is CurrentFrame. This is the current frame of the animation that we are going to draw on the screen. Whatever value it has, will determine what part of the surface we will draw to the screen. So, when we call our draw function, we would do something like this:
CSurface::OnDraw(Surf_Display, Surf_Image, 0, 0, 0, Anim_Yoshi.GetCurrentFrame() * 64, 64, 64);
Since our Yoshi is really 64 x 64 pixels, that's the width and height we will grab, and is also how we grab the frame. Look at the image below for an illustration.



When the CurrentFrame increases by 1, we jump down 64 pixels (the height of Yoshi's frame), and draw that frame.

The other part of this class is that we need to know how many frames this animation is, thus the MaxFrames. The last vital part to know is how many frames per second, or rather, how fast this animation is going to display. In order to determine that we find use this bit of code found in the OnAnimate function.
if(OldTime + FrameRate > SDL_GetTicks()) {
    return;
}
By taking the Old Time in milliseconds plus the desired frame rate, we can check it against how long the SDL has been running. So, for example, we just started our program. SDL_GetTicks is 0, and OldTime is 0. Our desired frame rate is 1 frame per second. So FrameRate = 1000 (milliseconds). So, is 0 + 1000 greater than 0? Yes, thus we will skip over the function and wait. But once 0 + 1000 is less than SDL_GetTicks, that must mean 1 second has passed. So we increment the frame, and then reset OldTime to the current time, and start over.

The next interesting tid bit is Oscillate and FrameInc. Not that I wanted to confuse anyone by adding this, but I feel it's necessary for certain things later on. Basically, when the Oscillate flag is true, the animation will increase frames, and then decrease frames. If we had an animation with 10 frames, it would do something like this:

0 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 2 ...

You see, it goes up to 9, and then decreases back down to 0, and so on. There are some interesting uses for this, but we'll get into that in other lessons. So how does this work? Take a look at the OnAnimate function.
void CAnimation::OnAnimate() {
    if(OldTime + FrameRate > SDL_GetTicks()) {
        return;
    }

    OldTime = SDL_GetTicks();

    CurrentFrame += FrameInc;

    if(Oscillate) {
        if(FrameInc > 0) {
            if(CurrentFrame >= MaxFrames) {
                FrameInc = -FrameInc;
            }
        }else{
            if(CurrentFrame <= 0) {
                FrameInc = -FrameInc;
            }
        }
    }else{
        if(CurrentFrame >= MaxFrames) {
            CurrentFrame = 0;
        }
    }
}

Bug Fix: if(CurrentFrame >= MaxFrames - 1) needs to be if(CurrentFrame >= MaxFrames). Kudos to everyone in the comments mentioning this.

Bug Fix: There was an error with placing CurrentFrame += FrameInc; after the if statement, it should have been placed before the if statement. What would happen is if the animation had MaxFrames = 0, it would still increase the CurrentFrame to 1. Thanks Alexander Mangel for helping find this bug!


We already know what the OldTime and such do, but what about the rest? For now, look at the else statement of the Oscillate if statement. You'll notice we're simply checking if the CurrentFrame has exceeded the Max Frames. If it has, reset back to 0. Pretty simple. Then below that, outside of the block, we increase to the next frame.

Now, the more confusing part is the Oscillate if statement. This is where the FrameInc variable comes in. Basically, the FrameInc is set to 1 or -1, depending on how we are increasing or decreasing the frames. Remember, Oscillating causes the frames to go from 0 to 9 back to 0. So if the FrameInc is greater than 0, we are increasing the frames, otherwise we are decreasing frames. The innermost if statements basically inverse the FrameInc if we reached the end of 0, or MaxFrames.

Now that that is all taken care of, lets put this class into action. Create a new CAnimation object within CApp.h:
CAnimation      Anim_Yoshi;
Now, lets set the MaxFrames, add this to CApp_OnInit:
Anim_Yoshi.MaxFrames = 8;
If you want to see your animation Oscillate, set this:
Anim_Yoshi.Oscillate = true;
Okay, now to make our animation loop, add this to CApp_OnLoop:
Anim_Yoshi.OnAnimate();
Now, the last thing, to make it actually animate; add this to CApp_OnRender:
CSurface::OnDraw(Surf_Display, Surf_Test, 290, 220, 0, Anim_Yoshi.GetCurrentFrame() * 64, 64, 64);
Now try compiling, and watch your Yoshi workout! Make sure you replace the myimage.bmp with the Yoshi image.