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

本程序通过敲击键盘上的“向下箭头”键或者在窗口中的鼠标左键单击来控制汉诺塔移动的移动。实际上如果有键盘或鼠标控制的动画就相当于是游戏了,可以认为游戏就是带有交互的动画。当然,这只是一种简单理解,大部分游戏是要有博弈在其中的,毕竟没有胜败的游戏是不好玩的。
在这里插入图片描述

一、 SDL中事件的概念

大部分操作系统如Windows、Linux和MacOS等,如果开发窗口程序,都主要是基于消息(Message)和事件(Event)的。简单地说,消息是用户或系统的输入部分,在窗口程序开发中,涉及消息循环的处理函数,例如鼠标单击或键盘某个键按下等。而事件是基于消息的,是消息到达后触发的操作。可以简单理解事件是一个函数,是操作系统收到消息后所做的反应。例如当您用鼠标左键单击窗口右上角关闭按钮是,产生了鼠标左键单击的消息,这时窗口关闭这就是一个事件,即执行了关闭操作。
在SDL中好像不提消息,所有的操作都是事件鼠标操作就是鼠标事件、键盘操作就是键盘事件等。在前面介绍的动画制作中利用循环显示了3次三层汉诺塔的动画过程,这个过程中每幅图形通过延时函数显示了1秒。如果不利用循环而是使用键盘或鼠标去控制每幅图片的显示,该如何做呢?这个时候就需要事件了。所谓事件相当于是程序的输入,键盘事件是敲击键盘,鼠标事件就是单击或双击鼠标键,窗口事件就是窗口的改变,如最大化,还原等。在SDL中,将所有的事件都存放在一个事件队列中,然后通过一个循环从队列中取出事件,进行处理,所有对事件的操作,就是对队列的操作。

二、事件处理的实现

EveProc()函数是在Game类中预留的事件处理函数。消息事件的处理都是在这个函数中进行的。本程序中定义了三个事件处理功能,即关闭窗口事件、窗口鼠标左键单击事件和键盘的向下箭头按键事件。

1. 关于窗口关闭功能的实现

功能描述:利用鼠标左键单击窗口右上角关闭(x)按钮,实现窗口关闭。
实现方法:主要通过在Game类中定义一个窗口start公有数据成员来标记窗口的状态,当start=1即表示窗口运行,=0表示窗口关闭,程序首先将start设为1,在事件处理中遇到关闭窗口消息就将其设置为0;然后在主函数中根据start的状态决定是否结束程序。在程序中的修改主要包括四个部分:
(1)类中添加公有数据成员start,并修改EveProc函数参数,如图所示:
在这里插入图片描述
说明:数据成员公有好像不太符合类设计的思想,但确实可以这样用的。
(2)在构造函数中设置start=1,如图所示:
在这里插入图片描述
(3)添加EveProc()函数定义,在其中加入消息处理,如图所示:
在这里插入图片描述
其中:
首先,定义了一个SDL接收事件变量e;
然后,while循环调用SDL_PollEvent(&e)事件轮询函数,反复接收消息;
最后,Switch…case结构用于判断接收到的消息类型是否是SDL_QUIT(表示关闭窗口的消息),如果是则修改Start。
这个while+switch…case结构可以作为接受消息检测的模板,后面直接使用即可。
说明:在SDL中可以处理的事件包括Window窗口相关的事件、键盘相关的事件、鼠标移动相关的事件、退出事件和用户自定义事件。
(4)修改主函数,如图所示:

在这里插入图片描述
此时,根据game.Start的状态决定程序是否继续运行。
运行该程序后,汉诺塔移动的动画消失,但却多了一个关闭窗口功能。

2. 键盘事件的处理

功能描述:利用键盘上的“向下箭头“键控制动画一幅一幅显示。
实现方法:通过在EveProc()中添加键盘处理事件,循环监听是否有“向下箭头“键按下,如果有则调用Update()修改盘子参数再显示。
这个过程中要注意两点:
(1)由于原来Update()是利用i循环显示8幅图片的,现在变为一次显示1幅图片,因此需要去掉i循环;同时在类中定义私有的数据成员i,用于传递显示的是第几幅图片。
(2)由于有了事件监听处理,所以原理图片显示过程中的延时不再需要了,可以去掉;
具体操作分为6步,介绍如下:
(1)修改Game类添加数据成员i用于控制显示的图片数,同时修改函数参数,如图所示:
在这里插入图片描述
说明:i是私有成员只能在类的成员函数中使用,Start是公有成员可以在类外使用。
(2)构造函数中初始化私有成员,如图所示:
在这里插入图片描述
(3)去掉Init()函数中的显示延时操作,如图所示:
在这里插入图片描述
(4)修改EveProc()函数添加键盘事件处理,如图所示:
在这里插入图片描述
其中,首先判断是否是SDL_KEYDOWN键盘按下事件,如果是则通过e.key.keysym.sym来获得按键类型,如果是SDLK_DOWN“向下箭头”按键,则调用Update()函数,其参数i表示第i幅图,在修改坐标并显示图片后,将i增加1个,因为共有8幅图(0~7),所以需要当i大于7时将i置为0,重新开始显示。
说明:
1)键盘事件主要分两种:SDL_KEYDOWN、SDL_KEYUP(键盘按下和抬起)。
2)按键类型很多包括组合按键和单独按键,这里介绍常用的单独按键包括:
在这里插入图片描述
注意:很奇怪的是,有些按键需要按下大小写锁定键才有效。
(5)修改Update函数变成一次修改显示一幅图像,如图所示:
在这里插入图片描述
(6)最后去掉OnDraw()函数中的延时操作,即可。

3.鼠标事件的处理

有了前面的铺垫,实现鼠标事件很简单了,只需在EveProc()函数中添加鼠标事件处理即可。如图所示:
在这里插入图片描述
鼠标事件可以是SDL_MOUSEMOTION(鼠标移动),SDL_MOUSEBUTTONDOWN(鼠标按下),SDL_MOUSEBUTTONUP(鼠标抬起)和SDL_MOUSEWHEEL(鼠标移动)类型。具体各种类型如何使用,可以从网上自行搜索。这里介绍一个常用的操作:
SDL_GetMouseState(&x,&y);
用于获取鼠标左键点击处的x坐标和y坐标。
利用这个函数加上输出操作,可以在程序开始时帮助你很好的了解背景图各部分的坐标。

三、 完整代码

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值