动画的基本概念是先拿来一连串的图像,就像下面这张精灵图中的火柴人。
然后从左往右逐个展示,来创造运动的幻觉。
所以当你在SDL中使用动画时,实际上你就是在展示一系列的
然后从左往右逐个展示,来创造运动的幻觉。
所以当你在SDL中使用动画时,实际上你就是在展示一系列的
SDL_Surface.
//火柴人
class Foo
{
private:
//坐标
int offSet;
//移动速度
int velocity;
//当前帧
int frame;
//动画状态
int status;
public:
//初始化变量
Foo();
//处理输入
void handle_events();
//移动火柴人
void move();
//显示火柴人
void show();
};
这是我们将要在屏幕上移动的火柴人的类。
首先,我们有"offSet" 和 "velocity"变量。因为我们仅仅将火柴人向左或向右移动,所以我们仅仅跟踪x方向的坐标和速度。
然后,我们有"frame" 和 "status"变量。"frame" 指示动画中显示的是哪一帧。"status"指示了显示哪个动画,即Foo向左走的动画或Foo向右走的动画。
接着,我们当然有构造器、事件处理函数和移动及显示火柴人的函数。
首先,我们有"offSet" 和 "velocity"变量。因为我们仅仅将火柴人向左或向右移动,所以我们仅仅跟踪x方向的坐标和速度。
然后,我们有"frame" 和 "status"变量。"frame" 指示动画中显示的是哪一帧。"status"指示了显示哪个动画,即Foo向左走的动画或Foo向右走的动画。
接着,我们当然有构造器、事件处理函数和移动及显示火柴人的函数。
void set_clips()
{
//剪切子图
clipsRight[ 0 ].x = 0;
clipsRight[ 0 ].y = 0;
clipsRight[ 0 ].w = FOO_WIDTH;
clipsRight[ 0 ].h = FOO_HEIGHT;
clipsRight[ 1 ].x = FOO_WIDTH;
clipsRight[ 1 ].y = 0;
clipsRight[ 1 ].w = FOO_WIDTH;
clipsRight[ 1 ].h = FOO_HEIGHT;
clipsRight[ 2 ].x = FOO_WIDTH * 2;
clipsRight[ 2 ].y = 0;
clipsRight[ 2 ].w = FOO_WIDTH;
clipsRight[ 2 ].h = FOO_HEIGHT;
clipsRight[ 3 ].x = FOO_WIDTH * 3;
clipsRight[ 3 ].y = 0;
clipsRight[ 3 ].w = FOO_WIDTH;
clipsRight[ 3 ].h = FOO_HEIGHT;
clipsLeft[ 0 ].x = 0;
clipsLeft[ 0 ].y = FOO_HEIGHT;
clipsLeft[ 0 ].w = FOO_WIDTH;
clipsLeft[ 0 ].h = FOO_HEIGHT;
clipsLeft[ 1 ].x = FOO_WIDTH;
clipsLeft[ 1 ].y = FOO_HEIGHT;
clipsLeft[ 1 ].w = FOO_WIDTH;
clipsLeft[ 1 ].h = FOO_HEIGHT;
clipsLeft[ 2 ].x = FOO_WIDTH * 2;
clipsLeft[ 2 ].y = FOO_HEIGHT;
clipsLeft[ 2 ].w = FOO_WIDTH;
clipsLeft[ 2 ].h = FOO_HEIGHT;
clipsLeft[ 3 ].x = FOO_WIDTH * 3;
clipsLeft[ 3 ].y = FOO_HEIGHT;
clipsLeft[ 3 ].w = FOO_WIDTH;
clipsLeft[ 3 ].h = FOO_HEIGHT;
}
这个函数设置了精灵图中每个单独子图的剪切范围。
我们有两组子图,其中clipsRight剪切的子图是Foo向右走动画的帧,clipsLeft剪切的子图是Foo向左走动画的帧。
我们有两组子图,其中clipsRight剪切的子图是Foo向右走动画的帧,clipsLeft剪切的子图是Foo向左走动画的帧。
Foo::Foo()
{
//初始化移动参数
offSet = 0;
velocity = 0;
//初始化动画参数
frame = 0;
status = FOO_RIGHT;
}
在Foo类的构造器中,我们首先初始化坐标和速度。
然后我们设置动画在帧0的状态,并且我们将status设置为
然后我们设置动画在帧0的状态,并且我们将status设置为
FOO_RIGHT,这样默认的动画就是火柴人向右走的动画。
void Foo::move()
{
//移动
offSet += velocity;
//确保火柴人在屏幕范围内
if( ( offSet < 0 ) || ( offSet + FOO_WIDTH > SCREEN_WIDTH ) )
{
offSet -= velocity;
}
}
现在在move()函数中,我们首先采用一贯的做法移动火柴人并确保它在屏幕内。
void Foo::show()
{
//如果Foo向左走
if( velocity < 0 )
{
//设置动画为向左走的动画
status = FOO_LEFT;
//动画进入下一帧
frame++;
}
//如果Foo向右走
else if( velocity > 0 )
{
//设置动画为向右走的动画
status = FOO_RIGHT;
//动画进入下一帧
frame++;
}
//如果Foo站着不动
else
{
//重新启动动画
frame = 0;
}
火柴人移动后,是时候实现真正的动画了。首先我们检查它移动的方向。
如果它向左走,我们将status设为
如果它向右走,我们将status设为
如果火柴人是静止的,我们将frame设为0来重新启动动画。这样火柴人在静止站立时就不会看上去像迈了一半的步子了。
如果它向左走,我们将status设为
FOO_LEFT,然后增加帧计数器的值,这样动画中的下一个子图将被显示出来。
如果它向右走,我们将status设为
FOO_RIGHT,然后增加帧计数器的值,这样动画中的下一个子图将被显示出来。
如果火柴人是静止的,我们将frame设为0来重新启动动画。这样火柴人在静止站立时就不会看上去像迈了一半的步子了。
//动画循环
if( frame >= 4 )
{
frame = 0;
}
之后,我们检查帧计数器是否已经达到第四帧,由于动画中仅有4帧,如果帧计数器超过这个数,我们就重启动画以使火柴人在移动时保持动画的循环。
//显示火柴人
if( status == FOO_RIGHT )
{
apply_surface( offSet, SCREEN_HEIGHT - FOO_HEIGHT, foo, screen, &clipsRight[ frame ] );
}
else if( status == FOO_LEFT )
{
apply_surface( offSet, SCREEN_HEIGHT - FOO_HEIGHT, foo, screen, &clipsLeft[ frame ] );
}
}
最后,我们在屏幕上显示正确的子图。
如果火柴人在向右走,我们从向右走的动画中应用正确的子图;如果火柴人在向左走,我们从向左走的动画中应用正确的子图。
如果火柴人在向右走,我们从向右走的动画中应用正确的子图;如果火柴人在向左走,我们从向左走的动画中应用正确的子图。
//设置精灵图的剪切
set_clips();
//帧率校准器
Timer fps;
//创建火柴人
Foo walk;
在我们的main函数中,初始化和文件加载完毕后,我们设置精灵图的剪切,然后声明一个FPS计时器,接着声明一个火柴人对象。
//当用户还没退出
while( quit == false )
{
//启动帧计时器
fps.start();
//当有事件需要处理
while( SDL_PollEvent( &event ) )
{
//处理火柴人的事件
walk.handle_events();
//如果用户还没有叉掉窗口
if( event.type == SDL_QUIT )
{
//退出程序
quit = true;
}
}
//移动火柴人
walk.move();
//用白色填充屏幕
SDL_FillRect( screen, screen->clip_rect, SDL_MapRGB( screen->format, 0xFF, 0xFF, 0xFF ) );
//在屏幕上显示火柴人
walk.show();
//更新屏幕
if( SDL_Flip( screen ) == -1 )
{
return 1;
}
//捕获帧率
if( fps.get_ticks() < 1000 / FRAMES_PER_SECOND )
{
SDL_Delay( ( 1000 / FRAMES_PER_SECOND ) - fps.get_ticks() );
}
}
这里我们有main循环。它和前面课程中和Dot类一起使用的main循环基本一致。
因此,就像你看到的那样,对于一个动画引擎,你需要做的仅仅是跟踪你要使用的是哪个动画以及你要blit哪一帧。
因此,就像你看到的那样,对于一个动画引擎,你需要做的仅仅是跟踪你要使用的是哪个动画以及你要blit哪一帧。