推箱子C++(多关卡)
该项目用到了类和对象,封装,程序设计,并且运用了easyx库。该项目可在vc2010等编译器下运行,用的都是C++98标准(着实是被C98恶心到了,深刻感受到了C++11的好)。
1.下载easyx库
去他们官网下载EasyX,会下载一个可执行(.exe)程序,点击
下一步。
选择你要安装的编译器,他就自动帮你配置好了。然后就可以直接使用了
调用这个库需要包含头文件
#include<graphics.h>
源文件后缀必须设为.cpp
2.推箱子原理
推箱子游戏的本质是通过不断改变数组的值,不断的贴图。
游戏设计
先用个简单的列子描述一下
拿这个二维数组举列,画出的地图是这样的
判断输赢
要想游戏胜利,首先得把球放进框里,然后遍历整个数组,如果还有球(2),就不结束,如果球全消失了。则游戏胜利。
大致思路就是,推一次,遍历判断一次场上是否还有球。,贴一次图
接下来需要确定什么情况下能走动。
无论是人还是球,能走动的情况无非两种,一 前面是空地,二 前面是框
人前面如果是球,那先判断球是否能挪动,如果球能挪动,再挪动人
人先走,人前面有个球,判断球,球前面是框,满足移动的条件,那挪动球到框的位置,此时球的位置变成了空地,这时人也满足条件,挪动人,人原来的位置变为空地。
改变数组的值
要改变数组的值,是与挪动同时进行的。当球挪进框里,框的位置要加上球的值,球原来的位置要减掉球的值。
人挪动,人当前位置要减掉人的值,挪动的新位置要加上人的值
3.类和对象大致介绍
类其实是个结构体,只不过这个结构体里面除了成员变量,还有成员函数。
对象就是这个结构体实列化出来的。像struct student lihua;
lihua就是对象。
类比为
类就想到于人类,人有眼睛,嘴巴,头发(成员变量),这些都是人都有的属性。人能吃饭,睡觉(这些行为相当于成员函数)。
对象就是具体到每一个人,张三,李四等。张三可以吃鸡蛋饭,李四可以吃牛肉饭。(调用同一种成员函数实现出不同的结果)其实就是传的函数参数不同,得到的结果也不同
张三眼睛可以是黑色,李四眼睛可以是蓝色。(他们都是有眼睛这一成员属性),颜色只不过是由我们自己设置改变的。
4.代码实现
首先创建一个头文件common.h,里面存放要用到的公共头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<map>
#include<string>
#include<vector>
#include<Windows.h>
#include<conio.h>//按键处理
#include<graphics.h>//图像
using namespace std;
//--------------------------------------------------------------------
//公共头文件
//————————————————————————————————————
接下来再创建一个资源头文件resource.h,里面写一个Res类。用它不断的贴图,由于这个类只用初始化一次,但会不停使用,所以可以把它成员设置为静态成员,但是因为我为了兼容VC2010(C++98标准),里面没有静态成员函数,以及把成员容器设为静态会报错。
class Res
{
public:
map<string, IMAGE*> img;//存图片名和图片(通过输入图片名获得图片)数据结构map
//map<string, string> music;
Res()
{
//创建图片对象
img["墙"] = new IMAGE; //1
img["路"] = new IMAGE; //0
img["鸡"] = new IMAGE; //5+3 = 8
img["框"] = new IMAGE; //3
img["鲲"] = new IMAGE; //7 或 7+3 = 10
img["球"] = new IMAGE; //5
//加载图片
loadimage(img["墙"], L"picture-img//1.bmp");//这里在输入地址前必须加个L,否则就必须在配置属性下将字符型改为多字符
loadimage(img["路"], L"picture-img//0.bmp");//字符串的实际存储有多种编码格式,
loadimage(img["鸡"], L"picture-img//8.bmp");//如果默认的编码格式和实际执行的平台不相符就会发生错误。
loadimage(img["框"], L"picture-img//3.bmp");// 或者改为_T(“abc”) 表示。就是加上 _T(“”)。
loadimage(img["鲲"], L"picture-img//7.bmp");
loadimage(img["球"], L"picture-img//5.bmp");
}
//Res* GetRes()
//{
//
// //构建一个对象。这个对象整体成员都能使用
//
//}
//释放
void DeleteRes()
{
}
~Res() {
delete img["墙"];
delete img["路"];
delete img["鸡"];
delete img["框"];
delete img["鲲"];
delete img["球"];
}
};
我这里用到了map这个容易,map本质是一颗搜索二叉树的封装。他的数据结构是这样的
_first记录的是第一个传入的值,这里我们输入汉字“鸡”
_second记录传入图片鸡的地址
于是当我们输入“鸡”这个汉字时,它会先在这颗二叉树里面找"鸡“这个汉字(是与_first比较),找到后,它会返回_second这张图片的地址。
我这里是把墙用1来表示,路用0来表示,球用5来表示,框用3来表示,鸡就是(球+框)8,人有两种状态,站在空地上7,站在框位置(7+3)。
一个游戏肯定要存数据,于是我们便写个Data类来存放地图数据,顺道在这个类中实现
判断是否可以移动,判断是否赢了,设置改变数组值的函数,判断人要移动的那个位置是否是球。
对了我们最开始是不知道人事在什么位置的,所以我们还得写个找人函数,获取人的x,y值。
先把data类的代码贴出来
data.h
#pragma once
#include"common.h"
class Data
{
public:
bool IsMove(int i,int j,vector<vector<int>>& Mapn);//判断是否能移动
bool NoBall(vector<vector<int>>& Mapn);//判断是否还有球,判断游戏输赢
bool IsBall(int i,int j, vector<vector<int>>& Mapn);
void SetValue(int i, int j, int value, vector<vector<int>>& Mapn);
pair<int,int> SearchPerson(vector<vector<int>>& Mapn);
vector<vector<int>>& GetMapn(int i)//相当于返回指针
{
return MapArr[i];
}
Data()
{
InitMap();
MapArr.resize(5);
MapArr[0] = MapArr1;
MapArr[1] = MapArr2;
MapArr[2] = MapArr3;
MapArr[3] = MapArr4;
MapArr[4] = MapArr5;
}
/*int aMap1[14][16] = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0},
{0,0,0,0,1,1,1,0,0,0,0,1,0,0,0,0},
{0,0,0,1,1,3,5,0,1,1,0,1,1,0,0,0},
{0,0,0,1,0,3,5,7,5,0,0,3,1,0,0,0},
{0,0,0,1,0,3,0,5,0,5,3,1,1,0,0,0},
{0,0,0,1,1,1,1,1,1,0,0,1,0,0,0,0},
{0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
};*/
void InitMap();
private:
vector<vector<vector<int>>> MapArr;//int[][][]
vector<vector<int>> MapArr1;//int[][]
vector<vector<int>> MapArr2;
vector<vector<int>> MapArr3;
vector<vector<int>> MapArr4;
vector<vector<int>> MapArr5;
};
地图
这个vector可以理解一维数组。
于是我创建5个二维数组存放地图。
MapArr存放二维数组的地址。
这里就是将int[][]数据放入vector<vector>中,(原本我是可以对vector处理像使用int[][]一样初始化的,但由于按照C++98标准,于是便麻烦多了)。
当把五个地图初始化好后,再将这五个地图地址放入MapArr中
判断输赢
传入地图关卡地址,然后对这个地图遍历一遍,如果没有球5,就是赢了。
是否可以移动
无论是人还是球,只有要移动的位置是空地0或者框3才能移动。
是否是球
设置值
找人的位置
也是遍历一遍,找到人,返回坐标。这里人有两种状态,7或10
众所周知,返回值只能返回一个值,于是我们设计一个结构体,这个结构体有两个变量。一个变量放x,一个变量放y。
获取地图
资源,数据既然完成,接下来就是游戏逻辑。
游戏逻辑
创建一个游戏类,这个类成员一个是资源,一个是数据。
这两个类的指针,以便获取他们的成员变量及成员函数
接下来就是开辟两块空间,以及初始化画布
刚进入游戏我们肯定要贴图,接下来就是按键移动,移动一次后判断是否结束
。
贴图函数
void Game::DrawMap(vector<vector<int>>& Mapn)
{
for (int i = 0; i < 14; i++)
{
for (int j = 0; j < 16; j++)
{
int x = j * 64;//因为easyx横纵坐标是以右下角为原点
int y = i * 64;
switch(Mapn[i][j])//传那张地图的指针过来,并根据数据贴图
{
case 0:
putimage(x, y, res->img["路"]);
break;
case 1:
putimage(x, y, res->img["墙"]);
break;
case 8:
putimage(x, y, res->img["鸡"]);
break;
case 3:
putimage(x, y, res->img["框"]);
break;
case 7:
case 10:
putimage(x, y, res->img["鲲"]);
break;
case 5:
putimage(x, y, res->img["球"]);
break;
}
}
}
char s[] = "按0返回主界面";
outtextxy(10, 20, s);
}
依旧是一次遍历,这里还可以用if实现,但if看起来太乱了,于是我用了switch
这里要注意一点x,y。因为easyx画布坐标是
所以x = j*64
图片大小,有 y = i*64
;
这里大小根据贴图图片大小设置,查看图片大小则是选中图片,右键属性里面查看。
按键移动
这步是玩游戏的核心
首先得把地图传过来
引用(有点像传指针)
然后调用找人函数,获取角色坐标。
用_getch()获取移动值,为什么用_getch(),不用getchar()
因为getchar()函数在读入一个字符时必须按一下Enter键,代码才会继续运行。
获得字符后进行以下判断
首先先判断人是否可以移动,调用移动函数。如果能移动,改变数组里面的值。
如果不能移动,说明前面有墙,有墙不执行操作,有球,就先判断球是否能移动,球能移动的话,先移动球,
再移动人,移动伴随着改变值。
这里返回一个bool值是为了,当用户输入0时,返回主界面
判断游戏是否结束
直接调用Noball函数就行
主界面
最初写成这样就可以直接玩第一关,但我们写的是多关卡,于是要有一个看得过去的界面。
先写一个菜单函数
子菜单
这里我着重写一下多关卡菜单的核心代码
这个就是从第一关开始玩,
这个BeginGame我套了两层循环,第一层循环是依次执行每个地图。
第二层循环是判断每个地图是否通关
已知KeyDown按0返回真,于是我们设置一个标志位flag在第一层循环里判断,用户是否主动跳出。
这里核心是如果用户不主动退出,当选择关卡时会依次从选择的关卡往下开始。因为会返回一个false,switchl里面的if(),就不会执行那个break.但如果用户按0,则会返回true,然后跳出这个switch显示菜单。这里是环环相扣的。
到此为止,推箱子多关卡代码全部完成。
游戏截图
游戏图片及源码我上传到了推箱子