欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于EasyX库的推箱子游戏(C语言)

程序员文章站 2022-07-03 17:59:23
文章目录前言主函数模块模块概述技术分析功能实现加载资源模块模块概述技术分析功能实现开始界面模块模块概述技术分析功能实现地图制作模块模块概述技术分析功能实现按键处理模块模块概述技术分析功能实现后退模块模块概述技术分析功能实现多功能模块模块概述技术分析功能实现后记前言​本推箱子游戏为小学期作品,从动手写到修改完善代码大概用了四五天,程序并不难,一些功能由于时间有限没有添加。本游戏是利用C语言基于Visual Studio 2013编写的,使用了EasyX库。玩家通过按键来实现游戏人物的上下左右移动及回到上...

前言

​ 本推箱子游戏为小学期作品,从动手写到修改完善代码大概用了四五天,程序并不难,一些功能由于时间有限没有添加。本游戏是利用C语言基于Visual Studio 2013编写的,使用了EasyX库。玩家通过按键来实现游戏人物的上下左右移动及回到上一步。玩家还可以通过鼠标来实现开始游戏,查看帮助,播放或暂停背景音乐,重新开始本关卡,返回上一关,跳过本关去下一关。

主函数模块

模块概述

​ 该模块首先调用几个子函数完成游戏的初始化,进入游戏的开始界面并且播放背景音乐。玩家开始游戏后进入循环体,反复调用几个子函数实现游戏的各功能。游戏通关后显示通关界面。

技术分析

1.MciSendString实现播放音乐

MciSendString函数可用来播放MP3文件。该函数有四个参数:
第一个参数:要发送的命令字符串。字符串结构是:[命令][设备别名][命令参数]。
第二个参数:返回信息的缓冲区,为一指定了大小的字符串变量。
第三个参数:缓冲区的大小,就是字符变量的长度。
第四个参数:回调方式,一般设为零。

在本游戏中,该函数后面三个参数都用的0。

2.实现子函数之间传参

主函数中定义了一个int型变量flag,当flag等于0时,代表此时背景音乐在播放;当flag等于1时,代表此时背景音乐暂停。将该变量在子函数中反复传递,以正确显示小喇叭的图标,避免出现图标闪烁的问题。

3.使用putimage函数

这个函数的几个重载用于在当前设备上绘制指定图像。

4.模拟对话框

使用了窗口句柄HWND及MessageBox函数来模拟一个对话框,当通过当前关卡时弹出此对话框,点击确认后进行下一步操作。

5.使用sprintf_s函数

该函数的功能是把格式化的数据写入某个字符串中,将其与模拟对话框结合,显示出当前通过的是那一关。

功能实现

void main()
{
	int flag = 0;
  	
	InitStack(sq);            
	hwnd = initgraph(64 * 8, 64 * 7);

	mciSendString("open 1.mp3", 0, 0, 0);
	mciSendString("play 1.mp3 repeat", 0, 0, 0);  //播放背景音乐

	load(); 
	mapload();
	start(flag);        

	while (1)
	{
		DrawMap(flag);

		if(flag == 1)
			putimage(7 * 64, 0, &v);

		multi(flag);

		Key();           
		
		if (gameover())
		{
			DrawMap(flag); 		
			char str[30];
			sprintf_s(str, "恭喜你,第%d关挑战成功!", cos + 1);
			MessageBox(hwnd, str, "恭喜", MB_OK);
			InitStack(sq);
			cos++;
			if (cos == 7)
				break;
		}

	}

	end();
	closegraph();
	system("pause");
}

加载资源模块

模块概述

​ 由于在主函数循环体的子函数中,将反复用到部分图片素材,该模块将部分图片素材加载到全局变量中,简化代码,避免重复加载。

技术分析

1.在本游戏的代码中,将sprintf_s函数和for循环结合使用,减少了代码量。

2.使用了loadimage函数,该函数为EasyX库中的一个函数,用于从文件中读取图像。该函数的声明如下:

void loadimage(
	IMAGE* pDstImg,			// 保存图像的 IMAGE 对象指针
	LPCTSTR pImgFile,		// 图片文件名
	int nWidth = 0,			// 图片的拉伸宽度
	int nHeight = 0,		// 图片的拉伸高度
	bool bResize = false	// 是否调整 IMAGE 的大小以适应图片);

功能实现

void load()
{
	for(int i = 0; i < 12; i++)
	{
		char filename[20] = "";
		sprintf_s(filename,"%d.jpg", imgindex[i]);
		loadimage(img + i, filename, 64, 64);
	}

	loadimage(&v, "v.jpg", 64, 64);
	loadimage(&h, "help.jpg", 64 * 8, 64 * 7);
	loadimage(&re, "re.jpg", 64, 64);
	loadimage(&L, "L.jpg", 64, 64);
	loadimage(&R, "R.jpg", 64, 64);
	loadimage(&q, "q.jpg", 64, 64);
}

开始界面模块

模块概述

该模块的功能是绘制出开始界面及实现在开始界面上进行的操作。

1.点击”START”开始游戏

玩家将鼠标移动到”START”区域时,”START”高亮显示,在该区域按下鼠标左键,正式进入推箱子游戏。

2.点击喇叭控制背景音乐播放或暂停

在喇叭区域按下鼠标左键,可以使音乐播放或暂停。

3.查看帮助

将鼠标停留在“?”区域时,可查看帮助文档。

以下为开始界面。墙的图片素材来自4399小游戏。文字及灰色图标为自制。

技术分析

该模块的关键之处有俩,一是根据鼠标操作来进行相应指令,二是小喇叭图标的变化。

1.鼠标操作

本游戏用到了一个MOUSEMSG变量。MOUSEMSG为EasyX库中的一个结构体,用于保存鼠标消息,定义如下:

struct MOUSEMSG
{
	UINT uMsg;			// 当前鼠标消息
	bool mkCtrl;		// Ctrl 键是否按下
	bool mkShift;		// Shift 键是否按下
	bool mkLButton;		// 鼠标左键是否按下
	bool mkMButton;		// 鼠标中键是否按下
	bool mkRButton;		// 鼠标右键是否按下
	int x;				// 当前鼠标 x 坐标(物理坐标)
	int y;				// 当前鼠标 y 坐标(物理坐标)
	int wheel;			// 鼠标滚轮滚动值
};

在本游戏中,用到了其中的uMsg,x, y。

使用GetMouseMsg函数获取一个鼠标消息。 WM_LBUTTONDOWN为左键按下消息。使用m.uMsg == WM_LBUTTONDOWN来判断鼠标左键是否按下,若按下则进行相应操作。

根据m.x及m.y来判断鼠标所在区域以进行相应操作。

2.小喇叭图标

为了使小喇叭图标随着鼠标操作而正确显示,使用了一个int型变量flag做标记。当flag等于0时,代表此时背景音乐在播放;当flag等于1时,代表此时背景音乐暂停。当flag等于1时,即显示静音后的喇叭。

功能实现

void start(int &flag)
{
	IMAGE s1, s2;
	MOUSEMSG m;

	loadimage(&s1, "start.jpg", 64 * 8, 64 * 7);
	loadimage(&s2, "start2.jpg", 64 * 2, 64);
	
	putimage(0, 0, &s1);        //绘制开始界面图像

	while (1)
	{
		m = GetMouseMsg();
		if (m.x > 3 * 64 && m.x < 5 * 64 && m.y > 4 * 64 && m.y < 5 * 64)//当鼠标移动到"START"上时,"START"高亮显示
		{	
			putimage(3*64, 4*64, &s2);
			if (m.uMsg == WM_LBUTTONDOWN)
				break;
		}
		else if (m.x > 7 * 64 && m.x < 8 * 64 && m.y > 0 && m.y < 64)//点击喇叭后暂停或播放背景音乐
		{	
			if (m.uMsg == WM_LBUTTONDOWN && flag == 0)      //flag==0代表背景音乐正在播放
			{
				mciSendString("pause 1.mp3", 0, 0, 0);				
				flag = 1;
			}
			else if (m.uMsg == WM_LBUTTONDOWN && flag == 1)   //flag==1代表背景音乐暂停播放
			{
				mciSendString("resume 1.mp3", 0, 0, 0);
				flag = 0;
			}
		}
		else if (m.x > 6 * 64 && m.x < 7 * 64 && m.y > 0 && m.y < 64)//鼠标停留在"?"时,查看帮助文档		
				putimage(0, 0, &h);
		else 
				putimage(0, 0, &s1);
				
		if (flag == 1)
			putimage(7 * 64, 0, &v);
	}
}

地图制作模块

模块概述

该模块的功能是实现地图的绘制及多关卡的设计。用数组来模拟推箱子的地图,使用三维数组来设计多关卡。本模块主要涉及两个子函数,分别是DrawMap()和mapload()。三维数组定义为全局变量map,方便对其操作。将初始地图放在子函数 mapload()中,游戏刚开始时使用该函数给地图数组map赋初值。当map数组值等于特定值时,该位置放置特定图像,以此完成地图的制作。

技术分析

1.为方便进行操作,地图数组定义为全局变量。在子函数DrawMap()中,使用switch语句,当数组元素等于特定值时,将该位置放入指定图像。

2.在子函数 mapload()中,使用memcpy函数给地图数组赋值。该函数头文件为“string.h”。

以下为游戏界面。

功能实现

void DrawMap(int flag)
{
	int x, y;

	for (int i = 0; i < 7; i++)
		for (int j = 0; j < 8; j++)
		{	
			x = 64 * j;
			y = 64 * i;
			switch (map[cos][i][j])
			{
			case 0: putimage(x, y, img + 0);     //空地
				break;
			case 1: putimage(x, y, img + 1);     //墙
				break;
			case 3: putimage(x, y, img + 2);     //目的地
				break;
			case 4: putimage(x, y, img + 3);     //箱子
				break;
			case 5:                              //人
			case 8: putimage(x, y, img + 4);     //人在目的地
				break;
			case 7: putimage(x, y, img + 5);     //箱子在目的地
				break;
			case 11:putimage(x, y, img + 6);     //上一关
				break;
			case 12:putimage(x, y, img + 7);     //下一关
				break;
			case 13:putimage(x, y, img + 8);     //问号
				break;
			case 14:putimage(x, y, img + 9);     //restart
				break;
			case 15:                             //喇叭
				if(flag == 1)
					putimage(x, y, img + 11);
				else 
					putimage(x, y, img + 10);
				break;
			default:
				break;
		}
	}
}

按键处理模块

模块概述

该模块的功能为玩家按下相应键来控制游戏人物的移动。玩家可以按方向键或者w,s,a,d/W,S,A,D来实现游戏人物的移动及推动箱子。

技术分析

1.不可见输入

使用_getch()函数,该函数是一个不回显函数,当玩家按下某个字符时,函数自动读取,无需按回车。

2.改变地图数组相应元素的值

使用switch来做相应选择。以游戏人物向上移动为例。若上一格是空地或目的地,将’w’进栈,将sq.box[sq.top]值赋0,当前位置值减5,代表人物离开此位置;上一格值加5,代表人物来到此位置。若上一格是箱子且上两格为空地或目的地,将’w’进栈,将sq.box[sq.top]值赋1,当前位置值减5,代表人物离开此位置;上一格值减4加5,代表箱子离开此位置,人物到达此位置;上两格值加4,代表箱子到达此位置。其他方向移动与向上移动类似。

功能实现

void Key()
{
	int i, j;

	for (i = 0; i < 7; i++)          //找出游戏人物所在位置
	{
		for (j = 0; j < 8; j++)
		{
			if (map[cos][i][j] == 5 || map[cos][i][j] == 8)
				break;
		}
		if (map[cos][i][j] == 5 || map[cos][i][j] == 8)
			break;
	}

	char userkey = _getch();               //不可见输入

	switch (userkey)                       //box值为0代表没有推动箱子,box为1代表推动箱子
	{
	case 'w':
	case 'W':
	case 72:
		if (map[cos][i - 1][j] == 0 || map[cos][i - 1][j] == 3) //上一格是空地或者目的地
		{
			map[cos][i - 1][j] += 5;
			map[cos][i][j] -= 5;
			Push(sq, 'w');
			sq.box[sq.top] = 0;
		}
		else if (map[cos][i - 1][j] == 4 || map[cos][i - 1][j] == 7)  //上一格有箱子
		{
			if (map[cos][i - 2][j] == 0 || map[cos][i - 2][j] == 3)  //上两格是空地或者目的地
			{
				map[cos][i][j] -= 5;
				map[cos][i - 1][j] += 1;     //减4(箱子走)加5(人来)
				map[cos][i - 2][j] += 4;
				Push(sq, 'w');
				sq.box[sq.top] = 1;
			}
		}
		break;
	case 's':
	case 'S':
	case 80:
		if (map[cos][i + 1][j] == 0 || map[cos][i + 1][j] == 3)
		{
			map[cos][i + 1][j] += 5;
			map[cos][i][j] -= 5;
			Push(sq, 's');
			sq.box[sq.top] = 0;
		}
		else if (map[cos][i + 1][j] == 4 || map[cos][i + 1][j] == 7)
		{
			if (map[cos][i + 2][j] == 0 || map[cos][i + 2][j] == 3)
			{
				map[cos][i][j] -= 5;
				map[cos][i + 1][j] += 1;     
				map[cos][i + 2][j] += 4;
				Push(sq, 's');
				sq.box[sq.top] = 1;
			}
		}
		break;
	case 'a':
	case 'A':
	case 75:
		if (map[cos][i][j - 1] == 0 || map[cos][i][j - 1] == 3)
		{
			map[cos][i][j - 1] += 5;
			map[cos][i][j] -= 5;
			Push(sq, 'a');
			sq.box[sq.top] = 0;
		}
		else if (map[cos][i][j - 1] == 4 || map[cos][i][j - 1] == 7)
		{
			if (map[cos][i][j - 2] == 0 || map[cos][i][j - 2] == 3)
			{
				map[cos][i][j] -= 5;
				map[cos][i][j - 1] += 1;     
				map[cos][i][j - 2] += 4;
				Push(sq, 'a');
				sq.box[sq.top] = 1;
			}
		}
		break;
	case 'd':
	case 'D':
	case 77:
		if (map[cos][i][j + 1] == 0 || map[cos][i][j + 1] == 3)
		{
			map[cos][i][j + 1] += 5;
			map[cos][i][j] -= 5;
			Push(sq, 'd');
			sq.box[sq.top] = 0;
		}
		else if (map[cos][i][j + 1] == 4 || map[cos][i][j + 1] == 7)
		{
			if (map[cos][i][j + 2] == 0 || map[cos][i][j + 2] == 3)
			{
				map[cos][i][j] -= 5;
				map[cos][i][j + 1] += 1;     
				map[cos][i][j + 2] += 4;
				Push(sq, 'd');
				sq.box[sq.top] = 1;
			}
		}
		break;
	case 'r':
	case 'R':back(i, j);
		break;
	default:
		break;
	}
}

后退模块

模块概述

​ 该模块利用栈FILO的特点来实现后退的功能。若栈不为空,则出栈一个元素;若为空,则直接返回。出栈后,分情况讨论。如果上一步没有推动箱子,则地图数组的值进行相应变化。如果上一步推动了箱子,则进行另一种变化。

技术分析

1.栈

栈FILO的特点适合进行后退操作。本模块包含了五个关于栈的子函数,有栈的初始化,判断栈空否,判断栈满否,入栈,出栈。

2.后退

若栈不为空,则出栈一个元素。出栈后,分情况讨论。以上一步为向上为例。如果上一步没有推动箱子,当前位置值减5,代表人物离开此位置;下一格值加5,代表人物来到此位置。如果上一步推动了箱子,当前位置值减1,代表人物离开此位置,箱子到达此位置;下一格值加5,代表人物到达此位置;上一格值减4,代表箱子离开此位置。其他方向移动与向上移动类似。

功能实现

void back(int i, int j)
{
	char a;
	int ifbox;

	if (!StackEmpty(sq))
	{
		Pop(sq, a);
		ifbox = sq.box[(sq.top) + 1];
	}
	else
		return;
	if (ifbox == 0)//0代表没有推动箱子,1代表推动箱子
	{
		switch (a)
		{
		case 'w':
			map[cos][i + 1][j] += 5;
			map[cos][i][j] -= 5;
			break;
		case 's':
			map[cos][i - 1][j] += 5;
			map[cos][i][j] -= 5;
			break;
		case 'a':
			map[cos][i][j + 1] += 5;
			map[cos][i][j] -= 5;
			break;
		case 'd':
			map[cos][i][j - 1] += 5;
			map[cos][i][j] -= 5;
			break;
		default:
			break;
		}
	}
	else if (ifbox == 1)
	{
		switch (a)
		{
		case 'w':
			map[cos][i][j] -= 1;
			map[cos][i + 1][j] += 5;
			map[cos][i - 1][j] -= 4;
			break;
		case 's':
			map[cos][i][j] -= 1;
			map[cos][i - 1][j] += 5;
			map[cos][i + 1][j] -= 4;
			break;
		case 'a':
			map[cos][i][j] -= 1;
			map[cos][i][j + 1] += 5;
			map[cos][i][j - 1] -= 4;
			break;
		case 'd':
			map[cos][i][j] -= 1;
			map[cos][i][j - 1] += 5;
			map[cos][i][j + 1] -= 4;
			break;
		default:
			break;
		}
	}
}

多功能模块

模块概述

该模块实现了游戏过程中的各种功能。首先检测是否有键盘输入,若有,则跳出循环。然后检测鼠标消息,根据鼠标操作来实现多种功能,功能有控制背景音乐的播放或暂停,重新开始本关卡,去上一关,去下一关及查看帮助。

技术分析

1._kbhit()函数

该函数功能为检查当前是否有键盘输入,若有则返回一个非0值,否则返回0。

2.MouseHit()函数

这个函数用于检测当前是否有鼠标消息。如果存在鼠标消息,返回 true;否则返回 false。

3.重置当前关卡

鼠标移动到指定区域时,re图标高亮显示。在此区域点击鼠标左键时,将地图数组初始化,重新绘制地图,栈初始化。

4.去上一关

若当前关卡不是第一关。当鼠标移动到指定区域时,图标高亮显示。在此区域点击鼠标左键时,关卡数减1,将地图数组初始化,重新绘制地图,栈初始化。

5.去下一关

若当前关卡不是第七关。当鼠标移动到指定区域时,图标高亮显示。在此区域点击鼠标左键时,关卡数加1,将地图数组初始化,重新绘制地图,栈初始化。

功能实现

void multi(int &flag)
{
	MOUSEMSG m;

	while (1)
	{
		if (_kbhit())	  //检查当前是否有键盘输入
			return;	
		if (MouseHit())   //检测当前是否有鼠标消息
		{
			m = GetMouseMsg();
			if (m.x > 7 * 64 && m.x < 8 * 64 && m.y > 0 && m.y < 64)
			{
				if (m.uMsg == WM_LBUTTONDOWN && flag == 0)
				{
					mciSendString("pause 1.mp3", 0, 0, 0);
					flag = 1;
				}
				else if (m.uMsg == WM_LBUTTONDOWN && flag == 1)
				{
					mciSendString("resume 1.mp3", 0, 0, 0);
					flag = 0;
				}
			}
			else if(m.x > 6 * 64 && m.x < 7 * 64 && m.y > 0 && m.y < 64) //restart
			{
				putimage(6 * 64, 0, &re);
				if (m.uMsg == WM_LBUTTONDOWN )
				{
					mapload();
					DrawMap(flag);
					InitStack(sq);
				}
			}		
			else if (m.x > 0 && m.x < 64 && m.y > 0 && m.y < 64)    //去上一关
			{
				if (cos != 0)
					putimage(0, 0, &L);
				if (m.uMsg == WM_LBUTTONDOWN && cos != 0)
				{
					cos--;
					mapload();
					DrawMap(flag);
					InitStack(sq);
				}
			}
			else if (m.x > 64 && m.x < 2 * 64 && m.y > 0 && m.y < 64)  //去下一关
			{
				if (cos != 6)
					putimage(64, 0, &R);
				if (m.uMsg == WM_LBUTTONDOWN && cos != 6)
				{
					cos++;
					mapload();
					DrawMap(flag);
					InitStack(sq);
				}
			}
			else if (m.x > 5*64 && m.x < 6 * 64 && m.y > 0 && m.y < 64) //查看帮助
				putimage(0, 0, &h);    
			else
				DrawMap(flag);					
		}
	}
}

后记

完整代码及图片素材待我上传github后,会将链接放到这里。

以下为用到的部分网站:

EasyX库官网

EasyX库使用入门

VS中使用 loadimage()函数载入图像报错与图像无法载入的解决办法

本文地址:https://blog.csdn.net/weixin_46874126/article/details/107314980