环境要求:
1、VS2015以上
2、成功安装并配置图形库
项目注意事项:代码复制好以后,把下面的字符集改为多字节字符集
第 1 节 项目需求
实现一款推箱子游戏,效果如下图所示 , 具体规则:1. 箱子只能推动而不能拉动;2. 如果箱子前一格是地板或箱子目的地,则可以推动一个箱子往前走一格,如果箱子已经在箱子目的地则不能再推动;3. 推箱子的小人不能从箱子目的地上直接穿过;4. 注意不要把箱子推到死角上,不然就无法再推动它了;5. 所有箱子都成功推到箱子目的地,游戏结束,过关成功!
第 2 节 项目实现
2.1 模块划分
( 作用: 1. 化繁为简 2. 适合团队协作 3. 高质量代码)
2.1.1 地图初始化
搭台唱戏
戏台坐标系 (650 x 650) :
地图表示:
使用二维数组
1、游戏道具显示(墙、箱子、箱子目的地、小人、地板)2、便于程序在游戏过程中进行逻辑判断和控制小人向前一步的动作控制3、 判断游戏结果
道具表示:
墙 : 0 ,地板 : 1 ,箱子目的地 : 2, 小人 : 3, 箱子 : 4, 箱子命中目标 : 5
编码实现:
#include <graphics.h>
#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;
#define RATIO 61
#define SCREEN_WIDTH 960
#define SCREEN_HEIGHT 768
#define LINE 9
#define COLUMN 12
#define START_X 100
#define START_Y 150
IMAGE images[6];
/*游戏地图*/
int map[LINE][COLUMN] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0 },{ 0, 1, 4, 1, 0, 2, 1, 0, 2, 1, 0, 0 },
{ 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0 },
{ 0, 1, 0, 2, 0, 1, 1, 4, 1, 1, 1, 0 },
{ 0, 1, 1, 1, 0, 3, 1, 1, 1, 4, 1, 0 },
{ 0, 1, 2, 1, 1, 4, 1, 1, 1, 1, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};
int main(void) {IMAGE bg_img;//搭台唱戏initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);loadimage(&bg_img, _T("blackground.bmp"), SCREEN_WIDTH,SCREEN_HEIGHT, true);putimage(0, 0, &bg_img);//加载道具图标loadimage(&images[0], _T("wall.bmp"), RATIO, RATIO, true);loadimage(&images[1], _T("floor.bmp"), RATIO, RATIO, true);loadimage(&images[2], _T("des.bmp"), RATIO, RATIO, true);loadimage(&images[3], _T("man.bmp"), RATIO, RATIO, true);loadimage(&images[4], _T("box.bmp"), RATIO, RATIO, true);loadimage(&images[5], _T("box.bmp"), RATIO, RATIO, true);for (int i = 0; i < LINE; i++) {for (int j = 0; j < COLUMN; j++) {putimage(START_X + j * RATIO, START_Y + i * RATIO,&images[map[i][j]]);}}system("pause");return 0;
}
2.1.2 热键控制
热键定义:
左 => a下 => s上 => w右 => d退出 => q
#include <conio.h>
//控制键 上、下、左、右 控制方向,'q' 退出
#define KEY_UP 'w' //char 'a'
#define KEY_LEFT 'a'
#define KEY_RIGHT 'd'
#define KEY_DOWN 's'
#define KEY_QUIT 'q'
//游戏环节
bool quit = false;
do {if (_kbhit()) { //玩家按键char ch = _getch();if (ch == KEY_UP) {gameControl(UP);}else if (ch == KEY_DOWN) {gameControl(DOWN);}else if (ch == KEY_LEFT) {gameControl(LEFT);}else if (ch == KEY_RIGHT) {gameControl(RIGHT);}else if (ch == KEY_QUIT) {quit = true;}}Sleep(100);
} while (quit == false); //!quit
2.1.3 推箱子控制
/**********************************************
*实现游戏四个方向(上、下、左、右)的控制
* 输入:
* direct - 人前进方向
* 输出: 无
**********************************************/
void gameControl(enum _DIRECTION direct) {struct _POS next_pos = man;struct _POS next_next_pos = man;switch (direct) {case UP:next_pos.x--;next_next_pos.x -= 2;break;case DOWN:next_pos.x++;next_next_pos.x += 2;break;case LEFT:next_pos.y--;next_next_pos.y -= 2;break;case RIGHT:next_pos.y++;next_next_pos.y += 2;break;}//宏展开 next_pos.x>=0 && next_pos.x<LINE && next_pos.y>=0 &&next_pos.y < COLUMNif (isValid(next_pos) && map[next_pos.x][next_pos.y] == FLOOR) {//人的前方是地板changeMap(&next_pos, MAN); //小人前进一格changeMap(&man, FLOOR);man = next_pos;}else if (isValid(next_next_pos) && map[next_pos.x][next_pos.y] ==BOX) {//人的前方是箱子//两种情况,箱子前面是地板或者是箱子目的地if (map[next_next_pos.x][next_next_pos.y] == FLOOR) {changeMap(&next_next_pos, BOX);changeMap(&next_pos, MAN); //小人前进一格changeMap(&man, FLOOR);man = next_pos;}else if (map[next_next_pos.x][next_next_pos.y] == BOX_DES) {changeMap(&next_next_pos, HIT);changeMap(&next_pos, MAN); //小人前进一格changeMap(&man, FLOOR);man = next_pos;}}
}
2.1.4 游戏结束
/**********************************************
*判断游戏是否结束,如果不存在任何一个箱子目的地,就代表游戏结束
*输入: 无
*返回值:
* true - 游戏结束 false - 游戏继续
**********************************************/
bool isGameOver() {for (int i = 0; i < LINE; i++) {for (int j = 0; j < COLUMN; j++) {if (map[i][j] == BOX_DES) return false;}}return true;
}
/**********************************************
*游戏结束场景,在玩家通关后显示
*输入:
* bg - 背景图片变量的指针
*返回值: 无
**********************************************/
void gameOverScene(IMAGE* bg) {putimage(0, 0, bg);settextcolor(WHITE);RECT rec = { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT };settextstyle(20, 0, _T("宋体"));drawtext(_T("恭喜您~ \n 您终于成为了一个合格的搬箱子老司机!"),&rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
//main 函数中
if (isGameOver()) {gameOverScene(&bg_img);quit = true;
}
3、完整代码实现:
box_main.cpp:
#pragma once#include"box_man.h"int main() {initgraph(WEIGHT, HEIGHT);IMAGE bg_img;//参数分别表示图片变量的地址,图片的路径,图片的高度,宽度,是否要拉伸//用_T()是为了进行编码转换,也可以更改项目属性——高级——多字符集loadimage(&bg_img, _T("blackground.bmp"), WEIGHT, HEIGHT, true);//加载图片到内存putimage(0, 0, &bg_img);//把图片显示到窗口,参数分别表示坐标x,坐标y(从左上角开始计算),图片变量地址//加载道具loadimage(&images[WALL], _T("wall_right.bmp"), RATIO, RATIO, true);//墙loadimage(&images[FLOOR], _T("floor.bmp"), RATIO, RATIO, true);//地板loadimage(&images[BOX_DES], _T("des.bmp"), RATIO, RATIO, true);//目的地loadimage(&images[MAN], _T("man.jpg"), RATIO, RATIO, true);//小人loadimage(&images[BOX], _T("box.jpg"), RATIO, RATIO, true);//箱子loadimage(&images[HIT], _T("des.jpg"), RATIO, RATIO, true);//目标命中,地图刚开始时没有数字5的,因此这个到后面判断游戏才发挥作用//加载到地图上for (int i = 0; i < LINE; i++) {for (int j = 0; j < COLUMN; j++) {if (map1[i][j] == MAN) {//找到小人的位置man.x = i;man.y = j;}putimage(WEIGHT_BIAS + j * RATIO, HEIGHT_BIAS + i * RATIO, &images[map1[i][j]]);//核心代码段!!这个代码非常漂亮,利用行列坐标直接求出图片的位置}}//进入游戏环节bool quit = false;do{if (_kbhit()) //玩家有敲击键盘的操作{char ch = _getch(); //获取敲击的热键switch (ch){case 'w':gameControl(UP);break;case 'a':gameControl(LEFT);break;case 's':gameControl(DOWN);break;case 'd':gameControl(RIGHT);break;case 'q':quit = true;break;default:break;}Sleep(100); //因为一直在循环,一直在消耗CPU,休眠可以节约CPU资源if (isGameOver()) { //游戏结束GameOverField(&bg_img);quit = true;}} } while (quit == false);//游戏结束system("pause");closegraph();return 0;
}
function.cpp:
#include<iostream>
#include<iostream>
#include<Windows.h>
#include<graphics.h>
#include<stdlib.h>
#include<string>
#include<conio.h> //获取键盘的热键
#include<mmsystem.h>//音乐头文件
#pragma comment(lib,"winmm.lib")//支持音乐播放的库文件
using namespace std;#define WEIGHT 1190 //游戏窗口的宽度
#define HEIGHT 800 //游戏窗口的高度#define LINE 9 //地图的行数
#define COLUMN 12 //地图的列数#define KEY_UP 'w' //上键
#define KEY_LEFT 'a' //左键
#define KEY_DOWN 's' //下键
#define KEY_RIGHT 'd' //右键
#define KEY_QUIT 'q' //退出#define WEIGHT_BIAS 300 //地图离整个画布的横向偏移
#define HEIGHT_BIAS 150 //地图离整个画布的纵向偏移
#define RATIO 61 //小人物、强、箱子等元素的长和宽//把判断条件用宏来替换
#define isValid(pos) pos.x >= 0 && pos.x < LINE && pos.y >= 0 && pos.y < COLUMN && map1[pos.x][pos.y]//小人所在二维数组的位置,行数和列数
struct _POS {//很多开源项目结构体名用下划线开头int x;int y;
};
struct _POS man;typedef enum _DIRECTION {UP,DOWN,LEFT,RIGHT
}DIRECTION;//用枚举定义道具
enum _PROPS {WALL, //墙FLOOR, //地板BOX_DES, //目的地MAN, //小人BOX, //箱子HIT, //命中目标ALL //为什么最后还要定义一个ALL,为了定义下面images数组的长度,这是顶尖项目的经验
};IMAGE images[ALL]; //存放各类道具图片变量//地图1,假设后面还要做几个关卡,那么map1表示第一关
int map1[LINE][COLUMN] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0 },
{ 0, 1, 4, 1, 0, 2, 1, 0, 2, 1, 0, 0 },
{ 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0 },
{ 0, 1, 0, 2, 0, 1, 1, 4, 1, 1, 1, 0 },
{ 0, 1, 1, 1, 0, 3, 1, 1, 1, 4, 1, 0 },
{ 0, 1, 2, 1, 1, 4, 1, 1, 1, 1, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};/*
小人发生移动时,改变地图中游戏的道具line 地图的行下标colum 地图的列下标pros 道具的类型
*/void changeMap(struct _POS* pos, enum _PROPS pros) {map1[pos->x][pos->y] = pros; //更改地图的状态putimage(WEIGHT_BIAS + pos->y * RATIO, HEIGHT_BIAS + pos->x * RATIO, &images[pros]);}/*
实现游戏四个方向的移动
这里有很大的优化空间
*/
//版本1
//void gameControl(DIRECTION direct) {
// if (direct == UP) {//先处理前进方向是地板的情况
// if (man.x - 1 >= 0 && map1[man.x - 1][man.y] == FLOOR) {//防御式编程,判断坐标的合法性
// changeMap(man.x-1, man.y, MAN); //小人的往上走一格
// changeMap(man.x, man.y, FLOOR);//小人原来的位置设置为地板
// man.x = man.x - 1;//调整小人的坐标
// }
// }
// else if (direct == DOWN) {
// if (man.x + 1 < LINE && map1[man.x + 1][man.y] == FLOOR) {
// changeMap(man.x + 1, man.y, MAN); //小人的往下走一格
// changeMap(man.x, man.y, FLOOR);//小人原来的位置设置为地板
// man.x = man.x + 1;//调整小人的坐标
// }
// }
// else if (direct == LEFT) {
// if (man.y - 1 >= 0 && map1[man.x][man.y - 1] == FLOOR) {
// changeMap(man.x, man.y-1, MAN); //小人的往左走一格
// changeMap(man.x, man.y, FLOOR);//小人原来的位置设置为地板
// man.y = man.y - 1;//调整小人的坐标
// }
// }
// else if (direct == RIGHT) {
// if (man.y + 1 < COLUMN && map1[man.x][man.y + 1] == FLOOR) {
// changeMap(man.x, man.y + 1, MAN); //小人的往右走一格
// changeMap(man.x, man.y, FLOOR);//小人原来的位置设置为地板
// man.y = man.y + 1;//调整小人的坐标
// }
// }
//}//版本2,简化代码!!提高代码重用率
void gameControl(DIRECTION direct) {struct _POS next_man = man;struct _POS next_box = man;switch (direct) {case UP:next_man.x--;next_box.x -= 2;break;case DOWN:next_man.x++;next_box.x += 2;break;case LEFT:next_man.y--;next_box.y -= 2;break;case RIGHT:next_man.y++;next_box.y += 2;break;}/*判断条件太长,用宏定义替换来优化*///if (next_man.x >= 0 && next_man.x < LINE &&// next_man.y >= 0 && next_man.y < COLUMN &&// map1[next_man.x][next_man.y] == FLOOR) {//防御式编程,判断坐标的合法性// changeMap(next_man.x, next_man.y, MAN); //小人的往上走一格// changeMap(man.x, man.y, FLOOR);//小人原来的位置设置为地板// man = next_man;//调整小人的坐标//}if (isValid(next_man) && map1[next_man.x][next_man.y] == FLOOR) {//防御式编程,判断坐标的合法性//changeMap(next_man.x, next_man.y, MAN); //小人的往上走一格//changeMap(man.x, man.y, FLOOR);//小人原来的位置设置为地板//结构体传递用指针传递,提高效率,进一步优化changeMap(&next_man, MAN);changeMap(&man, FLOOR);man = next_man;//调整小人的坐标}else if (isValid(next_man) && map1[next_man.x][next_man.y] == BOX) {//第一种情况:箱子前面是地板if (isValid(next_box) && map1[next_box.x][next_box.y] == FLOOR) {changeMap(&next_box, BOX);changeMap(&next_man, MAN);changeMap(&man, FLOOR);man = next_man;}//第二种情况,前面是箱子else if (isValid(next_box) && map1[next_box.x][next_box.y] == BOX_DES) {changeMap(&next_box, HIT);changeMap(&next_man, MAN);changeMap(&man, FLOOR);man = next_man;}//第二种情况,箱子前面是目的地}
}/*
判断游戏是否结束,如果地图中没有箱子目的地,说明说有箱子已经全部移动到指定位置
游戏结束,反之没有结束
*/
bool isGameOver() {for (int i = 0; i < LINE; i++) {for (int j = 0; j < COLUMN; j++) {if (map1[i][j] == BOX_DES) {return false;}}}return true;
}/*
游戏结束场景
*/
void GameOverField(IMAGE* bg) {putimage(0, 0, bg);settextcolor(WHITE);RECT rec = { 0,0,WEIGHT,HEIGHT };settextstyle(100, 0, _T("宋体"));loadimage(NULL, _T("游戏结束背景.jpg"));//加载胜利图片//音乐播放mciSendString(_T("play 游戏结束.mp3"), 0, 0, 0);outtextxy(WEIGHT_BIAS, HEIGHT / 2, _T("恭喜你通关啦"));}
box_man.h:
#pragma once //头文件文件只包含一次#ifndef TEST_H
#define TEST_H
#include<graphics.h>
#include<conio.h> //获取键盘的热键#define WEIGHT 1190 //游戏窗口的宽度
#define HEIGHT 800 //游戏窗口的高度#define LINE 9 //地图的行数
#define COLUMN 12 //地图的列数#define RATIO 61 //小人物、强、箱子等元素的长和宽#define WEIGHT_BIAS 300 //地图离整个画布的横向偏移
#define HEIGHT_BIAS 150 //地图离整个画布的纵向偏移enum _PROPS {WALL, //墙FLOOR, //地板BOX_DES, //目的地MAN, //小人BOX, //箱子HIT, //命中目标ALL //为什么最后还要定义一个ALL,为了定义下面images数组的长度,这是顶尖项目的经验
};extern IMAGE images[ALL]; //存放各类道具图片变量typedef enum _DIRECTION {UP,DOWN,LEFT,RIGHT
}DIRECTION;struct _POS {//很多开源项目结构体名用下划线开头int x;int y;
};
extern struct _POS man;extern int map1[LINE][COLUMN];//函数声明一定要放在最后!!void changeMap(struct _POS* pos, enum _PROPS pros);
void gameControl(DIRECTION direct);
bool isGameOver();
void GameOverField(IMAGE* bg);#endif
如果图形库正常安装,项目属性字符集改为多字节字符集就可以运行了。
运行:(我这里用的其他图片元素做的)
胜利后:
可能大家代码写好但是没有图像资源文件:
大家可以私聊我,一件三连免费送。没有图像资源光有代码运行不起来。