实验介绍
这个小项目是参考实验楼的免费项目来做的,因此整个博客基本与其相同。有兴趣的同学可以直接在其官网查找学习即可。我的实验环境也主要用自己的虚拟机来完成,这样也便于保存实验进度。仅为个人学习,绝无抄袭的意思。https://www.shiyanlou.com/courses/running
实验内容
本节实验主要通过 C++ 和 openGL 库来实现了一个吃豆人的小游戏。主要的课程内容涉及到 C++ 相关的序列容器 vector 、deque 和迭代器,以及 openGL 相关的窗口初始化与绘图功能。
知识点
- C++编程基础
- C++模板库使用
- openGL库使用
实验环境
- 虚拟机配置ubuntu 16.04
- openGL库
代码获取
1 | //下载源代码 |
环境准备
安装openGL库
1 | sudo apt-get update |
编译程序
进入/Pacman/src/目录,make一下1
make
运行程序
需要进入/Pacman/bin/目录1
./Pacman
运行效果
项目分析
模块拆分
在开始编写程序之前我们需要利用 C++ 的思想把实际问题转换成一个个对象。说到游戏应该就能想到角色、地图和怪物以及操作命令等。我们把这些元素细化就可以得到游戏设计模块:
- 角色设计
- 地图设计
- 怪物设计
- 食物设计
- 操作设计
- 界面设计
开发说明
- 编程语言:C++
- 开发环境:Linux ubuntu 16.04
- 第三方库:openGL
备注:openGL 是一种图形界面处理库,提供了许多 API 给其他语言调用。我们的地图、角色和界面等等都需要调用它。
详细设计
提前准备
在这里首先需要为我们的项目创建一个文件夹,进入 /home/shiyanlou/Code/ 目录下,将我们这次的项目命名为 Pacman。进入这个文件夹创建三个子文件夹 inlcude(存放头文件)、src(存放源代码)和 bin(存放可执行文件)。并创建相应的代码文件,具体内容与结构如下:
预定义与全局变量
首先在include文件夹中创建main.h,用来存放头文件和一些全局变量,全局变量的定义是方便所有的模块访问并反映游戏的状态。全局变量定义后,在主函数中进行赋值,所有模块就都能访问了。
1 | //main.h |
主函数与窗口
在主函数中对全局变量进行赋值,然后初始化一个窗口。在这里将使用到openGL 的初始化接口。具体包括:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void glutInit(int* argc,char** argv); //初始化
void glutInitDisplayMode(unsighed int mode); //定义显示方式 mode:是一个 GLUT 库里预定义的可能的布尔组合,使用mode去指定颜色模式,数量和缓冲区类型
void glutInitWindowSize(int width, int height); //设置窗口大小 width:窗口宽度 height:窗口高度
void glutInitWindowPosition(int x, int y); //确定窗口位置(默认左上角)
int glutCreateWindow(char* title); //设置窗口的标题,title:标题内容
void glutDisplayFunc(void(*func) (void)); //注册当前窗口的显示回调函数,void (*func)(void):回调函数名称,在这里我们用的是 display
void glutReshapeFunc(void(*func)(int width, int height)); // 重新设置窗口,void(*func)(int width,int height):回调函数名称,在这里我们用的是 reshape
void glutIdleFunc(void(*func)(void)); //调用渲染函数,void(*func)(int width,int height):回调函数名称,在这里我们用的是 reshape
void glutKeyboardFunc(void(*func)(unsigned char key, int x, int y)); //处理按键事件
void glutKeyboardUpFunc(void(*func)(unsigned char key, int x, int y)); //处理松开按键事件
void glutMainLoop(void); //循环执行
将下面的代码写入 Code/Pacman/src/Pacman.cpp 文件中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56//Pacman.cpp
using namespace std;
bool replay = false; //检查是否启动游戏
bool over = true; //检查游戏是否结束
float SquareSize = 50.0; //一个单元大小
float xIncrement = 0; // the coordinate of x
float yIncrement = 0; // the coordinate of y
int rotation = 0; // direction
float* monster1 = new float[3] {10.5, 8.5, 1.0}; //第一个怪物的坐标和方向
float* monster2 = new float[3] {13.5, 1.5, 2.0}; //第二个怪物的坐标和方向
float* monster3 = new float[3] {4.5, 6.5, 3.0}; //第三个怪物的坐标和方向
float* monster4 = new float[3] {2.5, 13.5, 4.0}; //第四个怪物的坐标和方向
vector<int> border = { 0, 0, 15, 1, 15, 15, 14, 1, 0, 14, 15, 15, 1, 14, 0, 0 }; //墙坐标
//障碍物坐标 (为了清晰分为三个)
vector<int> obstaclesTop = { 2, 2, 3, 6, 3, 6, 4, 5, 4, 2, 5, 4, 5, 3, 6, 5, 6, 1, 9, 2, 7, 2, 8, 5, 9, 5, 10, 3, 10, 4, 11, 2, 11, 5, 12, 6, 12, 6, 13, 2 };
vector<int> obstaclesMiddle = { 2, 9, 3, 7, 3, 7, 4, 8, 4, 9, 5, 11, 5, 6, 6, 10, 6, 10, 7, 8, 7, 8, 8, 9, 6, 7, 7, 6, 8, 6, 9, 7, 10, 6, 9, 10, 9, 10, 8, 8, 11, 9, 10, 11, 11, 8, 12, 7, 12, 7, 13, 9 };
vector<int> obstaclesBottom = { 2, 10, 3, 13, 3, 13, 4, 12, 5, 12, 6, 13, 6, 13, 7, 11, 8, 11, 9, 13, 9, 13, 10, 12, 11, 12, 12, 13, 12, 13, 13, 10 };
deque<float> food = { 1.5, 1.5, 1.5, 2.5, 1.5, 3.5, 1.5, 4.5, 1.5, 5.5, 1.5, 6.5, 1.5, 7.5, 1.5, 8.5, 1.5, 9.5, 1.5, 10.5, 1.5, 11.5, 1.5, 12.5, 1.5, 13.5, 2.5, 1.5, 2.5, 6.5, 2.5, 9.5, 2.5, 13.5, 3.5, 1.5, 3.5, 2.5, 3.5, 3.5, 3.5, 4.5, 3.5, 6.5, 3.5, 8.5, 3.5, 9.5, 3.5, 10.5, 3.5, 11.5, 3.5, 13.5, 4.5, 1.5, 4.5, 4.5, 4.5, 5.5, 4.5, 6.5, 4.5, 7.5, 4.5, 8.5, 4.5, 11.5, 4.5, 12.5, 4.5, 13.5, 5.5, 1.5, 5.5, 2.5, 5.5, 5.5, 5.5, 10.5, 5.5, 11.5, 5.5, 13.5, 6.5, 2.5, 6.5, 3.5, 6.5, 4.5, 6.5, 5.5, 6.5, 7.5, 6.5, 10.5, 6.5, 13.5, 7.5, 5.5, 7.5, 6.5, 7.5, 7.5, 7.5, 9.5, 7.5, 10.5, 7.5, 11.5, 7.5, 12.5, 7.5, 13.5, 8.5, 2.5, 8.5, 3.5, 8.5, 4.5, 8.5, 5.5, 8.5, 7.5, 8.5, 10.5, 8.5, 13.5, 9.5, 1.5, 9.5, 2.5, 9.5, 5.5, 9.5, 10.5, 9.5, 11.5, 9.5, 13.5, 10.5, 1.5, 10.5, 4.5, 10.5, 5.5, 10.5, 6.5, 10.5, 7.5, 10.5, 8.5, 10.5, 11.5, 10.5, 12.5, 10.5, 13.5, 11.5, 1.5, 11.5, 2.5, 11.5, 3.5, 11.5, 4.5, 11.5, 5.5, 11.5, 6.5, 11.5, 8.5, 11.5, 9.5, 11.5, 10.5, 11.5, 11.5, 11.5, 13.5, 12.5, 1.5, 12.5, 6.5, 12.5, 9.5, 12.5, 13.5, 13.5, 1.5, 13.5, 2.5, 13.5, 3.5, 13.5, 4.5, 13.5, 5.5, 13.5, 6.5, 13.5, 7.5, 13.5, 8.5, 13.5, 9.5, 13.5, 10.5, 13.5, 11.5, 13.5, 12.5, 13.5, 13.5 };
vector<vector<bool>> bitmap; // 2d图像,可移动区域
bool* keyStates = new bool[256]; // 按键记录
int points = 0; // 得分
//主函数
int main(int argc, char** argv){
// 初始化并创建屏幕
glutInit(&argc, argv);
glutDisplayMode(GLUT_DOUBLE | GLUT_RGB);//显示方式(双缓冲区,颜色索引方式)
glutInitWindowSize(750,750);
glutInitWindowPosition(500,50);//窗口起始位置
glutCreateWindow("Pacman - by Desmon");
//定义所有控制功能
glutDisplayFunc(display);//显示窗口
glutReshapeFunc(reshape); //重置窗口
glutIdleFunc(display); //loop
glutKeyboardFunc(keyPressed);
glutKeyboardUpFunc(keyUp);
//运行游戏
init();
glutMainLoop();
return 0;
}
初始化地图
设计了开始界面后,下面就是进入游戏,首先就是显示地图,在这里我们用障碍物填充地图,这样没有障碍物的地方就可以通行了,形成了一个迷宫。
头文件
将下面的代码写入 Code/Pacman/include/init.h 文件中:1
2
3
4
5
6
7
8
9/* #ifndef 指示检测指定的预处理器变量是否未定义,
如果未定义,那么跟在后面的所有指示被处理,直到出现#endif;
如果已定义,那么#ifndef测试为假,该指示和#endif指示间的代码都被忽略。*/
//init.h
void init(void);
源代码
OpenGL颜色相关的方法见此博客:https://blog.csdn.net/hebbely/article/details/69951068。
将下面的代码写入 Code/Pacman/src/init.cpp 文件中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//init.cpp
//初始化游戏
void init(void){
//清除屏幕
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);//着色
//重置按键
for (int i = 0; i < 256; i++){
keyStates[i] = false;
}
//用障碍物填充地图
bitmap.push_back({ true, true, true, true, true, true, true, true, true, true, true, true, true, true, true });
bitmap.push_back({ true, false, false, false, false, false, false, false, false, false, false, false, false, false, true });
bitmap.push_back({ true, false, true, true, true, true, false, true, true, false, true, true, true, false, true });
bitmap.push_back({ true, false, false, false, false, true, false, true, false, false, false, false, true, false, true});
bitmap.push_back({ true, false, true, true, false, false, false, false, false, true, true, false, false, false, true});
bitmap.push_back({ true, false, false, true, true, false, true, true, true, true, false, false, true, false, true});
bitmap.push_back({ true, true, false, false, false, false, true, false, true, true, false, true, true, false, true});
bitmap.push_back({ true, true, true, true, true, false, false, false, true, false, false, false, false, false, true});
bitmap.push_back({ true, true, false, false, false, false, true, false, true, true, false, true, true, false, true });
bitmap.push_back({ true, false, false, true, true, false, true, true, true, true, false, false, true, false, true });
bitmap.push_back({ true, false, true, true, false, false, false, false, false, true, true, false, false, false, true });
bitmap.push_back({ true, false, false, false, false, true, false, true, false, false, false, false, true, false, true });
bitmap.push_back({ true, false, true, true, true, true, false, true, true, false, true, true, true, false, true });
bitmap.push_back({ true, false, false, false, false, false, false, false, false, false, false, false, false, false, true });
bitmap.push_back({ true, true, true, true, true, true, true, true, true, true, true, true, true, true, true });
}
迷宫
上面我们已经对地图进行了初始化,也对障碍物进行了初始化,接下来就需要使用 openGL 来绘图了。这里涉及到的接口包括:
void glRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2 ); 绘画矩形 x1:矩形左上角横坐标 y1:矩形左上角纵坐标 x2:矩形右下角横坐标 y2:矩形右下角纵坐标
头文件
将下面的代码写入 /Code/Pacman/include/laberynth.h 文件中:1
2
3
4
5
6//laberynth.h
void drawLaberynth();
源代码
将下面的代码写入 Code/Pacman/src/laberynth.cpp 文件中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//laberynth.cpp
//障碍物与墙体的绘制方法
void drawLaberynth(){
glColor3f(1.0,1.0,1.0); //设置绘图颜色-白色
//边界
for (vector<int>::size_type i = 0; i < border.size(); i = i + 4){
glRectf(border.at(i) * squareSize, border.at(i+1) * squareSize, border.at(i+2) * squareSize, border.at(i+3) * squareSize);
}
//障碍物,分为上中下三部分绘图
for (vector<int>::size_type j = 0; j < obstaclesBottom.size(); j = j + 4){
glRectf(obstaclesBottom.at(j) * squareSize, obstaclesBottom.at(j + 1)*squareSize, obstaclesBottom.at(j + 2)*squareSize, obstaclesBottom.at(j + 3)*squareSize);
for (vector<int>::size_type k = 0; k < obstaclesMiddle.size(); k = k + 4){
glRectf(obstaclesMiddle.at(k) * squareSize, obstaclesMiddle.at(k + 1)*squareSize, obstaclesMiddle.at(k + 2)*squareSize, obstaclesMiddle.at(k + 3)*squareSize);
}
for (vector<int>::size_type p = 0; p < obstaclesTop.size(); p = p + 4){
glRectf(obstaclesTop.at(p) * squareSize, obstaclesTop.at(p + 1)*squareSize, obstaclesTop.at(p + 2)*squareSize, obstaclesTop.at(p + 3)*squareSize);
}
//glRectf函数是绘制矩形
}
游戏角色
在上面已经建立好了地图和迷宫,接下来一步,应该创建一个游戏角色,也就是吃豆人。按照以往的经验,吃豆人可以设计为一个圆形然后拥有一张嘴。需要用到的新接口包括:1
2void glBegin(GLenum mode); //表示绘图方式
void glVertex2f(GLfloat x, GLfloat y); //指定画笔位置
头文件
将下面的代码写入 Code/Pacman/include/createpacman.h 文件中:1
2
3
4
5
6
7//createpacman.h
void drawPacman(float positionX, float positionY, float rotation);
源代码
将下面的代码写入 Code/Pacman/src/createpacman.cpp 文件中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//createpacman.cpp
void drawPacman(float positionX, float positionY, float rotation){
int x, y;
glBegin(GL_LINES);//创建一条线
glColor3f(1.0, 1.0, 0.0); //黄色
for (int k=0; k<32; k++){
x = (float)k / 2.0 * cos((30 + 90*rotation) * M_PI / 180.0) + (positionX*squareSize);
y = (float)k / 2.0* sin((30 + 90 * rotation) * M_PI / 180.0) + (positionY*squareSize);
for (int i = 30; i < 330; i++){
glVertex2f(x, y);
x = (float)k / 2.0 * cos((i + 90 * rotation) * M_PI / 180.0) + (positionX*squareSize);
y = (float)k / 2.0* sin((i + 90 * rotation) * M_PI / 180.0) + (positionY*squareSize);
glVertex2f(x, y);
}
}
glEnd();//绘图结束
}
怪物设计
怪物设计这里我们需要考虑到几点,第一怪物的外形设计、第二怪物的移动设计,第三怪物与障碍物的碰撞检测。
头文件
将下面的代码写入 Code/Pacman/include/monster.h 文件中:
1 | //monster.h |
源代码
将下面的代码写入 /Code/Pacman/src/monster.cpp 文件中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90//monster.cpp
//绘画怪物
void drawMonster(float positionX, float positionY, float r, float g, float b){
int x, y;
glBegin(GL_LINES);
glColor3f(r, g, b);
//头
for (int k=0; k<32; k++){
x = (float)k / 2.0 * cos(360 * M_PI / 180.0) + (positionX*squareSize);
y = (float)k / 2.0 * sin(360 * M_PI / 180.0) + (positionY*squareSize);
for (int i=180; i<=360; i++){
glVertex2f(x, y);
x = (float)k / 2.0 * cos(i * M_PI / 180.0) + (positionX*squareSize);
y = (float)k / 2.0* sin(i * M_PI / 180.0) + (positionY*squareSize);
glVertex2f(x, y);
}
}
glEnd();
//身体
glRectf((positionX*squareSize) - 17, positionY*squareSize, (positionX*squareSize) + 15, (positionY*squareSize) + 15);
//眼睛和腿
glBegin(GL_POINTS);
glColor3f(0, 0.2, 0.4);
glVertex2f((positionX*squareSize) - 11, (positionY*squareSize) + 14); //legs
glVertex2f((positionX*squareSize) - 1, (positionY*squareSize) + 14); //legs
glVertex2f((positionX*squareSize) + 8, (positionY*squareSize) + 14); //legs
glVertex2f((positionX*squareSize) + 4, (positionY*squareSize) - 3); //eyes
glVertex2f((positionX*squareSize) - 7, (positionY*squareSize) - 3); //eyes
glEnd();
}
//怪物移动
void updateMonster(float *monster, int id){
// 找到当前位置
int x1Quadrant = (int)((monster[0] - (2/squareSize)) - (16.0 *cos(360 * M_PI / 180.0)) / squareSize);
int x2Quadrant = (int)((monster[0] + (2/squareSize)) + (16.0 *cos(360 * M_PI / 180.0)) / squareSize);
int y1Quadrant = (int)((monster[1] - (2/squareSize)) - (16.0 *cos(360 * M_PI / 180.0)) / squareSize);
int y2Quadrant = (int)((monster[1] + (2/squareSize)) + (16.0 *cos(360 * M_PI / 180.0)) / squareSize);
//怪物移动和撞墙检测
switch ((int)monster[2]){ //不同方向
case 1:
if (!bitmap.at(x1Quadrant).at((int)monster[1])){
monster[0] -= 2/squareSize;
}else {
int current = monster[2];
do{
monster[2] = (rand() % 4) + 1;
} while (current == (int) monster[2]);
}
break;
case 2:
if (!bitmap.at(x2Quadrant).at((int)monster[1])){
monster[0] += 2 / squareSize;
}
else {
int current = monster[2];
do{
monster[2] = (rand() % 4) + 1;
} while (current == (int)monster[2]);
}
break;
case 3:
if (!bitmap.at((int)monster[0]).at(y1Quadrant)){
monster[1] -= 2 / squareSize;
}
else {
int current = monster[2];
do{
monster[2] = (rand() % 4) + 1;
} while (current == (int)monster[2]);
}
break;
case 4:
if (!bitmap.at((int)monster[0]).at(y2Quadrant)){
monster[1] += 2 / squareSize;
}
else {
int current = monster[2];
do{
monster[2] = (rand() % 4) + 1;
} while (current == (int)monster[2]);
}
break;
default:
break;
}
}
食物设计
食物应该设计在可移动区域内,也就是迷宫内,在主函数中我们已经对食物坐标进行了初始化,这里需要进行绘图,另外还需考虑食物被吃的情况。这里用到的新接口包括:
void glPointSize( GLfloat size);点大小设置
头文件
将下面的代码写入 /Code/Pacman/include/food.h 文件中:1
2
3
4
5
6
7
8
9//food.h
bool foodEaten(int x, int y, float pacmanX, float pacmanY);
void drawFood(float pacmanX, float pacmanY);
源代码
1 | //food.cpp |
角色移动命令
现在已经设计好了迷宫、食物和怪物,接下来就可以做角色的控制设计了,在这里我们只设计了角色的上下左右移动。
头文件
将下面的代码写入 Code/Pacman/include/control.h 文件中:1
2
3
4
5
6
7
8
9//control.h
void keyPressed(unsigned char key, int x, int y);
void keyUp(unsigned char key, int x, int y);
void resetGame();
void keyOperations();
源代码
将下面的代码写入 Code/Pacman/src/control.cpp 文件中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78//control.cpp
//设置按键
void keyPressed(unsigned char key, int x, int y){
keyStates[key] = true;
}
//释放按键
void keyUp(unsigned char key, int x, int y){
keyStates[key] = false;
}
//重置所有元素并开始游戏
void resetGame(){
over = false;
xIncrement = 0;
yIncrement = 0;
rotation = 0;
monster1 = new float[3] {10.5, 8.5, 1.0};
monster2 = new float[3] {13.5, 1.5, 2.0};
monster3 = new float[3] {4.5, 6.5, 3.0};
monster4 = new float[3] {2.5, 13.5, 4.0};
points = 0;
for (int i = 0; i < 256; i++){
keyStates[i] = false;
}
food = { 1.5, 1.5, 1.5, 2.5, 1.5, 3.5, 1.5, 4.5, 1.5, 5.5, 1.5, 6.5, 1.5, 7.5, 1.5, 8.5, 1.5, 9.5, 1.5, 10.5, 1.5, 11.5, 1.5, 12.5, 1.5, 13.5, 2.5, 1.5, 2.5, 6.5, 2.5, 9.5, 2.5, 13.5, 3.5, 1.5, 3.5, 2.5, 3.5, 3.5, 3.5, 4.5, 3.5, 6.5, 3.5, 8.5, 3.5, 9.5, 3.5, 10.5, 3.5, 11.5, 3.5, 13.5, 4.5, 1.5, 4.5, 4.5, 4.5, 5.5, 4.5, 6.5, 4.5, 7.5, 4.5, 8.5, 4.5, 11.5, 4.5, 12.5, 4.5, 13.5, 5.5, 1.5, 5.5, 2.5, 5.5, 5.5, 5.5, 10.5, 5.5, 11.5, 5.5, 13.5, 6.5, 2.5, 6.5, 3.5, 6.5, 4.5, 6.5, 5.5, 6.5, 7.5, 6.5, 10.5, 6.5, 13.5, 7.5, 5.5, 7.5, 6.5, 7.5, 7.5, 7.5, 9.5, 7.5, 10.5, 7.5, 11.5, 7.5, 12.5, 7.5, 13.5, 8.5, 2.5, 8.5, 3.5, 8.5, 4.5, 8.5, 5.5, 8.5, 7.5, 8.5, 10.5, 8.5, 13.5, 9.5, 1.5, 9.5, 2.5, 9.5, 5.5, 9.5, 10.5, 9.5, 11.5, 9.5, 13.5, 10.5, 1.5, 10.5, 4.5, 10.5, 5.5, 10.5, 6.5, 10.5, 7.5, 10.5, 8.5, 10.5, 11.5, 10.5, 12.5, 10.5, 13.5, 11.5, 1.5, 11.5, 2.5, 11.5, 3.5, 11.5, 4.5, 11.5, 5.5, 11.5, 6.5, 11.5, 8.5, 11.5, 9.5, 11.5, 10.5, 11.5, 11.5, 11.5, 13.5, 12.5, 1.5, 12.5, 6.5, 12.5, 9.5, 12.5, 13.5, 13.5, 1.5, 13.5, 2.5, 13.5, 3.5, 13.5, 4.5, 13.5, 5.5, 13.5, 6.5, 13.5, 7.5, 13.5, 8.5, 13.5, 9.5, 13.5, 10.5, 13.5, 11.5, 13.5, 12.5, 13.5, 13.5 };
}
//控制吃豆人移动
void keyOperations(){
// 获得当前位置
float x = (1.5 + xIncrement) * squareSize;
float y = (1.5 + yIncrement) * squareSize;
// 更新按键
if (keyStates['a']){ //往左
x -= 2;
int x1Quadrant = (int)((x - 16.0 *cos(360 * M_PI / 180.0)) / squareSize);
if (!bitmap.at(x1Quadrant).at((int)y/squareSize)){
xIncrement -= 2 / squareSize;
rotation = 2;
}
}
if (keyStates['d']){ //往右
x += 2;
int x2Quadrant = (int)((x + 16.0 *cos(360 * M_PI / 180.0)) / squareSize);
if (!bitmap.at(x2Quadrant).at((int)y / squareSize)){
xIncrement += 2 / squareSize;
rotation = 0;
}
}
if (keyStates['w']){ //往上
y -= 2;
int y1Quadrant = (int)((y - 16.0 *cos(360 * M_PI / 180.0)) / squareSize);
if (!bitmap.at((int)x/squareSize).at(y1Quadrant)){
yIncrement -= 2 / squareSize;
rotation = 3;
}
}
if (keyStates['s']){ //往下
y += 2;
int y2Quadrant = (int)((y + 16.0 *cos(360 * M_PI / 180.0)) / squareSize);
if (!bitmap.at((int)x / squareSize).at(y2Quadrant)){
yIncrement += 2 / squareSize;
rotation = 1;
}
}
if (keyStates[' ']){ //开始游戏或重新游戏
if (!replay && over){
resetGame();
replay = true;
}
else if (replay && over){
replay = false;
}
}
}
开始界面
接着要准备一个欢迎界面,同时对游戏具体操作方式做一个声明,然后显示游戏元素。这里我们需要用到 openGL 的绘图接口。具体包括:
1 | void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); //红、绿、蓝和 alpha 值,指定值范围均为[ 0.0f,1.0f ] |
头文件
将下面的代码写入 Code/Pacman/include/gamestart.h 文件中:1
2
3
4
5
6
7
8
9//gamestart.h
void welcomeScreen();
void display();
void reshape(int w, int h);
源代码
将下面的代码写入 Code/Pacman/src/gamestart.cpp 文件中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90//gamestart.cpp
//欢迎界面
void welcomeScreen(){
glClearColor(0, 0.2, 0.4, 1.0);
string message = "*************************************";
string::iterator it = message.begin();
glRasterPos2f(150, 200);
while (it != message.end()){
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
}
message = "Pacman - by Desmon";
glColor3f(1,1,1); //white
glRasterPos2f(225, 250);
it = message.begin();
while (it != message.end()){
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
}
message = "*************************************";
glRasterPos2f(150, 300);
it = message.begin();
while (it != message.end()){
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
}
message = "To control Pacman use A to go right, D to go left, W to go up and S to go down.";
glRasterPos2f(50, 400);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *it++);
message = "To start or restart the game, press the space key.";
glRasterPos2f(170, 450);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *it++);
}
//显示屏幕和元素
void display(){
if (points == 1){
over = false;
}
keyOperations();
glClear(GL_COLOR_BUFFER_BIT); //清除颜色缓冲区(当前被激活为写操作的颜色缓存)
gameOver();
if (replay){
if (!over){
drawLaberynth();
drawFood((1.5 + xIncrement) * squareSize, (1.5 + yIncrement) * squareSize);
drawPacman(1.5 + xIncrement, 1.5 + yIncrement, rotation);
updateMonster(monster1, 1);
updateMonster(monster2, 2);
updateMonster(monster3, 3);
updateMonster(monster4, 4);
drawMonster(monster1[0], monster1[1], 0.0, 1.0, 1.0); //cyan
drawMonster(monster2[0], monster2[1], 1.0, 0.0, 0.0); //red
drawMonster(monster3[0], monster3[1], 1.0, 0.0, 0.6); //magenta
drawMonster(monster4[0], monster4[1], 1.0, 0.3, 0.0); //orange
}
else {
resultsDisplay();
}
}
else {
welcomeScreen();
}
glutSwapBuffers();
}
//重置窗口
void reshape(int w, int h){
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
glOrtho(0, 750, 750, 0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
游戏结果判断
到此游戏元素已经基本设计完成,这里我们给游戏设计一个结束判断模块,应该包括胜利条件和失败条件。
头文件
将下面的代码写入 /Code/Pacman/include/gameover.h 文件中:1
2
3
4
5
6//gameover.h
void gameOver();
源代码
将下面的代码写入 Code/Pacman/src/gameover.cpp 文件中:
1 | //gameover.cpp |
游戏结果界面
到这里已经是尾声了,我们的游戏也已经结束了。在结构界面除了得分以外,我们可以人性化的提供一些提示来进行接下来的操作。
头文件
将下面的代码写入 Code/Pacman/include/gameresult.h 文件中:1
2
3
4
5
6
7
8//gameresult.h
void resultsDisplay();
源代码
将下面的代码写入 Code/Pacman/src/gameresult.cpp 文件中:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69//gameresult.cpp
//游戏结果
void resultsDisplay(){
if (points == 106){
//胜利
string message = "*************************************";
string::iterator it = message.begin();
glRasterPos2f(170, 250);
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
message = "CONGRATULATIONS, YOU WON! ";
glColor3f(1, 1, 1);
glRasterPos2f(200, 300);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
message = "*************************************";
glRasterPos2f(170, 350);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
message = "To start or restart the game, press the space key.";
glRasterPos2f(170, 550);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *it++);
}else {
//Lost
string message = "*************************************";
string::iterator it = message.begin();
glRasterPos2f(210, 250);
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
message = "SORRY, YOU LOST ... ";
glColor3f(1, 1, 1);
glRasterPos2f(250, 300);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
message = "*************************";
glRasterPos2f(210, 350);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
message = "You got: ";
glRasterPos2f(260, 400);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
string result = to_string(points);
message = (char*)result.c_str();
glRasterPos2f(350, 400);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
message = " points!";
glRasterPos2f(385, 400);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, *it++);
message = "To start or restart the game, press the space key.";
glRasterPos2f(170, 550);
it = message.begin();
while (it!=message.end())
glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *it++);
}
}
Makefile(着重学习)
最后一步就是对源代码进行编译了,这里有两种方式,看个人喜好来选择。
- g++方式,这种方式好处是简单明了,弊端是每次编译都需要敲入有些麻烦。首先在终端进入Pacman/src/目录
1 | g++ *.cpp -std=c++11 -Wall -I../include -lglut -lGL -o ../bin/ |
- Makefile 方式,首先在 Pacman/src/ 目录下建立 Makefile 文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#编译方式
CC = g++
#C++版本,显示所有警告
VERSION = -g -std=c++11 -Wall
#头文件和库文件
INCLUDE = -I../include -lglut -lGL
#目标文件,最后生成文件
TARGET = ../bin/Pacman
#源代码路径
SRCS = $(wildcard *.cpp)
#编译为.o文件
OBJS = $(patsubst %cpp,%o,$(SRCS))
all:$(TARGET)
#执行编译
$(TARGET):$(OBJS)
$(CC) $(OBJS) $(VERSION) $(INCLUDE) -o $(TARGET)
%.o:%.cpp
$(CC) $(VERSION) $(INCLUDE) -c $<
#清除
.PHONY:clean
clean:
rm -rf $(OBJS) $(TARGET)