概述
程序运行时,通过空格键进行调整小球的上下跳跃,而障碍物的高度、宽度和速度均是随机的,每通过一个障碍物分数加1。但碰撞后游戏会暂停,直到按下“r”后游戏重新开始,分数也会同步清零。
实现效果和涉及的知识
本例子我们将模拟简易版的flappy bird。
例子中我们会学习EasyX图形库的文本相关库函数、随机数的生成和函数等。
文件结构
这个例子需要如下几个模块:小球,障碍物,得分计算,碰撞判断和游戏重启。代码的文件结构也是根据模块划分进行设置的。
具体代码
main.cpp
// 初版flappy bird
#include <graphics.h> // 绘图函数库,非cpp STL,是TurboC扩展库(EasyX)
#include <conio.h> // console input/output的简写,非cpp STL,定义了通过控制台进行输入输出的函数,如getch()
#include <stdio.h> // standard input&output:标准输入输出头文件,如scanf, printf
#include <iostream> // C++ STL 输入输出库
#include "ball_xy.h" // 计算小球的位置
#include "rect_xy.h" // 计算障碍物的位置
#include "ifcoll.h" // 判断小球和障碍物是否碰撞
#include "score.h" // 计算得分
#include "data.h" // 全局变量
using namespace std;
int main()
{
float GraWid = 600, GraHei = 400, r = 10, g = 1;
bool ifKBhit = false;
ball_xy Ball_XY;
rect_xy Rect_XY;
ifcoll IfColl;
Ball_XY.set_ball_envi(GraWid, GraHei, r, g);
Rect_XY.set_rect_ab(GraWid, GraHei);
initscore();
initgraph(GraWid, GraHei);
while (1)
{
while (1)
{
if (_kbhit()) // 注意这个函数,非常特殊:键盘敲击:true or 键盘不敲击:null(不是false)
// 如果不加_kbhit(),console在每个while循环都会被_getch()卡住:等待使用者键盘输入。
{
char kbin = _getch(); // Get a character from the console
if (kbin == ' ')
{
ifKBhit = true;
}
}
cleardevice();
Ball_XY.cal_ball_xy(ifKBhit);
fillcircle(Ball_XY.get_ball_x(), Ball_XY.get_ball_y(), r);
Rect_XY.cal_rect_para();
fillrectangle(Rect_XY.get_rect_ax(), Rect_XY.get_rect_by(), Rect_XY.get_rect_cx(), Rect_XY.get_rect_dy());
scorefun(Rect_XY.get_rect_ax(), GraWid); // 计算得分
TCHAR s[20]; // 这里划重点:_UNICODE宏
swprintf_s(s, _T("%d"), score);
settextstyle(40, 0, _T("宋体")); // EasyX:The function is used to styling the current font
outtextxy(50, 30, s); // EasyX:The function is used to output the string at the specified location.
cout << score;
;
IfColl.set_ifcoll_para(Ball_XY.get_ball_x(), Ball_XY.get_ball_y(), r, Rect_XY.get_rect_ax(), Rect_XY.get_rect_by(), Rect_XY.get_rect_cx(), Rect_XY.get_rect_dy());
if (IfColl.cal_coll()) // 如果碰撞则退出当前循环
{
break;
}
Sleep(50);
ifKBhit = false;
}
while (1) // 按‘r’重新开始游戏
{
if (_kbhit()) // 注意这个函数,非常特殊:键盘敲击:true or 键盘不敲击:null(不是false)
{
char kbinRS = _getch();
if (kbinRS == 'r') // 按‘r’重新开始游戏
{
Ball_XY.set_ball_envi(GraWid, GraHei, r, g);
Rect_XY.set_rect_ab(GraWid, GraHei);
initscore(); // 重新开始游戏时,分数应置零
break;
}
}
Sleep(50);
}
Sleep(1000);
}
closegraph();
return 0;
}
ball_xy.h
#pragma once // 防止.h被重复包含
#ifndef BALL_XY_H_
#define BALL_XY_H_
class ball_xy
{
private:
float x, y, vx, vy;
float GraWid, GraHei, r, g;
public:
ball_xy();
~ball_xy();
void set_ball_envi(float GraWid_FP, float GraHei_FP, float r_FP, float g_FP);
void cal_ball_xy(bool ifKBhit);
float get_ball_x();
float get_ball_y();
};
#endif // !BALL_XY_H_
ball_xy.cpp
#include "ball_xy.h"
ball_xy::ball_xy()
{
x = 0;
y = 0;
vx = 0;
vy = 0;
GraWid = 0;
GraHei = 0;
r = 0;
g = 0;
}
ball_xy::~ball_xy()
{
}
void ball_xy::set_ball_envi(float GraWid_FP, float GraHei_FP, float r_FP, float g_FP)
{
GraWid = GraWid_FP;
GraHei = GraHei_FP;
x = GraWid / 4;
y = GraHei - r_FP;
vx = 0;
vy = 0;
r = r_FP;
g = g_FP;
}
void ball_xy::cal_ball_xy(bool ifKBhit)
{
if (ifKBhit)
{
vy = -15;
y = y + vy;
}
else if (y - r < 0)
{
vy = -vy;
y = r;
}
else if (y + r >= GraHei)
{
vy = 0;
y = GraHei - r;
}
else
{
vy = vy + g;
y = y + vy;
}
}
float ball_xy::get_ball_x()
{
return x;
}
float ball_xy::get_ball_y()
{
return y;
}
rect_xy.h
#pragma once
#ifndef RECT_H_
#define RECT_H_
class rect_xy
{
private:
float rect_x, rect_L, rect_H, rect_vx, grawid, grahei;
float ax, by, cx, dy;
public:
rect_xy();
~rect_xy();
void set_rect_ab(float GraWid, float GraHei);
void cal_rect_para();
float get_rect_ax();
float get_rect_by();
float get_rect_cx();
float get_rect_dy();
};
#endif // !RECT_H_
rect_xy.cpp
这里要说一下随机数的生成:
- void srand(unsigned seed);
srand被包含在stdlib.h内,用来指定种子seed的函数,若不显式调用则系统默认调用srand(1)来指定rand()的初始化种子。srand函数指定的种子会对应一个随机数系列,所以当你用srand指定一样的种子是,rand出来的随机数序列总是一样的。当然为了防止一样,一方面我们可用流逝的时间作为种子,即用time函数来获取系统时间,time函数会返回一个time_t的数据,表示1970年1月1日至今所经历的时间(以秒为单位),单位为妙,然后我们将这个time_t类型的数据转换为unsigned类型就可以srand成种子了,即srand((unsigned)time(0)。
- int rand(void);
rand()被包含在stdlib.h内,从srand(seed)中指定的seed为范围的开始,返回一个[seed,RAND_MAX)区间中的随机数值。总结来说,可以表示为:int num = rand() % n +a;
其中的a是起始值,n-1+a是终止值,n是整数的范围。
- static time_t time(time_t * const _Time)
包含在ctime内,time(0)指函数返回当前时间,如果发生错误返回0。time(1)指函数返回当前时间,如果发生错误返回1.
#include "rect_xy.h"
#include <stdlib.h>
#include <ctime>
rect_xy::rect_xy()
{
float rect_x = 0, rect_L = 15, rect_H = 0, rect_vx = -5, grawid = 0, grahei = 0;
float ax = 0, by = 0, cx = 0, dy = 0;
}
rect_xy::~rect_xy()
{
}
void rect_xy::set_rect_ab(float GraWid, float GraHei)
{
grawid = GraWid;
grahei = GraHei;
rect_x = GraWid;
rect_L = 15;
rect_H = 100;
rect_vx = -10;
}
void rect_xy::cal_rect_para()
{
if (rect_x > 0 - rect_L && rect_x <= grawid)
{
rect_x = rect_x + rect_vx;
ax = rect_x;
by = grahei - rect_H;
cx = rect_x + rect_L;
dy = grahei;
}
else
{
rect_x = grawid;
srand(time(0)); // int rand(void):返回当前时间,如果发生错误返回零。
rect_vx = (5 + rand() % 20) * -1;
rect_H = (20 + rand() % 250);
rect_L = (15 + rand() % 100);
ax = rect_x;
by = grahei - rect_H;
cx = rect_x + rect_L;
dy = grahei;
}
}
float rect_xy::get_rect_ax()
{
return ax;
}
float rect_xy::get_rect_by()
{
return by;
}
float rect_xy::get_rect_cx()
{
return cx;
}
float rect_xy::get_rect_dy()
{
return dy;
}
ifcoll.h
#pragma once
#ifndef IFCOLL_H_
#define IFCOLL_H_
class ifcoll
{
private:
float ball_x, ball_y, r;
float rect_ax, rect_by, rect_cx, rect_dy;
bool ball_rect_coll;
public:
ifcoll();
~ifcoll();
void set_ifcoll_para(float Ball_x, float Ball_y, float R, float Rect_ax, float Rect_by, float Rect_cx, float Rect_dy);
bool cal_coll();
};
#endif // !IFCOLL_H_
ifcoll.cpp
#include "ifcoll.h"
ifcoll::ifcoll()
{
float ball_x = 0, ball_y = 0, r = 0;
float rect_ax = 0, rect_by = 0, rect_cx = 0, rect_dy = 0;
bool ball_rect_coll = false;
}
ifcoll::~ifcoll()
{
}
void ifcoll::set_ifcoll_para(float Ball_x, float Ball_y, float R, float Rect_ax, float Rect_by, float Rect_cx, float Rect_dy)
{
ball_x = Ball_x;
ball_y = Ball_y;
r = R;
rect_ax = Rect_ax;
rect_by = Rect_by;
rect_cx = Rect_cx;
rect_dy = Rect_dy;
ball_rect_coll = false;
}
bool ifcoll::cal_coll()
{
if (ball_x + r >= rect_ax && ball_x - r <= rect_cx)
{
if (ball_y >= rect_by)
{
ball_rect_coll = true;
}
}
else if (ball_x >= rect_ax && ball_x <= rect_cx)
{
if (ball_y - r >= rect_by)
{
ball_rect_coll = true;
}
}
else
{
ball_rect_coll = false;
}
return ball_rect_coll;
}
score.h
#pragma once
#ifndef SCORE_H_
#define SCORE_H_
extern void initscore();
extern void scorefun(float rect_x, float grawid);
#endif // !SCORE_H_
score.cpp
#include "score.h"
#include "data.h"
void initscore()
{
score = 0;
}
void scorefun(float rect_x, float grawid)
{
if (rect_x >= grawid)
{
score = score + 1;
}
}
data.h
#pragma once
#ifndef DATA_H_
#define DATA_H_
extern int score;
#endif // !DATA_H_
data.cpp
// 全局变量
int score = -1;