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.
Email (required, not published):
Website:
CSurface::OnDraw(Surf_Display, Surf_Test, 290, 220, Anim_Yoshi.GetCurrentFrame() * 64, 0, 64, 64);
Email (required, not published):
Website:
Email (required, not published):
Website:
if(Oscillate) {
...
}else{
if(CurrentFrame >= MaxFrames) {
CurrentFrame = 0;
}
}
The way it was (CurrentFrame >= MaxFrames - 1) the last frame wont be displayed, in this example the frame #7.
Email (required, not published):
Website:
Email (required, not published):
Website:
Email (required, not published):
Website:
D:\meh\CAnimation.cpp|19|warning: comparison between signed and unsigned integer expressions|
D:\meh\CAnimation.h||In constructor `Meh::Meh()':|
D:\meh\CAnimation.h|13|error: `int CAnimation::MaxFrames' is private|
D:\meh\meh.cpp|14|error: within this context|
D:\meh\CAnimation.h|14|error: `bool CAnimation::Oscillate' is private|
D:\meh\meh.cpp|15|error: within this context|
||=== Build finished: 4 errors, 1 warnings ===|
"Meh is CApp.."
Help me if you can..
Email (required, not published):
Website:
Sorry for the silly post. ^^"
Email (required, not published):
Website:
see link
http://imageshack.us/photo/my-images/411/ghosting.png/
Email (required, not published):
Website:
Email (required, not published):
Website:
SDL_FillRect(Surf_Display, NULL, SDL_MapRGB(Surf_Display->format, 0, 0, 0));
and yoshi looks better - no ghosts ;-)
Email (required, not published):
Website:
Email (required, not published):
Website:
Email (required, not published):
Website:
Email (required, not published):
Website:
I'm still having difficulties myself. I just seem to be putting the full image onto the screen instead of just the rectangle that represents the current frame.
Don
Email (required, not published):
Website:
Change your image size here.
Email (required, not published):
Website:
CSurface::OnDraw(Surf_Display, Surf_Image, 0, 0, 0, Anim_Yoshi.GetCurrentFrame() * H, H, W);
H stands for height, W for width (I guess in your case would that be * 73, 73, 70)
Email (required, not published):
Website:
OldTime = SDL_GetTicks();
to
OldTime = OldTime + FrameRate;
Email (required, not published):
Website:
Email (required, not published):
Website:
Email (required, not published):
Website:
So the problem about Yoshi walking strange is not wrong code. You just have to set Oscillate to false.
Thats my understanding of oscillation.
But i think Dave is right. It should be
if(CurrentFrame >= MaxFrames)
Email (required, not published):
Website:
Why no transparent Yoshi?
In CApp_OnInit.cpp:
CApp::OnInit(){
...
CSurface::Transparent(Surf_Test, 255,0,255);
}
Email (required, not published):
Website:
Email (required, not published):
Website: