本人编程小白,在学完C语言后尝试学习制作了推箱子小游戏,途中收获颇多,在此对于整个过程做一回顾梳理,若有写的不对的地方,还望各位大佬指正。
本篇文章为1.0版本,没有关卡,但拥有推箱子游戏较核心的功能。
目录
一、开始前的准备
二、游戏设计思路梳理
?1.小Tip
2.概述
三、详细过程
1.地图绘制
2.玩家移动模块
3.输赢判断模块
4.开始页面及音乐的添加
5.main函数部分
?四、效果展示
五、作者的话
一、开始前的准备
????????本文使用的工具是vs2010+easyx编程。由于我之前没有安装easyx,所以去官网进行了安装。
官网链接:进入官网后,找到 下载easyx 并按照提示完成安装。点击进入官网
备注:安装时最好安装帮助文档,并去网上对于easyx进行简单的学习,了解常见函数的用法,以便更好地操作。
帮助文档:(可通过该文档了解学习)点击进入
二、游戏设计思路梳理
?1.小Tip
????????首先我们可以分析一下这个游戏需要哪些模块,比如地图绘制,开始页面,玩家移动模块,输赢判断模块等等,再大概想一想各个模块都需要用到哪些东西,做一些准备,然后再开始上手。
2.概述
在概述这里简单说一下各模块都要什么:
1.地图绘制? 通过easyx的相关功能把图片绘制出来,当然,要提前准备好素材以及地图(可以在网上随便搜一个)。
2.开始页面? ? 这个页面看需求吧,需要的话可以根据自己的喜好设计一下。
3.玩家移动模块? ?这里要涉及到移动设计的逻辑问题,玩家在什么情况下可以移动,最简单的就是玩家前面是空地或者目的地,这个可以直接过去,其次就是当玩家面前有箱子(或箱子在目的地上)时,这就需要考虑箱子能不能推动,若箱子后面是空地或目的地,那可以过,若后面是墙或者还是箱子,这就过不去。设计的核心就在这里。
4.输赢判断模块? ? 这里可设定的规则挺多,时间限制,步数限制,或者死角无法推动(本文用的这一个)等等,可以自行设计。
三、详细过程
1.地图绘制
? ? ? ? 首先我们要用图片加载函数把图片加载,然后才能开始在控制台上贴图。
IMAGE img[6];//图片对象指针
void begin()
{
loadimage(&img[0],L"land.jpg",60,60);//加载图片
loadimage(&img[1],L"wall.jpg",60,60);//(IMAGE 对象指针,图片文件名,宽度,高度)
loadimage(&img[2],L"BoxYellow.jpg",60,60);
loadimage(&img[3],L"ManDOWN1.jpg",60,60);
loadimage(&img[4],L"aim.jpg",60,60);
loadimage(&img[5],L"BoxRed.jpg",60,60);//箱子在目的地时
}
加载完图片,就该绘制地图。但在此之前,我们需要规定一些数字来表示图片,以方便后续操作。
我们做出如下规定:
空地 ?0? ,墙 ?1,? 箱子 ?2,? 人物 ?3,? 目的地 ?4, 很自然的, 人站在目的地 就是 3+4=7,而箱子到达目的地 便为??2+4=6。
?规定完图片之后,我们就可以用数字把地图先搞出来:(地图上网随便找一个,按规定来就行)
int map[11][13] = { //初始化地图
0,0,0,1,1,1,1,1,1,1,0,0,0,
1,1,1,1,0,0,0,0,0,1,0,0,0,
1,0,0,0,4,1,1,1,0,1,0,0,0,
1,0,1,0,1,0,0,0,0,1,1,0,0,
1,0,1,0,2,0,2,1,4,0,1,0,0,
1,0,1,0,0,6,0,0,1,0,1,0,0,
1,0,4,1,2,0,2,0,1,0,1,0,0,
1,1,0,0,0,0,1,0,1,0,1,1,1,
0,1,0,1,1,1,4,0,0,0,0,0,1,
0,1,0,0,0,0,0,1,1,0,0,3,1,
0,1,1,1,1,1,1,1,1,1,1,1,1
};
这下万事俱备,可以开始贴图,造地图了。
void drawmap()
{
BeginBatchDraw();// 防止贴图过快
for(int i=0;i<11;i++)//行
{
for(int j=0;j<13;j++)//列
{
//每行单独贴图
switch(map[i][j])
{
case 0://空地
putimage(j*60,i*60,&img[0]);//(x坐标,y坐标,对象指针)
break;
case 1://墙
putimage(j*60,i*60,&img[1]);
break;
case 2://箱子
putimage(j*60,i*60,&img[2]);
break;
case 3:
case 7://人物
putimage(j*60,i*60,&img[3]);
break;
case 4://目的地
putimage(j*60,i*60,&img[4]);
break;
case 6://箱子到达目的地
putimage(j*60,i*60,&img[5]);
break;
default:
break;
}
}
}
EndBatchDraw();
}
备注:1.有关坐标的信息,可以在帮助文档中学习了解。
? ? ? ? ? ? 2.在绘制地图时,不要忘记加上easyx的头文件:
#include<easyx.h>
//或者这个#include <graphics.h>
? ? ? ? ? ? ?3.在贴图前要注意把素材图片和项目代码放在同一个文件夹。
?到了这里,地图的绘制就基本结束了。下面开启第二部分,也是最重要的一部分,玩家移动。
2.玩家移动模块
? ? ? ? 想要让玩家移动,首先我们要对玩家进行定位。
//找人的位置
int x,y;
for(int i=0;i<11;i++)
{
for(int j=0;j<13;j++)
{
if(map[i][j] == 3|| map[i][j] == 7)
{
x=i;
y=j;
}
}
}
有了玩家的位置之后,我们便开始设计移动。
这里我们首先要获取玩家的按键,从而让玩家能够控制人物移动。
获取按键:这里要用到getch()? ,不要忘记添加头文件
#include<conio.h> //getch() 头文件
?然后便开始设计按键的移动。这里需要注意的是:我们是通过数字的加减来实现玩家的移动的。
即:当玩家离开当前位置时,该位置减去3,而玩家即将到达的位置加上3。
然后我们开始设计移动,当玩家想要向上移动时,我们分情况来看:(在这里我们可以只考虑什么情况下玩家可以移动,不需要考虑无法移动的情况)
第一种:当玩家上方是空地或目的地时,玩家可以直接移动。
if(map[x-1][y]==0||map[x-1][y]==4) //人上方是空地或目的地
{
map[x-1][y]+=3;//人走上去 +3
map[x][y]-=3;//原位置-3
}
第二种:当玩家上方为箱子或箱子在目的地上时,有两种可能性:
? ? ? ? ? ? ? ? 1.箱子上方为空地或目的地,人可以和箱子一起上去;
? ? ? ? ? ? ? ? 2.箱子上面为箱子或者墙壁(此时玩家不能移动,这种情况我们不做考虑)
if(map[x-1][y]==2||map[x-1][y]==6) //上方为箱子或箱子在目的地上
{
if(map[x-2][y]==0||map[x-2][y]==4) //箱子上方是空地或目的地
{
//人走上去,箱子也推上去
map[x-2][y]+=2;
map[x-1][y]+=1;//-2 +3 --+1
map[x][y]-=3;
}
}
到这个时候,我们便完成了向上这一按键的移动设计,而玩家也可以顺利向上移动。
至于向左,向右,向下三种情况与之类似,只需要改动一些坐标,所以我们不做详细分析,仅提供源代码以供参考。
玩家移动设计函数的完整代码:(这里我们使用wasd进行移动操作)
switch(getch())
{
case 'W':
case 'w':
//往上
if(map[x-1][y]==0||map[x-1][y]==4) //人上方是空地或目的地
{
map[x-1][y]+=3;//人走上去 +3
map[x][y]-=3;//原位置-3
}
else if(map[x-1][y]==2||map[x-1][y]==6) //上方为箱子或箱子在目的地上
{
if(map[x-2][y]==0||map[x-2][y]==4) //箱子上方是空地或目的地
{
//人走上去,箱子也推上去
map[x-2][y]+=2;
map[x-1][y]+=1;//-2 +3 --+1
map[x][y]-=3;
}
}
break;
case 'S':
case 's':
//往下
if(map[x+1][y]==0||map[x+1][y]==4) //人下方是空地或目的地
{
map[x+1][y]+=3;//人走下去 +3
map[x][y]-=3;//原位置-3
}
else if(map[x+1][y]==2||map[x+1][y]==6) //下方为箱子或箱子在目的地上
{
if(map[x+2][y]==0||map[x+2][y]==4) //箱子下方是空地或目的地
{
//人走下去,箱子也推下去
map[x+2][y]+=2;
map[x+1][y]+=1;//-2 +3 --+1
map[x][y]-=3;
}
}
break;
case 'A':
case 'a':
//往左
if(map[x][y-1]==0||map[x][y-1]==4) //人左方是空地或目的地
{
map[x][y-1]+=3;//人往左走 +3
map[x][y]-=3;//原位置-3
}
else if(map[x][y-1]==2||map[x][y-1]==6) //左方为箱子或箱子在目的地上
{
if(map[x][y-2]==0||map[x][y-2]==4) //箱子左方是空地或目的地
{
//人往左走,箱子也推向左边
map[x][y-2]+=2;
map[x][y-1]+=1;//-2 +3 --+1
map[x][y]-=3;
}
}
break;
case 'D':
case 'd':
//往右
if(map[x][y+1]==0||map[x][y+1]==4) //人右方是空地或目的地
{
map[x][y+1]+=3;//人往右走 +3
map[x][y]-=3;//原位置-3
}
else if(map[x][y+1]==2||map[x][y+1]==6) //右方为箱子或箱子在目的地上
{
if(map[x][y+2]==0||map[x][y+2]==4) //箱子右方是空地或目的地
{
//人往右走,箱子也推向右边
map[x][y+2]+=2;
map[x][y+1]+=1;//-2 +3 --+1
map[x][y]-=3;
}
}
break;
default :
break;
}
备注:移动函数为模块的核心,至关重要。
?此时玩家移动的设计便可以告一段落了,我们即将开始第三部分,输赢的判断。
3.输赢判断模块
有了地图,并且玩家可以移动,我们的游戏便成功了一半,而另一半来自于输赢的判断,大家玩游戏都希望赢,所以一个判断标准也很重要。上面我有提到几种可能性,在这里我用的标准是 :??
【箱子碰到死角无法推动】为输,【地图上没有箱子】为赢
在这里我们可以用变量来标记三种状态:flag=0 游戏继续;flag=1 游戏失败;flag=2,游戏获胜。
下面做分类讨论:?
首先是赢的条件:当地图上没有箱子时,游戏即获胜。即数字里面找不到2时;
其次是输的情况:要构成死角,则至少四个面中要有两个面是墙,如此之下,便无法移动。
下面是输赢判断标准部分的代码:
int flag;
void judge()
{
flag=2;//初值
for(int i=0;i<11;i++)//行
{
for(int j=0;j<13;j++)//列
{
if(map[i][j]==2)
{
//表明未结束
flag=0;
if(map[i-1][j]==1||map[i+1][j]==1)
{
//找死角的墙
if(map[i][j-1]==1||map[i][j-1]==1)
{
//游戏结束
flag=1; //flag 分类
return; //=0 继续
//=1 结束
//=2 赢
}
}
}
}
}
}
?有了判断标准,我们还需要提示框来告知玩家他是输是赢,所以需要再加上一些提示盒子。
void box()
{
if(flag==1)
{
MessageBox(GetHWnd(),L"抱歉,你输了。",L"LOSE",MB_OK);//头文件为windows.h 有图形库时可不加
}
else
{
MessageBox(GetHWnd(),L"恭喜,你赢了!",L"VICTORY",MB_OK);
}
}
备注: 这里我们用到了MessageBox函数,使用该函数后将会弹出提示窗体,方便玩家看到结果,至于该函数的详细信息可以自行了解。
到了这里,整个输赢判断模块便真正结束了,而整个游戏的模块也基本成型了,下面我们将再添加一些优化,比如开始页面的添加,音乐的添加等。
4.开始页面及音乐的添加
? ? ? ? ? ? ? ?我们在最开始可以添加一个开始页面,给玩家一些提示,并且进一步完善游戏的模块。
备注:该模块的添加可根据需求自行设计。
我所添加的开始页面代码如下:
IMAGE iae;
void tip()
{
loadimage(&iae,L"logo.jpg",780,360);
putimage(0,0,&iae);
settextcolor(LIGHTCYAN);
settextstyle(24,0,_T("黑体"));
outtextxy(240,360,_T("开始游戏 请按 B/b "));
outtextxy(240,420,_T("移动请按 wasd/WASD"));
outtextxy(240,480,_T("!试试把箱子推到目的地!"));
outtextxy(480,600,_T("------拾光异世出品------"));
}
备注:这里有用到在图形库下的文字输出及格式和颜色设置。?
除了开始页面,我们还可以加一些音乐,比如游戏进行时的音乐,玩家移动时的音乐,游戏胜利时的音乐等。
在添加音乐之前我们需要添加一些头文件和库:
//加音乐的头文件与库
#include<mmsystem.h>
#pragma comment(lib,"WINMM.LIB")
然后是加音乐:可以分别在图片加载函数,移动设计函数,提示盒子函数里添加相对应的音乐。
//图片加载函数
mciSendString(L"open back.mp3 alias bgm",0,0,0);//具体用法可从帮助文档中了解
mciSendString(L"play bgm repeat",0,0,0);
//移动设计函数
PlaySound(L"Boxmove.WAV",nullptr,SND_FILENAME | SND_ASYNC);
//提示盒子函数
PlaySound(L"success.wav",nullptr,SND_FILENAME | SND_ASYNC);
进行完上述四个部分,我们便完成了大部分的设计任务,只剩下最后一步,把各个模块之间通过main函数组合起来。
5.main函数部分
在用main函数组合之前,我们需要考虑一个问题,我们要如何组织这几大模块,
仔细思考,应该先是开始页面,再然后的话,因为我们是要每移动一次便绘制一次地图,所以应该是在一个循环里面绘图(初始化),移动,绘图。判断,移动······
main函数部分代码(考虑到与开始页面的衔接,我们可以加一些衔接代码):
int main()
{
initgraph(780,660);//每个方块大小60*60 窗口大小780*660
tip();
if(getch()=='B'||getch()=='b')
{
begin();
while(1)
{
drawmap();
judge();
if(flag) break;//判断是否结束
play();
}
box();
}
getchar();
closegraph();//关闭窗口
return 0;
}
备注:用easyx进行绘图时,要先建立绘图窗口,并且在游戏结束后关闭该窗口。
?在将几大模块组合在一起时,整个设计过程便可以说接近尾声,我们的游戏便可以正常运行了。
?四、效果展示
最后我们一起看看效果图:
?
如果需要素材或者参考源码的话,可以通过网盘提取:
链接:https://pan.baidu.com/s/153nVLE8htqaeDuBSzYuyMA? 提取码:a8u1?
五、作者的话
? ? ? ? ? ? ? ? 虽然说上面的游戏到最后仍然很简陋,有着各种问题,诸如无法重新开始,判断机制不够完善等,但是也实现了推箱子游戏的最基本的功能,任务呢,也算是圆满完成啦。至于后续的优化,代码添加,我会继续努力的!!!
最后再说一句:希望自己在学习编程的道路上继续前进!? 加油,少年!!!
|