写给信息学竞赛选手的趣味编程 基于DevC++的SDL2图形程序设计(四) 三层汉诺塔的简单动画实现

汉诺塔问题是利用递归实现的经典程序问题。递归即函数自身调用自身的方法。虽然递归只能算是一种程序的实现方法而不是一种算法,但有许多算法都是可以用递归实现的,例如递推、递归搜索和分治等。
汉诺塔可以分为计数类问题(求搬动盘子次数)和构造问题(求搬动盘子过程)本课不讨论汉诺塔的程序实现,而是说明如何利用SDL2实现一个简单的三层汉诺塔移动过程的动画,如图所示。
本程序显示三层汉诺塔移动过程三次。核心问题是精灵的概念和坐标系的使用。
在这里插入图片描述

一、SDL中的相关概念

1.精灵spirit的概念

在动画制作中,精灵泛指动画中一切可以活动的部分。无论是主角、敌人还是背景中的一草一木,只要可以活动就可以认为是精灵。通常一个动画会有一个背景(background)图片,其余的部分都可以定义为精灵。例如,在汉诺塔动画中,有四幅图片,如下所示:
汉诺塔中的图片
其中,bg,jpg表示背景,其余b1、b2和b3都可以认为是精灵。

2.动画的概念

前面说过,在动画中每秒显示12帧以上连续的图像就是动画,但这样计算量和存储量都很巨大,是不得已而为之。在SDL程序设计中,动画的产生主要是精灵的改变。精灵的改变又分两种,一种是精灵图像的改变,一种是精灵位置的改变。
精灵图像的改变,可以借助一个精灵列表实现。所谓精灵列表就是在一幅精灵图像上有很多个精灵形象,每次选取图像中的不同形象以实现精灵图像的改变。如下例,就定义了一个人物的上下左右行走的过程。(说明:这个精灵列表来自于网络)
在这里插入图片描述

图4- 3一个精灵列表
精灵位置的改变,就是通过修改精灵的坐标,以实现在不同位置的显示。如汉诺塔动画就是通过每次改变精灵的位置来实现的。下面我们来具体说明。

3.程序中的坐标系

在数学中,我们定义平面直角坐标系来描述平面上每个点的位置。其中横轴为x轴且向右为正向;竖轴叫做y轴,向上为正向,这样我们就可以用(x,y)这样一对数据来描述平面上每个点的位置,如下图(a)所示。在计算机中的坐标系略有不同,就是y轴向下为正向,且x轴和y轴没有负数坐标,屏幕左上角对应(0,0)点,如下图(b)所示。

(a)                               (b)

图4- 4数学和计算机中的坐标系统

4. SDL中图像的描述

在SDL中如何描述一幅图像呢?通常采用四个参数,即:图像左上角的x轴坐标、图像左上角的y轴坐标,图像的宽和图像的高。如下图所示,其中红色图像可以表示为:
img1(x1,y1,w,h)
在这里插入图片描述

图4- 5 图像的描述
另外,在SDL中,坐标系是相对坐标系,所有精灵的坐标都是相对于窗口的而不是相对于屏幕的。即当新建一个窗口时,窗口的左上角坐标为(0,0)。

二、 程序基本原理

本程序基本原理是通过不断修改3个精灵的位置,以达到动画效果。因此有必要了解背景图片上的坐标。

1.背景图片坐标说明

在这里插入图片描述

图4- 背景图片坐标说明
背景图片坐标说明如图所示。由图可知如下信息:
 背景图片大小是宽640像素,高480像素;
 A柱上的盘子1左上角坐标为(75,190),宽78像素,高40像素;
 A柱上的盘子2左上角坐标为(55,230),宽118像素,高40像素;
 A柱上的盘子3左上角坐标为(35,270),宽158像素,高40像素;
 从A柱子平移到B柱子,需要x轴相应增加190;
 从B柱子平移到C柱子,需要x轴相应增加190;
 从A柱子平移到C柱子,需要x轴相应增加380;
由上述说明,我们可以把各个盘子在不同柱子上的位置,抽象成一个3列3行的表格,如下图所示,其中行高=40,列宽=190,注意x轴表示列,y轴表示行,为了与坐标顺序对应所以在后面描述位置时列在前,行在后。这样描述盘子移动过程更加直观。例如:初始时盘子1坐标为(x1,y1),位置在第1列第1行,当把盘子移动到第2列第3行时,即在原有基础上增加了1列,2行,所以盘子1的坐标变为(x1+1190,y1+240)。
在这里插入图片描述

2.三层汉诺塔移动过程说明

三层汉诺塔移动7步,包含初始化共显示8幅图像,每次图像显示都要修改三个盘子的位置,同时每个盘子的位置都是相对初始化时位置的修改。
在这里插入图片描述

可以使用打表方法记录每次盘子移动的位置。
定义数组InitPos记录三个盘子的初始坐标,定义如下:
int InitPos[6]={75,190,55,230,35,270};
其中:
(75,190)是盘1的初始x和y坐标;
(55,230)是盘2的初始x和y坐标;
(35,270)是盘3的初始x和y坐标。
定义数组pos记录8幅图像中,每次3个盘子的位置改变值,定义如下:
int pos[8][6]={
{0,0,0,0,0,0},//初始化
{2,2,0,0,0,0},//第1步
{2,2,1,1,0,0},//第2步
{1,1,1,1,0,0},//第3步
{1,1,1,1,2,0},//第4步
{0,2,1,1,2,0},//第5步
{0,2,2,0,2,0},//第6步
{2,0,2,0,2,0}};//第7步
注意:两个数组一个记录坐标,一个记录位置。

三、 编写程序

1.Update()函数

函数Update()循环8次,每次修改一幅图像中3个盘子的坐标并调用OnDraw()函数绘图。

1.	void Game::Update(){
2.		for(int i=0;i<=7;i++){
3.			int p[6]={};//定义三个盘子的坐标,必须放在循环中进行初始化 
4.			for(int j=0;j<6;j+=2){//计算三个盘子的坐标 
5.			  p[j]=InitPos[j]+pos[i][j]*190;
6.	      	  p[j+1]=InitPos[j+1]+pos[i][j+1]*40; 
7.			}
8.			OnDraw(p); //显示图像 
9.	    }
10.	}

需要说明的是第47行的循环,由于数组InitPos都是用下标05来描述3个盘子的x和y坐标,而每次需要同时处理一个盘子的x和y,所以循环3次。且每增加1列即增加190个像素,每增加1行即增加40个像素。
P[j]为盘子i的x坐标=起始坐标+列位置改变量190;
P[j+1]是盘子i的y坐标=起始坐标+行位置改变量
40;

2.OnDraw()函数

函数OnDraw()用于显示一副图像,包括一个背景和3个精灵(盘子),代码如下:

1.	void Game::OnDraw(int p[]){
2.		const char*s[4]={"bg.jpg","b1.bmp","b2.bmp","b3.bmp"};
3.		int W[]={0,78,118,158};
4.		SDL_Surface *b[4];
5.		SDL_Texture *bt[4];
6.		for(int i=0;i<4;i++){//载入图像创建纹理 
7.			b[i]=IMG_Load(s[i]);
8.			bt[i]=SDL_CreateTextureFromSurface(r,b[i]);
9.		}
10.		SDL_RenderClear(r);
11.	    SDL_RenderCopy(r,bt[0], NULL, NULL);//载入背景 
12.	    for(int i=1;i<=3;i++){//载入三个精灵(盘子) 
13.	    	SDL_Rect rect1;
14.	    	rect1.x=p[(i-1)*2+0];
15.	    	rect1.y=p[(i-1)*2+1];
16.	    	rect1.w=W[i];
17.	    	rect1.h=40; 
18.	    	SDL_RenderCopy(r,bt[i], NULL, &rect1);
19.	    } 
20.	    SDL_RenderPresent(r);
21.	    for(int i=0;i<4;i++){
22.	     	 SDL_FreeSurface(b[i]);
23.	     	 SDL_DestroyTexture(bt[i]);
24.	    }
25.	    SDL_Delay(1000);  
26.	}

该函数基本过程见第2讲的显示一幅图像部分。
其中第13~17行,每次定义了一个矩形区域rect1,用于填充目标渲染器。由于位置坐标p从0开始,而精灵从下标1开始且包含x和y两个坐标(下标0表示背景图)。所以取值如下:
rect1.x=p[(i-1)2+0];
rect1.y=p[(i-1)2+1];
第28行图像载入渲染器函数说明如下:
int SDL_RenderCopy(SDL_Renderer
renderer, SDL_Texture
texture,
const SDL_Rect* srcrect, const SDL_Rect* dstrect)
参数说明:
Renderer表示渲染器指针
Texture表示纹理指针
Srcrect 表示获取源纹理的一个矩形区域填充渲染器,NULL表示整个纹理
Dstrect表示用纹理填充目标渲染器的一个矩形区域,NULL表示这个渲染器。
返回值:返回值为0表示复制成功,为一个负数表示操作失败。

3.完整代码

代码清单1 三层汉诺塔简单动画实现

1.	#include "SDL.h"
2.	#include "SDL_image.h"
3.	int InitPos[6]={75,190,55,230,35,270};
4.	int pos[8][6]={{0},{2,2,0,0,0,0},{2,2,1,1,0,0},{1,1,1,1,0,0},
5.	{1,1,1,1,2,0},{0,2,1,1,2,0},{0,2,2,0,2,0},{2,0,2,0,2,0}};
6.	class Game{ 
7.	    private:
8.	        SDL_Window *w;
9.	        SDL_Renderer* r;
10.	    public:
11.	        Game();
12.	        ~Game();
13.	        void Init();//初始化 
14.	        void EveProc(int&);//预留接口 
15.	        void Update();//后台操作 
16.	        void OnDraw(int p[]);//前台显示  
17.	}; 
18.	Game::Game(){
19.	    SDL_Init(SDL_INIT_EVERYTHING);  
20.	    w= SDL_CreateWindow("hanoi", SDL_WINDOWPOS_CENTERED, 
21.	        SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
22.	    r= SDL_CreateRenderer(w, -1, SDL_RENDERER_ACCELERATED 
23.	        | SDL_RENDERER_PRESENTVSYNC);
24.	    SDL_SetRenderDrawColor(r,255,255,255,255) ;
25.	} 
26.	Game::~Game(){
27.	    SDL_DestroyRenderer(r);
28.	    SDL_DestroyWindow(w);
29.	    SDL_Quit();
30.	} 
31.	void Game::Init(){  
32.	    SDL_Surface *bg =IMG_Load("bg.jpg");//文本表面
33.	    SDL_Texture *bgt = SDL_CreateTextureFromSurface(r, bg);//文本纹理
34.	    SDL_RenderClear(r);
35.	    SDL_RenderCopy(r,bgt, 0, 0);
36.	    SDL_RenderPresent(r);
37.	    SDL_Delay(1000);
38.	    SDL_FreeSurface(bg);
39.	    SDL_DestroyTexture(bgt);
40.	}
41.	void Game::Update(){
42.	    for(int i=0;i<=7;i++){
43.	        int p[6]={};//定义三个盘子的坐标,必须放在循环中进行初始化 
44.	        for(int j=0;j<6;j+=2){//计算三个盘子的坐标 
45.	          p[j]=InitPos[j]+pos[i][j]*190;
46.	          p[j+1]=InitPos[j+1]+pos[i][j+1]*40; 
47.	        }
48.	        OnDraw(p); //显示图像 
49.	    }
50.	} 
51.	void Game::OnDraw(int p[]){
52.	    const char*s[4]={"bg.jpg","b1.bmp","b2.bmp","b3.bmp"};
53.	    int W[]={0,78,118,158};
54.	    SDL_Surface *b[4];
55.	    SDL_Texture *bt[4];
56.	    for(int i=0;i<4;i++){//载入图像创建纹理 
57.	        b[i]=IMG_Load(s[i]);
58.	        bt[i]=SDL_CreateTextureFromSurface(r,b[i]);
59.	    }
60.	    SDL_RenderClear(r);
61.	    SDL_RenderCopy(r,bt[0], NULL, NULL);//载入背景 
62.	    for(int i=1;i<=3;i++){//载入三个盘子 
63.	        SDL_Rect rect1;
64.	        rect1.x=p[(i-1)*2+0];
65.	        rect1.y=p[(i-1)*2+1];
66.	        rect1.w=W[i];
67.	        rect1.h=40; 
68.	        SDL_RenderCopy(r,bt[i], NULL, &rect1);
69.	    } 
70.	    SDL_RenderPresent(r);
71.	    for(int i=0;i<4;i++){
72.	         SDL_FreeSurface(b[i]);
73.	         SDL_DestroyTexture(bt[i]);
74.	    }
75.	    SDL_Delay(1000);  
76.	}  
77.	int main(int argc, char* argv[]){
78.	    Game game;
79.	    for(int i=0;i<3;i++){
80.	        game.Init(); 
81.	        game.Update(); 
82.	    }   
83.	    return 0;
84.	}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值