先上效果图。

游戏的实现主要基于EasyX图形库,有暂停界面、结束界面、游戏音效、游戏存档,并最终集成为一个exe文件。可谓麻雀虽小,五脏俱全 。这里先放出源代码和下载链接,以后有时间会以此为例写一个C++小游戏开发教程系列。

链接:https://pan.baidu.com/s/1sBPD-LTGCTa7bfkI1RloXg
提取码:8848

组件类及其定义:

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
#pragma once
#include<graphics.h>
#include<utility>
using namespace std;
typedef pair<double, double> BVec;

class Object
{
int _x;
int _y; //组件基类的四个成员,逻辑坐标(x,y),运动速度v,加速度g,贴图ptr、掩码ym_ptr
int _width;
int _height;
BVec _v;
BVec _g;
IMAGE _ptr;
IMAGE ym_ptr;
public:
Object(LPCTSTR path, LPCTSTR ympath, int width, int height, int posx= 0, int posy= 0);//构造函数
Object(LPCTSTR path, int width, int height, int posx = 0, int posy = 0);
Object(LPCTSTR path);
Object():_x(0),_y(0),_width(0),_height(0),_v(make_pair(0,0)),_g(make_pair(0,0)){}
int x()const{ return _x; };
int y()const { return _y; };
BVec v()const { return _v; }; //数据成员操作接口
BVec g()const { return _g; }
int width()const { return _width; };
int height()const { return _height; };
void setx(int newx) { _x = newx; };
void sety(int newy) { _y = newy; };
void setv(int x, int y) { _v.first = x; _v.second = y; };
void setvx(int x) { _v.first = x; };
void setvy(int y) { _v.second = y; }
void setg(BVec newg) { _g = newg; };
void setptr(IMAGE newptr) { _ptr = newptr; }
void setptr_ym(IMAGE newptr_ym) { ym_ptr = newptr_ym; }
void setwidth(int newwidth) { _width = newwidth; }
void setheight(int newheight) { _height = newheight; }
void move();//此方法更新组件位置。
void show_n()const; //显示组件,不显示掩码
void show()const; //显示组件和掩码
~Object() { };

};
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
#include "Object.h"
Object::Object(LPCTSTR path,LPCTSTR ympath, int width, int height, int posx , int posy )
{
loadimage(&_ptr, _T("PNG"), path, width, height);
loadimage(&ym_ptr,_T("PNG"), ympath, width, height);
_x = posx;
_y = posy;
_width = width;
_height = height;
_v.first = 0;
_v.second = 0;
_g.first = 0;
_g.second = 0;
}
Object::Object(LPCTSTR path, int width, int height, int posx , int posy )
{
loadimage(&_ptr, _T("PNG"),path, width, height);
_x = posx;
_y = posy;
_width = width;
_height = height;
_v.first = 0;
_v.second = 0;
_g.first = 0;
_g.second = 0;
}
Object::Object(LPCTSTR path)
{
loadimage(&_ptr, path);
_x = 0;
_y = 0;
_width = _ptr.getwidth();
_height = _ptr.getheight();
_v.first = 0;
_v.second = 0;
_g.first = 0;
_g.second = 0;
}
void Object::move()
{
_x += _v.first;
_y += _v.second;
_v.first += _g.first;
_v.second += _g.second;
}
void Object::show_n()const
{
putimage(_x, _y, &_ptr);
}
void Object::show()const
{
putimage(_x, _y, &ym_ptr, SRCAND);
putimage(_x, _y, &_ptr, SRCPAINT);
}

角色类及其定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once
#include "Object.h"
#include<vector>
class bird : //角色类继承组件类(游戏角色只有小鸟,故名为小鸟类)
public Object //添加动作序列、死亡贴图
{
vector<IMAGE> seq;
vector<IMAGE> seq_ym;
IMAGE die;
IMAGE die_ym;
int index = 0;
public:
bird();
void add_seq(LPCTSTR path,LPCTSTR ympath);//添加动作序列
void add_die(LPCTSTR path, LPCTSTR ympath);//添加死亡贴图
void select_next();//切换动作
IMAGE& getdie() { return die; }
IMAGE& getdie_ym() { return die_ym; }
};
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
#include "bird.h"
#include "GamePragma.h"
#include<utility>
using std::make_pair;
void bird::add_seq(LPCTSTR path,LPCTSTR ympath)
{
IMAGE img,ym;
loadimage(&img,_T("PNG"), path);
loadimage(&ym, _T("PNG"),ympath);
seq.push_back(img);
seq_ym.push_back(ym);
setptr(seq.back());
setptr_ym(seq_ym.back());
setwidth(img.getwidth());
setheight(img.getheight());
}
void bird::select_next()
{
int len = seq.size();
if (index == len - 1)
{
index = 0;
}
else index++;
setptr(seq[index]);
setptr_ym(seq_ym[index]);
}
bird::bird()
{
setg(make_pair(0, gravity));
setx(100);
sety(200);
}
void bird::add_die(LPCTSTR path, LPCTSTR ympath)
{
loadimage(&die, _T("PNG"), path);
loadimage(&die_ym,_T("PNG"), ympath);
}

障碍物类及其实现

1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma once
#include "Object.h"
class Tube : //管道类继承组件类,添加方向、和小鸟是否通过。
public Object
{
int direction;
bool if_pass ;
public:
Tube(LPCTSTR path, LPCTSTR ympath, int width, int height, int posx, int posy,int d);
int getd()const { return direction; }
bool getp() { return if_pass; }
void setp(bool p) { if_pass = p; }
};
1
2
3
4
5
6
7
#include "Tube.h"
Tube::Tube(LPCTSTR path, LPCTSTR ympath, int width, int height, int posx, int posy, int d) :
Object(path, ympath, width, height, posx, posy)
{
direction = d;
if_pass = false;
}

游戏参数定义

1
2
3
4
5
6
7
8
9
10
#pragma once
#define WinWidth 800
#define WinHeight 600
#define gravity 0.5
#define BirdUpdate 5
#define TubeWidth 90
#define TubeSpeed -3.5
#define BirdJump -9
#define TubeGenerateSpeed 85
#define CloudSpeed -5

用到的函数

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
含游戏运作需要的函数
#pragma once
#include "Object.h"
#include "GamePragma.h"
#include<vector>
#include<cmath>
#include "bird.h"
#include"Tube.h"
#include<ctime>
#include<random>
#include<iostream>
using namespace std;
void PlayBg(Object& bg1,Object& bg2)//播放背景
{
bg1.show_n();
bg2.setx(bg1.x() + WinWidth);
bg2.show_n();
bg1.move();
bg2.move();
if (bg1.x() == -WinWidth)bg1.setx(0);
}
void PlayCloud(vector<Object>& cloud,int& count,long double runtime)//播放云
{
if (!count)
{
cloud.push_back(Object(_T("cloud"), _T("cloud_ym"), 0, 0, 800, rand()%400));
cloud.back().setvx(CloudSpeed);
}
for (auto& c : cloud)
{
c.show();
c.move();
}
if (count == 130)count = 0;
else count++;
if (cloud.front().x() <= -200)
{
cloud.erase(cloud.begin());
}
}
void PlayBird(bird& b,int& flyfreq,bool start)//播放鸟
{
b.show();
if(start)
b.move();


if (flyfreq == 8)
{
b.select_next();
flyfreq = 0;
}
else flyfreq++;

}
int TubeGenerator()//生成障碍物高度。所用的分布与正态分布图像关于x轴对称
{
mt19937 rng;
rng.seed(std::random_device()());
std::uniform_int_distribution<int> dist(30, 390);
int h = dist(rng);
if (h >= 210)h -= 180;
else h += 180;
return h;
}
void PlayTube(vector<Tube>& tube, int& count)//播放障碍物
{
if (!count)
{
int h = TubeGenerator();
tube.push_back(Tube(_T("tube"), _T("tube_ym"), TubeWidth, h, 800, 0,0));
tube.back().setvx(TubeSpeed);
tube.push_back(Tube(_T("rtube"), _T("rtube_ym"), TubeWidth, 400-h, 800, 200+h, 1));
tube.back().setvx(TubeSpeed);

}
if (count == TubeGenerateSpeed)count = 0;
else count++;
for (auto& t : tube)
{
t.show();
t.move();
}

if (!tube.empty()&&tube.front().x() <= -100)
{
tube.erase(tube.begin());
tube.erase(tube.begin());
}
}
int CheckColid(const vector<Tube>& tube,const bird& b)//碰撞检测
{
int bx1 = b.x();
int by1 = b.y();
int bx2 = bx1 + b.width();
int by2 = by1 + b.height();
for (int i =0; i < tube.size(); i++)
{
int d = tube[i].getd();
int tx1 = tube[i].x();
int ty1 = tube[i].y();
int tx2 = tx1 + tube[i].width();
int ty2 = ty1 + tube[i].height();
bool s1 = (d == 0 && tx1 <= bx1 && bx1 <= tx2 && (by1+20) <= ty2);
bool s2= (d == 0 && tx1 <= bx2-5 && bx2-5 <= tx2 && by1 <= ty2);
bool s3= (d == 1 && tx1 <= bx1+20 && bx1+20 <= tx2 && by2-5 >= ty1);
bool s4= (d == 1 && tx1 <= bx2-20 && bx2-20 <= tx2 && by2-5 >= ty1);
if (s1 || s2 || s3 || s4)return true;
}
return false;
}
void PlayBirdDie(bird& b)//播放鸟死亡动画
{
b.setvy(5);
b.setptr(b.getdie());
b.setptr_ym(b.getdie_ym());
b.show();
b.move();
}
void add_score(vector<Tube>&tube, long int& score)//计分器
{
for (auto it = tube.begin(); it != tube.end(); it++)
{
if ((*it).getp() == false)
{
if ((*it).x() <= 20)
{
(*it).setp(true);
score++;
it++;
(*it).setp(true);
break;
}
}
}
}
int GetPauseMenuAct(ExMessage& p)//获取玩家在用户界面的操作
{
if (329 <= p.x && p.x <= 468 && 147 <= p.y && p.y <= 186)return 1;
if (329 <= p.x && p.x <= 468 && 260 <= p.y && p.y <= 300)return 2;
if (329 <= p.x && p.x <= 468 && 375 <= p.y && p.y <= 413)return 3;
return 0;
}
int GetOverMenuAct(ExMessage& p)//获取玩家在结束界面的操作
{
if (330 <= p.x && p.x <= 470 && 320 <= p.y && p.y <= 360)return 1;
if (330 <= p.x && p.x <= 470 && 395 <= p.y && p.y <= 435)return 2;
return 0;
}

主函数

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#include<graphics.h>
#include<conio.h>
#include<windows.h>
#include "Object.h"
#include "function.h"
#include<vector>
#include<ctime>
#include<windows.h>
#include"Tube.h"
#include<string>
#include<fstream>
#include<Mmsystem.h>
#include<dsound.h>
#include<stdio.h>
#pragma comment(lib,"WINMM.LIB")
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)
using namespace std;
int main()
{
initgraph(800, 600);
BEGIN:
Object Background1(_T("Background"), 800, 600); //加载贴图资源及相关变量
Object Background2(_T("Background"), 800, 600);

Background1.setv(-1, 0);
Background2.setv(-1, 0);

Object pausemenu(_T("pausemenu"), _T("pausemenu_ym"),400,450,200,50);
Object overmenu(_T("overmenu"), _T("overmenu_ym"), 400, 450, 200,-500);
Object startflag(_T("startflag"), _T("startflag_ym"), 0, 0, 200, 200);
bird b;
b.add_seq(_T("bird1"), _T("bird1_ym"));
b.add_seq(_T("bird2"), _T("bird2_ym"));
b.add_seq(_T("bird3"), _T("bird3_ym"));
b.add_seq(_T("bird4"), _T("bird4_ym"));
b.add_die(_T("bird_die"), _T("bird_die_ym"));
vector<Tube> tube;
vector<Object> cloud;
int cloudcount = 0;
int tubecount = 0;
int flyfreq = 0;
int birdcount = 0;

time_t begin, now;
double runtime;
begin = clock();
bool start = false;

long int score = 0;
settextstyle(50, 0, _T("JandaManateesolid"));

setbkmode(TRANSPARENT);
BeginBatchDraw();
bool firstflag = true;
bool LASTKEYDOWN = false;

GAMING:
while (true)//主循环
{
if (_kbhit()) //检测是否按下ESC键暂停
{
int a = _getch();
if (a == 27)
{

pausemenu.show();
FlushBatchDraw();
ExMessage m;
while (1)
{
peekmessage(&m);
if (KEY_DOWN(VK_LBUTTON)) //检测选择
{
int selection = GetPauseMenuAct(m);
switch (selection)
{
case 0:break;
case 1:
PlaySound(TEXT("click"), NULL, SND_RESOURCE | SND_ASYNC );//播放点击音效
Sleep(100);
goto GAMING; //选择Resume,回到主循环

case 2:
PlaySound(TEXT("click"), NULL, SND_RESOURCE | SND_ASYNC );
goto BEGIN; //选择Restart,跳转到开始
case 3:
PlaySound(TEXT("click"), NULL, SND_RESOURCE | SND_ASYNC );
Sleep(100); //选择Exit退出游戏
exit(1);
}

}

}

}
}
now = clock();
runtime = (now - begin) / CLOCKS_PER_SEC; //记录游戏运行时间
PlayBg(Background1, Background2);
PlayCloud(cloud, cloudcount, runtime); //使各贴图运动
PlayBird(b, flyfreq,start);

if (start)
{
PlayTube(tube, tubecount); //游戏进行过程加载障碍物和计分器
add_score(tube, score);
}
TCHAR scorestr[10];
_stprintf_s(scorestr, _T("%d"), score);

outtextxy(380- 13 * (to_string(score).length() - 1), 20, scorestr); //打印分数

if (KEY_DOWN(VK_LBUTTON)) //检测是否单击鼠标左键并松开。
{
if (!LASTKEYDOWN)
{
b.setvy(BirdJump); //重设速度使小鸟跳跃
start = true;
LASTKEYDOWN = true;
PlaySound(TEXT("plop"), NULL, SND_RESOURCE | SND_ASYNC );//播放音效
}

}
else
{
LASTKEYDOWN = false;
}

FlushBatchDraw();//刷新帧
if (CheckColid(tube, b) || b.y() >= 620||b.y()<=-50) //死亡检测
{

PlaySound(TEXT("die"), NULL, SND_RESOURCE | SND_ASYNC);//播放结束音乐及动画
b.setvy(-5);
b.setptr(b.getdie());
b.setptr_ym(b.getdie_ym());
ifstream f("data.dat", ios::binary | ios::in);
long int best;
if (!f.is_open()) //更新最高分
{
f.close();
ofstream fo("data.dat", ios::binary);
fo.write((char*)&score, sizeof(long int));
best = score;
fo.close();
}
else
{
f.read((char*)&best, sizeof(long int));
if (score > best)
{
best = score;
f.close();
ofstream fo("data.dat", ios::binary);
fo.write((char*)&score,sizeof(score));
fo.close();
}
f.close();
}
while (true) //死亡动画次循环
{
PlayBg(Background1, Background2);
for (auto& c : cloud)
{
c.show();
}
for (auto& t : tube)
{
t.show();
}
outtextxy(370 , 20, scorestr);
b.show();
b.move();
FlushBatchDraw();
Sleep(1);
if (b.y() >= 620)
{
ExMessage m;
overmenu.setvy(10);
TCHAR scorestr[10];
_stprintf_s(scorestr, _T("%d"), score);

TCHAR beststr[10];
_stprintf_s(beststr, _T("%d"), best);
while (true) //结束菜单播放次循环
{
PlayBg(Background1, Background2);
for (auto& c : cloud)
{
c.show();
}
for (auto& t : tube)
{
t.show();
}
overmenu.show();
outtextxy(395-13*(to_string(score).length()-1), overmenu.y()+110, scorestr);
outtextxy(395 - 13 * (to_string(best).length() - 1), overmenu.y() + 200, beststr);
if (overmenu.y() < 50)
{
overmenu.move();
}
else
{

while (true)//等候操作次循环
{
peekmessage(&m);
if (KEY_DOWN(VK_LBUTTON))
{
int selection = GetOverMenuAct(m);
switch (selection)
{
case 1:
PlaySound(TEXT("click"), NULL, SND_RESOURCE | SND_ASYNC );
goto BEGIN;
case 2:
PlaySound(TEXT("click"), NULL, SND_RESOURCE | SND_ASYNC );
Sleep(100);
exit(1);
default: break;
}
}

}
closegraph();
}
FlushBatchDraw();
Sleep(1);
}

}
}

}
Sleep(1);
}

EndBatchDraw();

closegraph();
return 0;
}