基于EasyX库的推箱子游戏(C语言)
前言
本推箱子游戏为小学期作品,从动手写到修改完善代码大概用了四五天,程序并不难,一些功能由于时间有限没有添加。本游戏是利用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后,会将链接放到这里。
以下为用到的部分网站:
VS中使用 loadimage()函数载入图像报错与图像无法载入的解决办法
本文地址:https://blog.csdn.net/weixin_46874126/article/details/107314980