先上效果图。
游戏的实现主要基于EasyX图形库,有暂停界面、结束界面、游戏音效、游戏存档,并最终集成为一个exe文件。可谓麻雀虽小,五脏俱全?。这里先放出源代码和下载链接,以后有时间会以此为例写一个C++小游戏开发教程系列。
链接:https://pan.baidu.com/s/1sBPD-LTGCTa7bfkI1RloXg? 提取码:8848
组件类及其定义:
#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() { };
};
#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);
}
?角色类及其定义
#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; }
};
#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);
}
障碍物类及其实现
#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; }
};
#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;
}
?游戏参数定义
#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
用到的函数
含游戏运作需要的函数
#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;
}
主函数
#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;
}
|