推箱子,经典小游戏,带我们回到小时候,来一波“爷童回”。可以在数据库表中设计不一样的关卡,自己设计地图,学习的同时又能体验游戏的乐趣~
实现:采用C++语言,使用visual studio工具,与数据库进行相连,实现用户登录与获取关卡信息功能。
用户登录后可获取当前已到达哪一关卡,接着从那一关开始继续推箱子。同时加载地图也是通过读取关卡表,支持在表中添加多种不同地图,提升游戏体验。
效果:源代码:游戏视频链接
目录:github地址
一、数据库表设计
二、连接数据库与用户登录
三、初始化游戏界面
四、获取并加载地图数据
五、操作游戏
六、跳转至下一关或结束游戏
用户表用来存用户的信息,包括用户id,用户名,密码,关卡id。
| 字段名 | 类型 | 是否为空 | 键 | 备注 |
|---|---|---|---|---|
| id | int | 否 | 主键 | 自动增长 |
| username | varchar(64) | 否 | 唯一键 | 用户名(登录时使用) |
| password | varchar(32) | 否 | NA | 密码(用md5加密) |
| level_id | int | 否 | NA | 保存用户所在的关卡 |
用户表里中保存的关卡id(预设为1表示第一关),当用户下次登录时,从上次玩到的关卡开始继续游戏。
创建语句:
CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT PRIMARY KEY, username varchar(64) NOT NULL UNIQUE, password varchar(32) NOT NULL, level_id int DEFAULT '1');1.2 关卡表设计
关卡表用来存放地图信息,包括地图id,地图名字,地图行数,地图列数,地图数据,下一关地图id。
| 字段名 | 类型 | 是否为空 | 键 | 备注 |
|---|---|---|---|---|
| id | int | 否 | 主键 | 地图的id |
| name | varchar(64) | 否 | NA | 地图名字 |
| map_row | int | 否 | NA | 地图行数 |
| map_column | int | 否 | NA | 地图列数 |
| map_data | varchar(32) | 否 | NA | 地图数据 |
| next_level_id | int | 否 | NA | 下一关的地图id |
其中,通过读取表中的地图数据来加载地图,next_level_id存下一关的地图id,没有下一关存0。
创建语句:
CREATE TABLE levels( id int NOT NULL PRIMARY KEY, name varchar(64) NOT NULL, map_row int NOT NULL, map_column int NOT NULL, map_data varchar(4096) NOT NULL, next_level_id int DEFAULT '0');二 、连接数据库与用户登录 2.1 连接数据库
创建了一个database文件,用来存放与数据库连接的方法,和需要与数据库交互的一些方法,database.h中为方法的声明,database.cpp中为方法的定义。
以下为连接数据库的方法,只写在database.cpp文件中:
#define DB_HOST "127.0.0.1"
#define DB_USER "root"
#define DB_PASSWD "123456!"
#define DB_NAME "push_box"
#define DB_PORT 3308
bool connectDB(MYSQL &mysql) {
mysql_init(&mysql); //初始化句柄
//设置字符编码
mysql_options(&mysql,MYSQL_SET_CHARSET_NAME,"gbk");
//连接数据库
if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_PASSWD, DB_NAME, DB_PORT, NULL, 0) == NULL) {
printf("数据库连接失败,原因:%s",mysql_error(&mysql));
return false;
}
return true;
}
其中, DB_HOST, DB_USER, DB_PASSWD, DB_NAME, DB_PORT都使用了宏定义。
2.2 实现用户登录在database文件中通过getUserInfo方法,用来获取用户信息并进行用户登录认证。
database.h:
#include "box.h" bool getUserInfo(useInfo &user);
database.cpp:
#include#include #include "database.h" //省略上面已写的宏定义与conactDB方法 bool getUserInfo(useInfo &user) { MYSQL mysql; MYSQL_RES *result; //定义结果集 MYSQL_ROW row; //记录结构体 char sql[256]; //将sql语句存入其中 int ret; //连接到数据库 if (!connectDB(mysql)) { return false; } snprintf(sql,256,"select id,level_id from user where username='%s' and password=md5('%s');", user.name.c_str(), user.pwd.c_str()); ret=mysql_query(&mysql, sql); //成功返回0 if (ret) { printf("查询数据失败,%s,原因:%s",sql,mysql_error(&mysql)); mysql_close(&mysql); //关闭数据库 return false; } //获取结果 result = mysql_store_result(&mysql); row = mysql_fetch_row(result); //结果读出 if (row == NULL) { //没有查询到数据 mysql_free_result(result); //释放结果集 mysql_close(&mysql); //关闭数据库 return false; } user.id = atoi(row[0]); //atoi将字符串转为int user.levelID = atoi(row[1]); mysql_free_result(result); //释放结果集 mysql_close(&mysql); //关闭数据库 return true; }
其中userInfo为结构体,存放在box.h头文件中,如下所写:
typedef struct _useInfo{
int id; //用户id
string name; //用户名
string pwd; //密码
int levelID; //关卡id
}useInfo;
从数据库中获取的用户数据会保存到结构体对象中。
同时main方法中的写法如下:
#include三 、初始化游戏界面#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 //用户登录方法,有四次输入密码的机会 bool login() { // useInfo user; int times = 0; bool ret = false; do { times++; cout << "请输入用户名:"; cin >> user.name; cout << "请输入密码:"; cin >> user.pwd; ret = getUserInfo(user); if (times > 4) { break; } if (ret == false) { cout << "请重新输入账号/密码!" << endl; } } while (!ret); return ret; } int main(){ if (login() == false) { exit(-1); } return 0; }
在main.cpp文件中,创建了initGame方法,用来初始化窗口大小,加载人、箱子等图片。
更新main.cpp:
#include#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 void initGame() { // cleardevice(); initgraph(BG_WIDTH, BG_HEIGTH); //初始化窗口大小 loadimage(&bg_img, _T("blackground.bmp"), BG_WIDTH, BG_HEIGTH, true); //加載背景圖片 putimage(0, 0, &bg_img); //把圖片放到窗口上 //加載墻,人等圖片 loadimage(&img[WALL], _T("wall_right.bmp"), IMG_SIZE, IMG_SIZE, true); loadimage(&img[FLOOER], _T("floor.bmp"), IMG_SIZE, IMG_SIZE, true); loadimage(&img[BOX_DEC], _T("des.bmp"), IMG_SIZE, IMG_SIZE, true); loadimage(&img[MAN], _T("man.bmp"), IMG_SIZE, IMG_SIZE, true); loadimage(&img[BOX], _T("box.bmp"), IMG_SIZE, IMG_SIZE, true); loadimage(&img[HIT], _T("box.bmp"), IMG_SIZE, IMG_SIZE, true); } int main(){ if (login() == false) { exit(-1); } initGame(); return 0; }
initGame方法中的窗口大小为宏定义,图片数组中墙、地板等为枚举类型,都定义在box.h文件中:
#pragma once #include四 、获取并加载地图数据 4.1 获取地图数据using namespace std; #define IMG_SIZE 61 #define BG_WIDTH 800 #define BG_HEIGTH 640 //枚舉道具 enum PROP { WALL, FLOOER, BOX_DEC, MAN, BOX, HIT, ALL };
在初始化界面后,需要从数据库中读取地图数据。同样也是在database文件中,通过getLevelInfo来获取关卡信息,如下所写:
//省略头文件,宏定义,conactDB方法和getUserInfo方法
bool getLevelInfo(levelInfo &level,int level_id) {
MYSQL mysql;
MYSQL_RES *result; //定义结果集
MYSQL_ROW row; //记录结构体
char sql[256]; //将sql语句存入其中
int ret;
//连接到数据库
if (!connectDB(mysql)) {
return false;
}
snprintf(sql, 256, "select name,map_row,map_column,map_data,next_level_id from levels where id='%d';", level_id);
ret = mysql_query(&mysql, sql); //成功返回0
if (ret) {
printf("查询数据失败,%s,原因:%s", sql, mysql_error(&mysql));
mysql_close(&mysql); //关闭数据库
return false;
}
//获取结果
result = mysql_store_result(&mysql);
row = mysql_fetch_row(result); //结果读出
if (row == NULL) { //没有查询到数据
mysql_free_result(result); //释放结果集
mysql_close(&mysql); //关闭数据库
return false;
}
level.name = row[0]; //atoi将字符串转为int
level.map_row = atoi(row[1]);
level.map_column = atoi(row[2]);
level.map_data = row[3];
level.next_level = atoi(row[4]);
mysql_free_result(result); //释放结果集
mysql_close(&mysql); //关闭数据库
return true;
}
其中levelInfo为结构体,存放在box.h头文件中,如下所写:
typedef struct _levelInfo {
int id; //关卡id
string name; //关卡名字
int map_row; //地图行数
int map_column; //地图列数
string map_data; //地图数据
int next_level; //下一关id
}levelInfo;
从数据库中获取的地图数据会保存到结构体对象中。
更新main.cpp中代码:
#include4.2 加载地图数据#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 levelInfo level; //创建level结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 //省略 int main(){ if (login() == false) { printf("登录失败n"); exit(-1); } initGame(); if (!getLevelInfo(level, user.levelID)) { // 获取地图数据 printf("获取地图数据失败n"); exit(-2); } return 0; }
读取出地图数据后,需要将数据解析出来,并保存到地图数组map中。在database中使用loadLevel方法:
bool loadLevel(levelInfo &level, int map[MAP_LINE][MAP_COLUMN]) {
if (level.map_row > MAP_LINE || level.map_column > MAP_COLUMN) {
printf("地图设计太大了,请重新设计!n");
return false;
}
if (level.map_data.length() < 1) {
printf("地图有误,请重新设计!n");
return false;
}
int start = 0, end = 0;
int row = 0, column = 0;
do {
end = level.map_data.find("|", start); //从start开始找(即从字符串第一个字符开始),直到找到|符号,返回|所在位置的下标,没找到返回-1
//当找到最后一串时,后面没有|符号了,end一定是返回-1,所以需要手动将end指向字符串结束符
if (end < 0) {
end = level.map_data.length();
}
string line = level.map_data.substr(start, end - start); //把字符串取出来
printf("line: %sn", line.c_str());
char *next_data = NULL;
char *item = strtok_s((char *)line.c_str(), ",", &next_data); //根据逗号解析字符串,返回
column = 0; //给列清零重新开始计数
while (item && column= level.map_row) { //读完每一行后退出
break;
}
start = end + 1; //指向下一串字符
} while (1);
if (row < level.map_row) {
printf("地图数据有误,请重新设计n");
return false;
}
return true;
}
更新main.cpp文件:
#include#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 levelInfo level; //创建level结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 int map[MAP_LINE][MAP_COLUMN] = { 0 }; //创建地图数组 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 //省略 int main(){ if (login() == false) { printf("登录失败n"); exit(-1); } initGame(); if (!getLevelInfo(level, user.levelID)) { // 获取地图数据 printf("获取地图数据失败n"); exit(-2); } if (!loadLevel(level, map)) { //加载地图 printf("加载地图失败n"); exit(-3); } return 0; }
其中地图数组map中的行和列为宏定义,大小为50,在box.h文件中定义,更新box.h如下:
#pragma once #include4.3 构建地图using namespace std; #define IMG_SIZE 61 #define BG_WIDTH 800 #define BG_HEIGTH 640 #define MAP_LINE 50 #define MAP_COLUMN 50 //枚舉道具 enum PROP { WALL, FLOOER, BOX_DEC, MAN, BOX, HIT, ALL };
在解析出地图数据保存到map数组中后,需要将人物,地板构建出来,在main.cpp中进行编写,更细如下:
#include#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 levelInfo level; //创建level结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 int map[MAP_LINE][MAP_COLUMN] = { 0 }; //创建地图数组 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 //省略 int main(){ if (login() == false) { printf("登录失败n"); exit(-1); } initGame(); if (!getLevelInfo(level, user.levelID)) { // 获取地图数据 printf("获取地图数据失败n"); exit(-2); } if (!loadLevel(level, map)) { //加载地图 printf("加载地图失败n"); exit(-3); } //構建地圖,同時記錄人和目的地所在的位置 for (int i = 0; i < level.map_row; i++) { for (int j = 0; j < level.map_column; j++) { if (map[i][j] == MAN) { man.x = i; man.y = j; } if (map[i][j] == BOX_DEC) { ::dec.x = i; ::dec.y = j; } putimage(WIDTH_MOVE + IMG_SIZE * j, HEIGTH_MOVE + IMG_SIZE * i, &img[map[i][j]]); } } return 0; }
其中,putimage是将地图加载到界面上,WIDTH_MOVE和HEIGTH_MOVE为偏移量,IMG_SIZE为图片大小,都为宏定义,写在box.h文件中。更新box.h文件如下:
#pragma once #include五、操作游戏 5.1 读取键盘按键using namespace std; #define IMG_SIZE 61 #define BG_WIDTH 800 #define BG_HEIGTH 640 #define MAP_LINE 50 #define MAP_COLUMN 50 #define WIDTH_MOVE 34 #define HEIGTH_MOVE 45 #define IMG_SIZE 61 //枚舉道具 enum PROP { WALL, FLOOER, BOX_DEC, MAN, BOX, HIT, ALL };
写完以上流程,需要控制游戏人物移动,通过 _kbhit方法 来判断是否有按下键盘,若有按下则通过 _getch方法 获取键盘按下的键并以char字母返回。其中,键盘会一直被按下,应该是否循环来判断键盘事件。
更新main.cpp文件如下:
#include5.2 实现人物移动 5.2.1 控制人物移动#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 levelInfo level; //创建level结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 int map[MAP_LINE][MAP_COLUMN] = { 0 }; //创建地图数组 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 //省略 int main(){ if (login() == false) { printf("登录失败n"); exit(-1); } initGame(); if (!getLevelInfo(level, user.levelID)) { // 获取地图数据 printf("获取地图数据失败n"); exit(-2); } if (!loadLevel(level, map)) { //加载地图 printf("加载地图失败n"); exit(-3); } //構建地圖,同時記錄人和目的地所在的位置 //省略 //判断键盘按键进行处理 do{ if(_kbhit()){ //判断键盘是否有按键 char ch = _getch(); //获取键盘按下的值,并转为char字母 } }while(1); return 0; }
以上获取键盘按键后,需要对按键进行处理,本例中使用w,s,a,d来表示人物上下左右移动,因为每次按键都要对人物进行移动,可以将人物移动的方法提取出来,传入参数为上/下/左/右。
更新main.cpp文件如下,增加controlGame方法:
#include#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 levelInfo level; //创建level结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 int map[MAP_LINE][MAP_COLUMN] = { 0 }; //创建地图数组 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 //省略 //控制人物移动,传入参数为方向 void controlGame(enum direction direct){ if(direct == UP){ if(map[man.x][man.y-1] == FLOOER && man.y-1>0) //判断人物前面是地板才能移动 //移动后改变人物所在位置为地板,改变人物前所在地板为人物 //changeMap(man.x,man.y,FLOOER); //下一小节继续编写 } } int main(){ if (login() == false) { printf("登录失败n"); exit(-1); } initGame(); if (!getLevelInfo(level, user.levelID)) { // 获取地图数据 printf("获取地图数据失败n"); exit(-2); } if (!loadLevel(level, map)) { //加载地图 printf("加载地图失败n"); exit(-3); } //構建地圖,同時記錄人和目的地所在的位置 //省略 //判断键盘按键进行处理 do{ if(_kbhit()){ //判断键盘是否有按键 char ch = _getch(); //获取键盘按下的值,并转为char字母 //判断人物移动方向 if(ch == KEY_UP){ controlGame(UP); }else if(ch == KEY_DOWN){ controlGame(DOWN); }else if(ch == KEY_LEFT){ controlGame(LEFT); }else if(ch == KEY_RIGHT){ controlGame(RIGHT); } } }while(1); return 0; }
其中,controlGame中的参数为枚举类型,在box.h文件中定义:
//定义人物移动的方向
#define KEY_UP 'w'
#define KEY_DOWN 's'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define QUIET 'q'
//枚举方向
enum direction {
UP,
DOWN,
LEFT,
RIGHT
};
5.2.1 控制人物移动
当人物进行上下左右移动的时候,地图会发生变化,对人物前所在位置为地板还是箱子等情况进行处理,使用changeMap方法,对人物每次移动进行地图的变化。
更新main.cpp文件如下,增加changeMap方法:
#include#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 levelInfo level; //创建level结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 int map[MAP_LINE][MAP_COLUMN] = { 0 }; //创建地图数组 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 //省略 void changeMap(int x, int y, enum PROP pro){ map[x][y] = pro; //将地图进行转变 putimage(WIDTH_MOVE + IMG_SIZE * y, HEIGTH_MOVE + IMG_SIZE * x, &img[map[x][y]]); //将需转变的图形进行加载 } //控制人物移动,传入参数为方向 void controlGame(enum direction direct){ if(direct == UP){ if(map[man.x-1][man.y] == FLOOER && man.x-1>0) //判断人物前面是地板才能移动 //移动后改变人物所在位置为地板,改变人物前所在地板为人物 changeMap(man.x,man.y,FLOOER); changeMap(man.x-1,man.y,MAN); }else if(direct == DOWN){ if(map[man.x+1][man.y] == FLOOER && man.x+1 0) //判断人物前面是地板才能移动 //移动后改变人物所在位置为地板,改变人物前所在地板为人物 changeMap(man.x,man.y,FLOOER); changeMap(man.x,man.y-1,MAN); }else if(direct == RIGHT){ if(map[man.x][man.y+1] == FLOOER && man.y+1 5.3 优化 以上虽然可以对人物进行移动,但很多代码有重复,显得有些冗余,可以进行优化。并且上一小节值对人物前是地板进行处理,本小节增加人物前为箱子和目的地进行处理。
1、 首先,对controlGame方法进行优化:
//人物不能移除地图外 #define isValid(pos) pos.x>=0 && pos.x=0 && pos.y 以上优化后,判断人物前是不是箱子只需要一个判断,不需要增加方向的判断,同时也需增加防御型编程(即人物不能移到地图外)
2、 接着,对controlGame方法增加人物前为箱子与目的地的判断:
//人物不能移除地图外 #define isValid(pos) pos.x>=0 && pos.x=0 && pos.y 六、游戏结束或跳转至下一关 处理完游戏的操作逻辑后,需要判断是否有下一关了,有则跳转并加载下一关,否则游戏结束。
6.1 游戏结束在循环中加入gameOver方法,当所有箱子都推入到目的地中的时候,游戏结束(下一节考虑有下一关情况)。
更新main.cpp文件如下,增加gameOver方法:
#include6.2 绘制结束画面#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 levelInfo level; //创建level结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 int map[MAP_LINE][MAP_COLUMN] = { 0 }; //创建地图数组 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 //省略 //改变地图数据 //changeMap方法省略 //控制人物移动,传入参数为方向 //controlGame方法省略 bool gameOver(){ //当地图上没有目的地的时候quiet为true for (int i = 0; i < level.map_row; i++) { for (int j = 0; j < level.map_column; j++) { if (map[i][j] == BOX_DEC) { return false; } //判断人是不是站在地图上 if (::dec.x == man.x && ::dec.y == man.y) { return false; } } } return true; } int main(){ bool quiet = false; //定义quiet变量,控制游戏结束 if (login() == false) { printf("登录失败n"); exit(-1); } initGame(); if (!getLevelInfo(level, user.levelID)) { // 获取地图数据 printf("获取地图数据失败n"); exit(-2); } if (!loadLevel(level, map)) { //加载地图 printf("加载地图失败n"); exit(-3); } //構建地圖,同時記錄人和目的地所在的位置 //省略 //判断键盘按键进行处理 do{ if(_kbhit()){ //判断键盘是否有按键 char ch = _getch(); //获取键盘按下的值,并转为char字母 //判断人物移动方向 if(ch == KEY_UP){ controlGame(UP); }else if(ch == KEY_DOWN){ controlGame(DOWN); }else if(ch == KEY_LEFT){ controlGame(LEFT); }else if(ch == KEY_RIGHT){ controlGame(RIGHT); } if (gameOver()) { quiet = true; } } }while(quiet == false); //当游戏没有结束,会一直循环 closegraph(); //关闭easyx return 0; } 当游戏结束后,使用gameOverScren方法绘制结束画面。
更新main.cpp文件:
#include6.3 跳转下一关#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 levelInfo level; //创建level结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 int map[MAP_LINE][MAP_COLUMN] = { 0 }; //创建地图数组 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 //省略 //改变地图数据 //changeMap方法省略 //控制人物移动,传入参数为方向 //controlGame方法省略 bool gameOver(){ //当地图上没有目的地的时候quiet为true for (int i = 0; i < level.map_row; i++) { for (int j = 0; j < level.map_column; j++) { if (map[i][j] == BOX_DEC) { return false; } //判断人是不是站在地图上 if (::dec.x == man.x && ::dec.y == man.y) { return false; } } } return true; } //绘制结束界面 void gameOverScren(IMAGE *bg, bool quiet) { if (quiet) { IMAGE victory; putimage(0, 0, bg); loadimage(&victory, _T("victory.png"), VICTORY_WIDTH, VICTORY_HEIGTH, true); //加載背景圖片 putimage(96, 161, &victory); settextcolor(WHITE); settextstyle(25, 0, "宋体"); //繪製矩形 RECT rc = { 0,0,BG_WIDTH,BG_HEIGTH - VICTORY_HEIGTH - 161 }; //顯示文字,垂直居中,水平居中,文字顯示在同一行 drawtext(_T("恭喜您通关了"), &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } } int main(){ bool quiet = false; //定义quiet变量,控制游戏结束 if (login() == false) { printf("登录失败n"); exit(-1); } initGame(); if (!getLevelInfo(level, user.levelID)) { // 获取地图数据 printf("获取地图数据失败n"); exit(-2); } if (!loadLevel(level, map)) { //加载地图 printf("加载地图失败n"); exit(-3); } //構建地圖,同時記錄人和目的地所在的位置 //省略 //判断键盘按键进行处理 do{ if(_kbhit()){ //判断键盘是否有按键 char ch = _getch(); //获取键盘按下的值,并转为char字母 //判断人物移动方向 if(ch == KEY_UP){ controlGame(UP); }else if(ch == KEY_DOWN){ controlGame(DOWN); }else if(ch == KEY_LEFT){ controlGame(LEFT); }else if(ch == KEY_RIGHT){ controlGame(RIGHT); } if (gameOver()) { quiet = true; } } Sleep(100); }while(quiet == false); //当游戏没有结束,会一直循环 gameOverScren(&bg_img, quiet); ::system("pause"); //暂停 closegraph(); //关闭easyx return 0; } 当地图存在下一关的时候,不显示游戏结束页面,跳转到下一关继续游戏。
更新main.cpp文件,在gameOver方法中添加判断:
#include#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 levelInfo level; //创建level结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 int map[MAP_LINE][MAP_COLUMN] = { 0 }; //创建地图数组 bool haveNextLeve = false; //定义变量,用于判断是否有下一关 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 //省略 //改变地图数据 //changeMap方法省略 //控制人物移动,传入参数为方向 //controlGame方法省略 //绘制上一关结束后跳转到下一关的画面 void nextLevelMessage(IMAGE *bg) { //IMAGE victory; putimage(0, 0, bg); //loadimage(&victory, _T("victory.png"), VICTORY_WIDTH, VICTORY_HEIGTH, true); //加載背景圖片 //putimage(96, 161, &victory); settextcolor(WHITE); settextstyle(25, 0, "宋体"); //繪製矩形 RECT rc = { 0,0,BG_WIDTH,BG_HEIGTH }; //顯示文字,垂直居中,水平居中,文字顯示在同一行 drawtext(_T("恭喜您通过了这一关,点击任意键继续"), &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE); ::system("pause"); cleardevice(); } bool gameOver(){ //当地图上没有目的地的时候quiet为true for (int i = 0; i < level.map_row; i++) { for (int j = 0; j < level.map_column; j++) { if (map[i][j] == BOX_DEC) { return false; } //判断人是不是站在地图上 if (::dec.x == man.x && ::dec.y == man.y) { return false; } } } if (level.next_level > 0) { nextLevelMessage(&bg_img); user.levelID = level.next_level; level = nextLevel; //当前关卡变为下一关 haveNextLeve = true; } else { haveNextLeve = false; } return true; } //绘制结束界面 //省略gameOverScren方法 int main(){ bool quiet = false; //定义quiet变量,控制游戏结束 if (login() == false) { printf("登录失败n"); exit(-1); } initGame(); do{ if (!getLevelInfo(level, user.levelID)) { // 获取地图数据 printf("获取地图数据失败n"); exit(-2); } if (!loadLevel(level, map)) { //加载地图 printf("加载地图失败n"); exit(-3); } //構建地圖,同時記錄人和目的地所在的位置 //省略 //判断键盘按键进行处理 do{ if(_kbhit()){ //判断键盘是否有按键 char ch = _getch(); //获取键盘按下的值,并转为char字母 //判断人物移动方向 if(ch == KEY_UP){ controlGame(UP); }else if(ch == KEY_DOWN){ controlGame(DOWN); }else if(ch == KEY_LEFT){ controlGame(LEFT); }else if(ch == KEY_RIGHT){ controlGame(RIGHT); } if (gameOver()) { quiet = true; } } Sleep(100); }while(quiet == false); //当游戏没有结束,会一直循环 while(haveNextLeve); gameOverScren(&bg_img, quiet); ::system("pause"); //暂停 closegraph(); //关闭easyx return 0; } 因为第一次加载过地图,可以直接使用循环,第二次可以继续走原来那段代码,使用nextLevelMessage方法绘制这一关结束后跳转到下一关时的画面。
6.4 更新用户信息当我们加载下一关的地图时,需要对数据库中的用户表中的level_id更新,更新为下一关地图的id。
更新main.cpp文件,增加updateUserLevel方法:
#include#include #include #include #include #include #include #include "box.h" #include "database.h" useInfo user; //创建user结构体对象 levelInfo level; //创建level结构体对象 //創建數組來存放不同種類的圖片 IMAGE img[ALL]; IMAGE bg_img; //創建圖片對象 int map[MAP_LINE][MAP_COLUMN] = { 0 }; //创建地图数组 bool haveNextLeve = false; //定义变量,用于判断是否有下一关 //用户登录方法,有四次输入密码的机会 //省略 //初始化游戏界面 //省略 //改变地图数据 //changeMap方法省略 //控制人物移动,传入参数为方向 //controlGame方法省略 //绘制上一关结束后跳转到下一关的画面 void nextLevelMessage(IMAGE *bg) { //IMAGE victory; putimage(0, 0, bg); //loadimage(&victory, _T("victory.png"), VICTORY_WIDTH, VICTORY_HEIGTH, true); //加載背景圖片 //putimage(96, 161, &victory); settextcolor(WHITE); settextstyle(25, 0, "宋体"); //繪製矩形 RECT rc = { 0,0,BG_WIDTH,BG_HEIGTH }; //顯示文字,垂直居中,水平居中,文字顯示在同一行 drawtext(_T("恭喜您通过了这一关,点击任意键继续"), &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE); ::system("pause"); cleardevice(); } bool gameOver(){ //当地图上没有目的地的时候quiet为true for (int i = 0; i < level.map_row; i++) { for (int j = 0; j < level.map_column; j++) { if (map[i][j] == BOX_DEC) { return false; } //判断人是不是站在地图上 if (::dec.x == man.x && ::dec.y == man.y) { return false; } } } if (level.next_level > 0) { updateUserLevel(level.next_level,user); //更新用户表中用户信息 nextLevelMessage(&bg_img); user.levelID = level.next_level; level = nextLevel; //当前关卡变为下一关 haveNextLeve = true; } else { haveNextLeve = false; } return true; } //绘制结束界面 //省略gameOverScren方法 int main(){ bool quiet = false; //定义quiet变量,控制游戏结束 if (login() == false) { printf("登录失败n"); exit(-1); } initGame(); do{ if (!getLevelInfo(level, user.levelID)) { // 获取地图数据 printf("获取地图数据失败n"); exit(-2); } if (!loadLevel(level, map)) { //加载地图 printf("加载地图失败n"); exit(-3); } //構建地圖,同時記錄人和目的地所在的位置 //省略 //判断键盘按键进行处理 do{ if(_kbhit()){ //判断键盘是否有按键 char ch = _getch(); //获取键盘按下的值,并转为char字母 //判断人物移动方向 if(ch == KEY_UP){ controlGame(UP); }else if(ch == KEY_DOWN){ controlGame(DOWN); }else if(ch == KEY_LEFT){ controlGame(LEFT); }else if(ch == KEY_RIGHT){ controlGame(RIGHT); } if (gameOver()) { quiet = true; } } Sleep(100); }while(quiet == false); //当游戏没有结束,会一直循环 while(haveNextLeve); gameOverScren(&bg_img, quiet); ::system("pause"); //暂停 closegraph(); //关闭easyx return 0; } 更新database.cpp文件,增加updateUserLevel方法:
//更新用户关卡信息 bool updateUserLevel(int nextLevel, useInfo &user) { MYSQL mysql; char sql[256]; //将sql语句存入其中 int ret; //连接到数据库 if (!connectDB(mysql)) { return false; } snprintf(sql, 256, "update user set level_id=%d where id='%d';", nextLevel,user.id); ret = mysql_query(&mysql, sql); //成功返回0 if (ret) { printf("更新数据失败,%s,原因:%s", sql, mysql_error(&mysql)); mysql_close(&mysql); //关闭数据库 return false; } mysql_close(&mysql); //关闭数据库 return true; }结语:以上就是个人推箱子游戏的学习与总结,有任何不足的地方,欢迎大家提出意见与建议,感谢~



