动画的基本概念是先拿来一连串的图像,就像下面这张精灵图中的火柴人。
然后从左往右逐个展示,来创造运动的幻觉。
所以当你在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哪一帧。