对于推箱子游戏,相信大家并不会陌生吧。这是一个非常经典的小游戏。今天,我就将我开发这款游戏的经验分享给大家。
第一步,我会分析建立出游戏的大体框架。推箱子游戏实际上是在一片二维数组的地图上进行数组元素变换,同时进行图形的绘制工作。并且从游戏的玩法可以得到一些基本游戏元素(一个小人,箱子,destination(目标),地板,墙壁,还有整个背景)。
首先需要引用图形库头文件#include <graphics.h>(这里我使用的是Easy X库)。这样就能够进行基本的图形操作。我们需要在main函数中创建图形窗口代码如下:
int main()
{
initgraph(652, 460); // 创建绘图窗口,大小为 640x480 像素
Gamemain(); //游戏运行的主体函数
_getch(); // 按任意键继续
closegraph(); // 关闭绘图窗口
}
好的,接下来该定义游戏中所有的基本要素将游戏中会出现的所有元素类型定义为枚举类型
typedef enum{ NUL, WALL, FLOOR, PLAYER, DEST, BOX, PLAYERONDEST=7,BOXONDEST=9 }TYPE;
/*NUL为背景色,WALL是墙,FLOOR为地板,PLAYER是玩家,DEST是终点,BOX是箱子,
PLAYERONDEST代表人站在终点,BOXONDEST代表想在放在了终点上。这样就将所有会
遇到的元素都定义为了枚举类型,方便后面使用*/
我们可以把整张地图定义为一个二维数组
TYPE layout[14][20]; //这里我们定义地图为20*14。
int row = 14; //并且将行列定义成全局变量
int col = 20;
在初始化绘图界面之前,我们得将所有元素对应的.jpg格式的图片统一保存在源代码所在文件夹里面,保证能够读取到。并且为了能够统一管理每一关卡的布局,我们新建名为level.txt的文本文档将所有关卡布局有规律地写在此文件中(以便后续进行下一关时,只需跳过相应的关卡的数据,就能读取到当前关卡的布局数据)。
接下来开始初始化绘图工作,首先我们需要建立图像对象,
IMAGE imgnul ;
IMAGE imgwall ;
IMAGE imgfloor ;
IMAGE imgplayer ;
IMAGE imgdest ;
IMAGE imgbox ;
然后进行初始化布局函数(void initgraph(int* level))的定义,为什么我要这里传递指针型数据,因为我需要根据level的值,来计算出需要跳过多少字节来读取文件,从而能得到对应关卡。函数如下
void initgraph(int * level)
{
FILE* pf;
pf = fopen("./level/level.txt", "rb"); //只读操作
if (pf != NULL)
{
char c;
fseek(pf, (*level) * 574+(*level)*6, 0); //level从零开始
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++) //遍历赋值
{
c = fgetc(pf);
layout[i][j] =(TYPE)( c - '0');
fgetc(pf);
}
fgetc(pf);
}
fclose(pf);
}
loadimage(&imgnul ,_T("./res/bk.jpg")); //相对路径
loadimage(&imgwall, _T("./res/wall.jpg"));
loadimage(&imgfloor, _T("./res/floor.jpg"));
loadimage(&imgplayer, _T("./res/player.jpg"));
loadimage(&imgdest, _T("./res/dest.jpg"));
loadimage(&imgbox, _T("./res/box.jpg"));
}
大致可以通过上述代码,来实现布局数组初始化。并且从当前目录下读取各个元素图像。
接下来就是将布局图形化,即完成游戏界面绘制,在关卡的布局上我还增加了当前移动步数的显示,与当前关卡的显示效果,并且还增加了显示当前关卡最好记录的功能,效果如下:
函数void DrawLayout(int* level,int * MoveSteps)该函数的形参为关卡与步数,根据所传参数,可以进行上述功能的实现。具体如下:
void DrawLayout(int* level,int * MoveSteps)
{
putimage(0, 0, &imgnul);
TCHAR s1[] = _T("Level:");
outtextxy(10*32+6,13*32+20,s1);
TCHAR S1[5];
_stprintf(S1, _T("%d"), *level+1);
outtextxy(10 * 32 + 6 + textwidth(s1) , 13 * 32 + 20, S1);
TCHAR s2[] = _T("MoveSteps:");
outtextxy(12 * 32 + 6 + textwidth(s1), 13 * 32 + 20,s2);
TCHAR S2[5];
_stprintf(S2, _T("%d"), *MoveSteps);
outtextxy(12 * 32 + 6 + textwidth(s1) + textwidth(s2), 13 * 32 + 20,S2);
TCHAR s3[] = _T("The most of records:");
outtextxy(6, 13 * 32 + 20, s3);
FILE* pf1;
pf1 = fopen("./The most point/The most point.txt", "r+"); //打开记录文件,读取旧的记录。
char record1[4];
if (pf1 != NULL)
{
fseek(pf1, (*level) * 4, SEEK_SET);
fgets(record1, 4, pf1);
}
fclose(pf1);
int i = atoi(record1);
TCHAR S3[5];
_stprintf(S3, _T("%d"), i);
outtextxy(6 + textwidth(s3), 13 * 32 + 20,S3);
int x, y;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
x = 32 * j + 6;
y = 32 * i + 6;
switch (layout[i][j])
{
case WALL:
putimage(x, y, &imgwall); break;
case FLOOR:
putimage(x, y, &imgfloor); break;
case PLAYER :case PLAYERONDEST:
putimage(x, y, &imgplayer); break;
case DEST:
putimage(x, y, &imgdest); break;
case BOX:case BOXONDEST:
putimage(x, y, &imgbox); break;
}
}
}
}
关于绘制方面工作就不再进行赘述,利用Easy X库中的putimage函数导入对应的图像。关于移动步数与第几关的显示需要主调函数中步数与关卡的值,再利用outtextxy()函数把对应的字符串在制定位置输出。关于每一关最好记录的处理,即在当前目录下建立The most of records.txt文本文档,用来读写记录。先默认存储各个关卡的记录为零,然后通过对比成绩看是否为新的记录,若是则写入新的记录。
接下来就到了移动小人儿的操作了,这里上下左右移动的方法基本一样,我就以上移来进行解读。首先得理清逻辑,什么时候我们才能上移小人儿(并且还得知道小人儿是站在地板(FLOOR)还是小人儿在dest(终点上))。所以我们得找到小人儿的位置,将小人的位置传进函数中再进行判断是否可以移动。
关于可以移动小人儿的条件:1.当小人儿的上面为地板(FLOOR)时可以移动小人儿,并且移动后将新的值赋给对应的位置(必须注意找到小人儿时的位置值是PLAYER还是PLAYERONDEST)不然不能正确的赋值,程序就会出现各种问题。下面简述剩余情况2.当小人儿的上面为终点(DEST)时可以移动小人。3.当小人儿的上一个为箱子(必须再判断是BOX还是BOSONDEST不然后续运行会出错)并且上上一个是否是(地板或是终点)才能移动小人。大致逻辑如上,下面附上上移代码:
void PlayerUp()
{
int x = 0, y = 0;
FindPlayer(&x, &y); //查找当前小人位置
if (layout[x - 1][y] == DEST || layout[x - 1][y] == FLOOR || (layout[x - 1][y] == BOX && (layout[x - 2][y] == DEST || layout[x - 2][y] == FLOOR))
|| (layout[x - 1][y] == BOXONDEST && (layout[x - 2][y] == FLOOR || layout[x - 2][y] == DEST)))
{
if ((layout[x - 1][y] == BOX && (layout[x - 2][y] == DEST || layout[x - 2][y] == FLOOR)))
{
if (layout[x - 2][y] == DEST)
{
layout[x - 2][y] = BOXONDEST;
layout[x - 1][y] = PLAYER;
layout[x][y] = (layout[x][y] == PLAYERONDEST ? DEST : FLOOR);;
}
else if (layout[x - 2][y] == FLOOR)
{
layout[x - 2][y] = BOX;
layout[x - 1][y] = PLAYER;
layout[x][y] =(layout[x][y] == PLAYERONDEST ? DEST : FLOOR);;
}
}
else if (layout[x - 1][y] == DEST)
{
layout[x - 1][y] = PLAYERONDEST;
layout[x][y] =(layout[x][y] == PLAYERONDEST ? DEST : FLOOR);
}
else if (layout[x - 1][y] == FLOOR)
{
layout[x - 1][y] = PLAYER;
layout[x][y] = (layout[x][y] == PLAYERONDEST ? DEST : FLOOR);
}
else if ((layout[x - 1][y] == BOXONDEST) && (layout[x - 2][y] == FLOOR || layout[x - 2][y] == DEST))
{
if (layout[x - 2][y] == FLOOR)
{
layout[x - 2][y] = BOX;
layout[x - 1][y] = PLAYERONDEST;
layout[x][y] = (layout[x][y] == PLAYERONDEST ? DEST: FLOOR);
}
else if (layout[x - 2][y] == DEST)
{
layout[x - 2][y] = BOXONDEST;
layout[x - 1][y] = PLAYERONDEST;
layout[x][y] = (layout[x][y] == PLAYERONDEST ? DEST : FLOOR);
}
}
}
}
目前就写了这么多,就先分享到这。还有许多功能没有实现,后续写出更多功能后会更新。欢迎各位朋友围观点赞,有什么疑问评论区交流讨论。