在这篇博客中,将简单实现并深入解析一个C++ 飞机大战游戏的代码,该小游戏界面由EasyX图形库实现,相关内容可以参考EasyX图形库基础使用教程(快速上手),小游戏运行演示效果见飞机大战效果演示,源码和相关资源文件课件已上传gitee:PLANE · TTKun/Project - 码云
这个飞机大战游戏主要由多个类和函数构成,整体功能包括游戏的初始化、玩家操作、敌机与子弹的生成和管理、碰撞检测以及游戏的开始和结束界面展示等。
BK
类 - 背景类IMAGE
类型的引用,同时初始化背景图像的y
坐标为-WIN_HEIGHT
,这是为了让背景从屏幕上方开始滚动。show
函数
y
坐标是否为0,如果是则重置为-WIN_HEIGHT
,然后每次将y
坐标增加2,最后使用putimage
函数在新的坐标位置绘制背景图像。代码:
class BK //背景(方便实现背景滚动效果)
{
public:
BK(IMAGE& img)
:img(img),
y(-WIN_HEIGHT)
{
}
void show() {
if (!y) y = -WIN_HEIGHT;
y += 2;
putimage(0, y, &img);
}
private:
IMAGE& img;
int y;
};
hero_plane
类 - 玩家飞机类IMAGE
类型的指针,用于不同状态下的玩家飞机图像,同时初始化玩家飞机的初始位置、血量等属性。例如,通过计算将飞机初始位置设置在屏幕下方中央。MouseControl
函数
y
坐标小于等于450则限制为450,然后根据鼠标的x
坐标重新计算并更新飞机的位置矩形。show
函数
GetRect
函数
代码:
class hero_plane //玩家飞机
{
public:
hero_plane(IMAGE* hero_planey, IMAGE* hero_planeb, IMAGE* hp_down1y, IMAGE* hp_down1b, IMAGE* hp_down2y, IMAGE* hp_down2b)
:point(0),hpy(hero_planey), hpb(hero_planeb), hpd1y(hp_down1y), hpd1b(hp_down1b), hpd2y(hp_down2y), hpd2b(hp_down2b)
, HP(HERO_HP)
{
rect.left = WIN_WIDTH / 2 - (*hpy).getwidth() / 2;
rect.top = WIN_HEIGHT - (*hpy).getheight();
rect.right = rect.left + (*hpy).getwidth();
rect.bottom = WIN_HEIGHT;
}
void MouseControl() {
ExMessage mouse_mess;
if (peekmessage(&mouse_mess, EM_MOUSE))
{
if (mouse_mess.y <= 450) {
mouse_mess.y = 450;
}
rect.left = mouse_mess.x - (*hpy).getwidth() / 2;
rect.top = mouse_mess.y - (*hpy).getheight() / 2;
rect.right = rect.right = rect.left + (*hpy).getwidth();
rect.bottom = rect.top + (*hpy).getheight();
}
}
void show() {
//putimage(185, 550, &imgy, SRCAND);
//putimage(185, 550, &imgb, SRCPAINT);
if (HP >= 5) {
putimage(rect.left, rect.top, hpy, SRCAND);
putimage(rect.left, rect.top, hpb, SRCPAINT);
}
else if (HP >= 3 && HP <= 4) {
putimage(rect.left, rect.top, hpd1y, SRCAND);
putimage(rect.left, rect.top, hpd1b, SRCPAINT);
}
else if (HP >= 1 && HP <= 2) {
putimage(rect.left, rect.top, hpd2y, SRCAND);
putimage(rect.left, rect.top, hpd2b, SRCPAINT);
}
}
RECT& GetRect() { return rect; }
long long point;
int HP;
private:
IMAGE* hpy;
IMAGE* hpb;
IMAGE* hpd1y;
IMAGE* hpd1b;
IMAGE* hpd2y;
IMAGE* hpd2b;
RECT rect;
//int HP;
};
plane_bullet
类 - 玩家飞机发射的子弹类IMAGE
类型的指针和一个hero_plane
类型的指针。根据玩家飞机的位置初始化子弹的位置矩形,确保子弹从飞机中间位置发射。show
函数
rect.top <= 0
)则返回false
,表示子弹已出界。每次将子弹的top
和bottom
坐标减少3(实现向上移动),移动距离move
增加3,然后使用putimage
函数在新位置绘制子弹。getleft
函数
GetRect
函数
代码:
class plane_bullet //玩家飞机发射的子弹
{
public:
plane_bullet(IMAGE* img, hero_plane* hp)
: imgPtr(img), move(0)
{
rect.left = hp->GetRect().left + (hp->GetRect().right - hp->GetRect().left - imgPtr->getwidth()) / 2;
rect.right = rect.left + imgPtr->getwidth();
rect.top = hp->GetRect().top;
rect.bottom = rect.top + imgPtr->getheight();
}
bool show() {
if (rect.top <= 0) return false;
rect.top -= 3;
rect.bottom -= 3;
move += 3;
putimage(rect.left, rect.top, imgPtr);
return true;
}
LONG getleft() {
return rect.left;
}
RECT& GetRect() { return rect; }
int move;
private:
IMAGE* imgPtr;
RECT rect;
};
enemy_plane
类 - 敌方飞机类IMAGE
类型的指针,用于不同状态下的敌机图像,以及一个x
坐标值。初始化敌机的位置、血量等属性,将敌机的初始位置设置在屏幕上方的指定x
坐标处。show
函数
top
和bottom
坐标增加1(实现向下移动),如果敌机的top
坐标超出屏幕底部则返回false
。CreateBullet
函数(代码中未实现具体函数体)
GetRect
函数
代码:
class enemy_plane //敌方飞机
{
public:
enemy_plane(IMAGE* enemy1y, IMAGE* enemy1b, IMAGE* enemy1_down1y, IMAGE* enemy1_down1b, IMAGE* enemy1_down2y, IMAGE* enemy1_down2b, int x);
bool show();
void CreateBullet(IMAGE* enemy_bullet);
RECT& GetRect();
int enemy1_hp;
int width() {
return imgenemy1b->getwidth();
}
vector<enemy_bullet> enemy_bul_vec;
private:
IMAGE* imgenemy1y;
IMAGE* imgenemy1b;
IMAGE* imgenemy1_down1y;
IMAGE* imgenemy1_down1b;
IMAGE* imgenemy1_down2y;
IMAGE* imgenemy1_down2b;
IMAGE* imgenemy_bullet;
RECT rect;
};
// 实现 enemy_plane 的成员函数,在类外声明
enemy_plane::enemy_plane(IMAGE* enemy1y, IMAGE* enemy1b, IMAGE* enemy1_down1y, IMAGE* enemy1_down1b, IMAGE* enemy1_down2y, IMAGE* enemy1_down2b, int x)
//初始化
: imgenemy1y(enemy1y), imgenemy1b(enemy1b), imgenemy1_down1y(enemy1_down1y), imgenemy1_down1b(enemy1_down1b),
imgenemy1_down2y(enemy1_down2y), imgenemy1_down2b(enemy1_down2b), enemy1_hp(ENEMY_HP)
{
rect.left = x;
rect.right = x + imgenemy1b->getwidth();
rect.top = -imgenemy1b->getheight();
rect.bottom = 0;
}
bool enemy_plane::show() //敌方飞机的移动显示
{
if (rect.top >= WIN_HEIGHT) return false;
rect.top += 1;
rect.bottom += 1;
if (enemy1_hp == 3) {
putimage(rect.left, rect.top, imgenemy1y, SRCAND);
putimage(rect.left, rect.top, imgenemy1b, SRCPAINT);
}
else if (enemy1_hp == 2) {
putimage(rect.left, rect.top, imgenemy1_down1y, SRCAND);
putimage(rect.left, rect.top, imgenemy1_down1b, SRCPAINT);
}
else if (enemy1_hp == 1) {
putimage(rect.left, rect.top, imgenemy1_down2y, SRCAND);
putimage(rect.left, rect.top, imgenemy1_down2b, SRCPAINT);
}
return true;
}
RECT& enemy_plane::GetRect() //获取敌方飞机贴图相关信息
{
return rect;
}
enemy_bullet
类 - 敌方飞机发射的子弹类IMAGE
类型的指针和一个enemy_plane
类型的指针。根据敌机的宽度和位置初始化子弹的位置矩形,确保子弹从敌机中间位置发射。show
函数
bullet_rect.top <= 0
)则返回false
,表示子弹已出界。每次将子弹的top
和bottom
坐标增加2(实现向下移动),移动距离move
增加2,然后使用putimage
函数在新位置绘制子弹。GetRect
函数
class enemy_bullet //敌方飞机发射的子弹
{
public:
enemy_bullet(IMAGE* img, enemy_plane* ep);
bool show();
RECT& GetRect();
int move;
private:
IMAGE* bullet_ptr;
RECT bullet_rect;
enemy_plane* plane_ref;
};
// 实现 enemy_bullet 的成员函数,类外声明
enemy_bullet::enemy_bullet(IMAGE* img, enemy_plane* ep)//初始化
: bullet_ptr(img), move(0), plane_ref(ep)
{
bullet_rect.left = (ep->width() - img->getwidth()) / 2 + ep->GetRect().left;
bullet_rect.right = bullet_rect.left + img->getwidth();
bullet_rect.top = ep->GetRect().bottom;
bullet_rect.bottom = bullet_rect.top + img->getheight();
}
bool enemy_bullet::show() //敌方飞机子弹的移动显示
{
if (bullet_rect.top <= 0) return false;
bullet_rect.top += 2;
bullet_rect.bottom += 2;
move += 2;
putimage(bullet_rect.left, bullet_rect.top, bullet_ptr);
return true;
}
RECT& enemy_bullet::GetRect() //获取敌方飞机发射的子弹的贴图相关信息
{
return bullet_rect;
}
transparentimage
透明背景贴图函数
IMAGE
对象,获取其图像缓冲区的指针。然后遍历图像的每个像素,将亮度低于0.03的像素设置为白色,其他非白色像素设置为黑色,最后通过SRCAND
和SRCPAINT
模式将处理后的图像与原始图像合成,从而实现透明背景的效果。代码:
void transparentimage(int x, int y, IMAGE img) //便于直接使用透明背景贴图的函数,可用于功能拓展
{
IMAGE img1;
DWORD* d1;
img1 = img;
d1 = GetImageBuffer(&img1);
float h, s, l;
for (int i = 0; i < img1.getheight() * img1.getwidth(); i++) {
RGBtoHSL(BGR(d1[i]), &h, &s, &l);
if (l < 0.03) {
d1[i] = BGR(WHITE);
}
if (d1[i] != BGR(WHITE)) {
d1[i] = 0;
}
}
putimage(x, y, &img1, SRCAND);
putimage(x, y, &img, SRCPAINT);
}
is_click
判定鼠标点击函数函数
(x,y)
是否在指定的矩形区域r
内,通过比较坐标与矩形的边界坐标来实现。代码:
bool is_click(int x, int y, RECT& r) //判断是否点击指定区域
{
return (r.left <= x && r.right >= x && r.top <= y && r.bottom >= y);
}
RectCrashRect
判定碰撞函数
r
,这个矩形的坐标是通过将r1
的坐标根据r2
的大小进行调整得到的。然后通过比较新矩形与r2
的坐标关系来判断是否碰撞,这种计算方式是一种比较特殊的碰撞检测算法,需要仔细理解坐标的计算逻辑。代码:
bool RectCrashRect(RECT& r1, RECT& r2) //判断两个贴图的碰撞
{
RECT r;
r.left = r1.left - (r2.right - r2.left);
r.right = r1.right;
r.top = r1.top - (r2.bottom - r2.top);
r.bottom = r1.bottom;
return (r.left < r2.left&& r2.left <= r.right && r.top <= r2.top && r2.top <= r.bottom);
}
Welcome
欢迎界面函数
代码:
void Welcome() //初始化界面
{
LPCTSTR title = _T("打飞机");
LPCTSTR title_play = _T("开始游戏");
LPCTSTR title_exit = _T("退出游戏");
IMAGE background;
RECT title_playr, title_exitr;
BeginBatchDraw();
loadimage(&background, "./images/bk.png");
putimage(0, 0, &background);
setbkcolor(WHITE);
//cleardevice();
settextstyle(40, 0, _T("黑体"));
settextcolor(BLACK);
outtextxy(WIN_WIDTH / 2 - textwidth(title) / 2,WIN_HEIGHT/5,title);
outtextxy(WIN_WIDTH / 2 - textwidth(title_play) / 2, WIN_HEIGHT *2 / 5, title_play);
title_playr.left = WIN_WIDTH / 2 - textwidth(title_play) / 2;
title_playr.right = title_playr.left + textwidth(title_play);
title_playr.top = WIN_HEIGHT *2/ 5;
title_playr.bottom = title_playr.top + textheight(title_play);
outtextxy(WIN_WIDTH / 2 - textwidth(title_exit) / 2, WIN_HEIGHT *3 / 5, title_exit);
title_exitr.left = WIN_WIDTH / 2 - textwidth(title_exit) / 2;
title_exitr.right = title_exitr.left + textwidth(title_exit);
title_exitr.top = WIN_HEIGHT *3/ 5;
title_exitr.bottom = title_exitr.top + textheight(title_exit);
EndBatchDraw();
//判断鼠标是否点击了开始或结束
while (true) {
ExMessage mouse_mess;
getmessage(&mouse_mess, EM_MOUSE);
if (mouse_mess.lbutton) //判断鼠标左键是否按下
{
if (is_click(mouse_mess.x, mouse_mess.y, title_playr)) {
return;
//如果在title_playr范围内点击,则返回,return后接着主函数继续运行游戏。
}
else if (is_click(mouse_mess.x, mouse_mess.y, title_exitr)) {
exit(0);
//如果如果在title_exitr范围内点击,直接退出程序
}
}
}
}
Over
函数
代码:
void Over(long long& kill)//游戏结束结算
{
printf_s("o");
TCHAR* str = new TCHAR[128];
_stprintf_s(str, 128, _T("击杀数:%llu"), kill);
settextcolor(RED);
outtextxy(WIN_WIDTH / 2 - textwidth(str) / 2, WIN_HEIGHT / 5, str);
// 键盘事件 (按Enter返回)
LPCTSTR info = _T("按Enter返回");
settextstyle(20, 0, _T("黑体"));
outtextxy(WIN_WIDTH - textwidth(info), WIN_HEIGHT - textheight(info), info);
while (true)
{
ExMessage mess;
getmessage(&mess, EM_KEY);
if (mess.vkcode == 0x0D)
{
return;
}
}
}
CreateEnemy
创建敌机函数
ep_vec
中添加一个新的敌机对象,敌机的初始位置是在屏幕宽度范围内随机生成的。代码:
void CreateEnemy(vector<enemy_plane>& ep_vec, IMAGE& enemy1y, IMAGE& enemy1b,IMAGE& enemy1_down1y, IMAGE& enemy1_down1b, IMAGE& enemy1_down2y, IMAGE& enemy1_down2b)
//创建敌机
{
ep_vec.push_back(enemy_plane(&enemy1y, &enemy1b, &enemy1_down1y, &enemy1_down1b, &enemy1_down2y, &enemy1_down2b, abs(rand()) % (WIN_WIDTH - enemy1y.getwidth())));
}
EnemyShow
敌机移动显示函数
ep_vec
,调用每个敌机的show
函数来显示敌机。代码:
void EnemyShow(vector<enemy_plane>&ep_vec) //敌机移动显示
{
for (int i = 0; i < ep_vec.size(); i++) {
ep_vec[i].show();
}
}
CreatePlaneBullet
创建玩家飞机弹药函数
plane_bul_vec
中添加一个新的子弹对象,子弹的初始位置和属性根据玩家飞机来确定。代码:
void CreatePlaneBullet(vector<plane_bullet>& plane_bul_vec,hero_plane& hp,IMAGE& img) //创建玩家飞机子弹
{
plane_bul_vec.push_back(plane_bullet(&img, &hp));
}
BulletShow
玩家飞机弹药移动显示函数
plane_bul_vec
,调用每个子弹的show
函数来显示子弹。代码:
void BulletShow(vector<plane_bullet>& bul_vec) //玩家飞机子弹移动显示
{
for (int i = 0; i < bul_vec.size();i++) {
bul_vec[i].show();
}
}
DeleteEnemy
删除出界敌机函数代码:
void DeleteEnemy(vector<enemy_plane>& ep_vec) //删除敌方出界的飞机,提高程序效率
{
if (ep_vec.empty()) return;
if (ep_vec[0].GetRect().top >= WIN_HEIGHT) {
ep_vec.erase(ep_vec.begin());
}
}
DeleteBullet
删除玩家飞机出界子弹函数bul_vec
,如果子弹的移动距离超过屏幕高度则删除这个子弹。代码:
void DeleteBullet(vector<plane_bullet>& bul_vec) //删除玩家飞机出界的子弹,提高程序效率
{
auto it = bul_vec.begin();
while (it != bul_vec.end()) {
if (it->move > WIN_HEIGHT) {
it = bul_vec.erase(it);
}
else {
++it;
}
}
}
DeleteEnemyBullet
删除敌机出界子弹函数DeleteBullet
函数,使用迭代器遍历敌方飞机子弹向量enemy_bul_vec
,如果子弹的移动距离超过屏幕高度则删除这个子弹。代码:
void DeleteEnemyBullet(vector<enemy_bullet>& enemy_bul_vec) //删除敌方飞机出界的子弹,提高程序效率
{
auto it = enemy_bul_vec.begin();
while (it != enemy_bul_vec.end()) {
if (it->move > WIN_HEIGHT) {
it = enemy_bul_vec.erase(it);
}
else {
++it;
}
}
}
DestroyEnemy
摧毁敌机函数代码:
void DestroyEnemy(vector<plane_bullet>& bul_vec, vector<enemy_plane>& ep_vec, hero_plane& hp) //成功击中敌机后的操作
{
// 处理敌机与我方飞机的碰撞
for (auto ep = ep_vec.begin(); ep != ep_vec.end();ep++) {
if (RectCrashRect((*ep).GetRect(), hp.GetRect())) {
ep = ep_vec.erase(ep);
hp.HP -= 3;
break;
}
}
// 处理子弹与敌机的碰撞
auto bul = bul_vec.begin();
while (bul != bul_vec.end()) {
auto ep = ep_vec.begin();
while (ep != ep_vec.end()) {
if (RectCrashRect((*bul).GetRect(), (*ep).GetRect())) {
bul = bul_vec.erase(bul);
(*ep).enemy1_hp--;
if ((*ep).enemy1_hp <= 0) {
ep = ep_vec.erase(ep);
hp.point++;
if (ep == ep_vec.end()) {
break;
}
//当删除一个敌机后,ep迭代器可能已经失效,后续的循环可能会出现未定义的行为。
//解决方法可以是在删除敌机后,正确地更新迭代器,确保循环的正确性
}
break;
}
else {
++ep;
}
}
if (bul != bul_vec.end()) {
++bul;
}
}
}
RunGame
游戏主体运行函数Over
函数进行游戏结束结算。代码:
void RunGame() //主体运行
{
setbkcolor(WHITE);
cleardevice();
IMAGE background;
IMAGE my_plane[2];
IMAGE hpd1[2];
IMAGE hpd2[2];
IMAGE enemy1[2];
IMAGE enemy1_down1[2];
IMAGE enemy1_down2[2];
IMAGE enemy2[2];
IMAGE bullet1;
IMAGE bullet2;
loadimage(&background, _T("./images/bk2.png"),WIN_WIDTH);
putimage(0, 0, &background);
loadimage(&my_plane[0], _T("./images/me1y.png"),80,88);
loadimage(&my_plane[1], _T("./images/me1b.png"), 80, 88);
loadimage(&hpd1[0], _T("./images/hpd1y.png"), 80, 88);
loadimage(&hpd1[1], _T("./images/hpd1b.png"), 80, 88);
loadimage(&hpd2[0], _T("./images/hpd2y.png"), 80, 88);
loadimage(&hpd2[1], _T("./images/hpd2b.png"),80,88);
loadimage(&bullet1, _T("./images/bullet1.png"));
loadimage(&bullet2, _T("./images/bullet2.png"));
loadimage(&enemy1[0], _T("./images/enemy1y.png"));
loadimage(&enemy1[1], _T("./images/enemy1b.png"));
loadimage(&enemy1_down1[0], _T("./images/enemy1_down1y.png"));
loadimage(&enemy1_down1[1], _T("./images/enemy1_down1b.png"));
loadimage(&enemy1_down2[0], _T("./images/enemy1_down2y.png"));
loadimage(&enemy1_down2[1], _T("./images/enemy1_down2b.png"));
BK bk = BK(background);
hero_plane hp(&my_plane[0], &my_plane[1],&hpd1[0],&hpd1[1],&hpd2[0],&hpd2[1]);
vector<enemy_plane>ep_vec;
vector<plane_bullet>plane_bul_vec; //我方飞机子弹
vector<enemy_bullet>enemy_bul_vec; //敌方飞机子弹
ep_vec.push_back(enemy_plane(&enemy1[0], &enemy1[1], &enemy1_down1[0], &enemy1_down1[1], &enemy1_down2[0], &enemy1_down2[1], 50));
int count = 0;
while (hp.HP>0) {
count++;
BeginBatchDraw();
flushmessage();
//开始准备
bk.show();
Sleep(8);
hp.MouseControl();
hp.show();
//生成敌方飞机
if (ep_vec.empty() || ep_vec[ep_vec.size() - 1].GetRect().top>=50) {
CreateEnemy(ep_vec, enemy1[0], enemy1[1], enemy1_down1[0], enemy1_down1[1], enemy1_down2[0], enemy1_down2[1]);
}
EnemyShow(ep_vec);
DeleteEnemy(ep_vec);
//生成我方飞机子弹
if (!plane_bul_vec.size()) {
CreatePlaneBullet(plane_bul_vec, hp, bullet1);
}
else if (plane_bul_vec[plane_bul_vec.size() - 1].move >= 36) {
CreatePlaneBullet(plane_bul_vec, hp, bullet1);
}
//生成敌机子弹
auto ep = ep_vec.begin();
while (ep != ep_vec.end()) {
if ((*ep).GetRect().bottom % 150 == 20) {
//enemy_plane& epn = *ep;
enemy_bullet eb(&bullet2, &(*ep));
enemy_bul_vec.push_back(eb);
}
ep++;
}
//敌机子弹数组移动和击中判定
auto eb = enemy_bul_vec.begin();
while (eb != enemy_bul_vec.end()) {
if (RectCrashRect((*eb).GetRect(), hp.GetRect())) {
hp.HP -= 1;
eb = enemy_bul_vec.erase(eb);
}
(*eb).show();
eb++;
}
BulletShow(plane_bul_vec);
DeleteBullet(plane_bul_vec);
DeleteEnemyBullet(enemy_bul_vec);
DestroyEnemy(plane_bul_vec, ep_vec, hp);
EndBatchDraw();
}
Over(hp.point);
}
Plane.h
#pragma once
#include
#include
#include
#include
#include
#include
#define WIN_WIDTH 460 //窗口宽度
#define WIN_HEIGHT 700 //窗口高度
#define HERO_HP 6 //英雄飞机血量
#define ENEMY_HP 3 //敌机血量
using namespace std;
class BK //背景(方便实现背景滚动效果)
{
public:
BK(IMAGE& img)
:img(img),
y(-WIN_HEIGHT)
{
}
void show() {
if (!y) y = -WIN_HEIGHT;
y += 2;
putimage(0, y, &img);
}
private:
IMAGE& img;
int y;
};
class hero_plane //玩家飞机
{
public:
hero_plane(IMAGE* hero_planey, IMAGE* hero_planeb, IMAGE* hp_down1y, IMAGE* hp_down1b, IMAGE* hp_down2y, IMAGE* hp_down2b)
:point(0),hpy(hero_planey), hpb(hero_planeb), hpd1y(hp_down1y), hpd1b(hp_down1b), hpd2y(hp_down2y), hpd2b(hp_down2b)
, HP(HERO_HP)
{
rect.left = WIN_WIDTH / 2 - (*hpy).getwidth() / 2;
rect.top = WIN_HEIGHT - (*hpy).getheight();
rect.right = rect.left + (*hpy).getwidth();
rect.bottom = WIN_HEIGHT;
}
void MouseControl() {
ExMessage mouse_mess;
if (peekmessage(&mouse_mess, EM_MOUSE))
{
if (mouse_mess.y <= 450) {
mouse_mess.y = 450;
}
rect.left = mouse_mess.x - (*hpy).getwidth() / 2;
rect.top = mouse_mess.y - (*hpy).getheight() / 2;
rect.right = rect.right = rect.left + (*hpy).getwidth();
rect.bottom = rect.top + (*hpy).getheight();
}
}
void show() {
//putimage(185, 550, &imgy, SRCAND);
//putimage(185, 550, &imgb, SRCPAINT);
if (HP >= 5) {
putimage(rect.left, rect.top, hpy, SRCAND);
putimage(rect.left, rect.top, hpb, SRCPAINT);
}
else if (HP >= 3 && HP <= 4) {
putimage(rect.left, rect.top, hpd1y, SRCAND);
putimage(rect.left, rect.top, hpd1b, SRCPAINT);
}
else if (HP >= 1 && HP <= 2) {
putimage(rect.left, rect.top, hpd2y, SRCAND);
putimage(rect.left, rect.top, hpd2b, SRCPAINT);
}
}
RECT& GetRect() { return rect; }
long long point;
int HP;
private:
IMAGE* hpy;
IMAGE* hpb;
IMAGE* hpd1y;
IMAGE* hpd1b;
IMAGE* hpd2y;
IMAGE* hpd2b;
RECT rect;
//int HP;
};
class plane_bullet //玩家飞机发射的子弹
{
public:
plane_bullet(IMAGE* img, hero_plane* hp)
: imgPtr(img), move(0)
{
rect.left = hp->GetRect().left + (hp->GetRect().right - hp->GetRect().left - imgPtr->getwidth()) / 2;
rect.right = rect.left + imgPtr->getwidth();
rect.top = hp->GetRect().top;
rect.bottom = rect.top + imgPtr->getheight();
}
bool show() {
if (rect.top <= 0) return false;
rect.top -= 3;
rect.bottom -= 3;
move += 3;
putimage(rect.left, rect.top, imgPtr);
return true;
}
LONG getleft() {
return rect.left;
}
RECT& GetRect() { return rect; }
int move;
private:
IMAGE* imgPtr;
RECT rect;
};
// 前向声明 enemy_plane 类
class enemy_plane;
class enemy_bullet //敌方飞机发射的子弹
{
public:
enemy_bullet(IMAGE* img, enemy_plane* ep);
bool show();
RECT& GetRect();
int move;
private:
IMAGE* bullet_ptr;
RECT bullet_rect;
enemy_plane* plane_ref;
};
class enemy_plane //敌方飞机
{
public:
enemy_plane(IMAGE* enemy1y, IMAGE* enemy1b, IMAGE* enemy1_down1y, IMAGE* enemy1_down1b, IMAGE* enemy1_down2y, IMAGE* enemy1_down2b, int x);
bool show();
void CreateBullet(IMAGE* enemy_bullet);
RECT& GetRect();
int enemy1_hp;
int width() {
return imgenemy1b->getwidth();
}
vector<enemy_bullet> enemy_bul_vec;
private:
IMAGE* imgenemy1y;
IMAGE* imgenemy1b;
IMAGE* imgenemy1_down1y;
IMAGE* imgenemy1_down1b;
IMAGE* imgenemy1_down2y;
IMAGE* imgenemy1_down2b;
IMAGE* imgenemy_bullet;
RECT rect;
};
void transparentimage(int x, int y, IMAGE img);//便于直接使用透明背景贴图的函数
bool is_click(int x, int y, RECT& r); //判断是否点击指定区域
bool RectCrashRect(RECT& r1, RECT& r2); //判断两个贴图的碰撞
void Welcome(); //初始化界面
void Over(long long& kill);//游戏结束结算
void CreateEnemy(vector<enemy_plane>& ep_vec, IMAGE& enemy1y, IMAGE& enemy1b, IMAGE& enemy1_down1y, IMAGE& enemy1_down1b, IMAGE& enemy1_down2y, IMAGE& enemy1_down2b);
void CreatePlaneBullet(vector<plane_bullet>& bul_vec, hero_plane& hp, IMAGE& img); //创建弹药
void EnemyShow(vector<enemy_plane>& ep_vec);//敌机运动
void BulletShow(vector<plane_bullet>& bul_vec);//弹药运动
void DeleteEnemy(vector<enemy_plane>& ep_vec);//删除出界后的敌机
void DeleteBullet(std::vector<plane_bullet>& bul_vec);//删除出界弹药
void DeleteEnemyBullet(vector<enemy_bullet>& enemy_bul_vec);//删除敌方出界子弹,使程序更高效
void DestroyEnemy(vector<plane_bullet>& bul_vec, vector<enemy_plane>& ep_vec, hero_plane& hp);//成功击中敌机后的操作
void RunGame(); //游戏主体运行
/*
技术要点/实现功能:
1、地图的绘制(移动)
2、主角战机的移动和绘制
3、敌机的生成、绘制和移动
4、主角战机发射的弹药的移动和绘制
5、弹药和敌机的碰撞判定
6、敌机/主角战机坠毁
7、开始、结束界面以及得分栏等
*/
Plane.cpp
#include "plane.h"
void transparentimage(int x, int y, IMAGE img) //便于直接使用透明背景贴图的函数,可用于功能拓展
{
IMAGE img1;
DWORD* d1;
img1 = img;
d1 = GetImageBuffer(&img1);
float h, s, l;
for (int i = 0; i < img1.getheight() * img1.getwidth(); i++) {
RGBtoHSL(BGR(d1[i]), &h, &s, &l);
if (l < 0.03) {
d1[i] = BGR(WHITE);
}
if (d1[i] != BGR(WHITE)) {
d1[i] = 0;
}
}
putimage(x, y, &img1, SRCAND);
putimage(x, y, &img, SRCPAINT);
}
bool is_click(int x, int y, RECT& r) //判断是否点击指定区域
{
return (r.left <= x && r.right >= x && r.top <= y && r.bottom >= y);
}
bool RectCrashRect(RECT& r1, RECT& r2) //判断两个贴图的碰撞
{
RECT r;
r.left = r1.left - (r2.right - r2.left);
r.right = r1.right;
r.top = r1.top - (r2.bottom - r2.top);
r.bottom = r1.bottom;
return (r.left < r2.left&& r2.left <= r.right && r.top <= r2.top && r2.top <= r.bottom);
}
void Welcome() //初始化界面
{
LPCTSTR title = _T("打飞机");
LPCTSTR title_play = _T("开始游戏");
LPCTSTR title_exit = _T("退出游戏");
IMAGE background;
RECT title_playr, title_exitr;
BeginBatchDraw();
loadimage(&background, "./images/bk.png");
putimage(0, 0, &background);
setbkcolor(WHITE);
//cleardevice();
settextstyle(40, 0, _T("黑体"));
settextcolor(BLACK);
outtextxy(WIN_WIDTH / 2 - textwidth(title) / 2,WIN_HEIGHT/5,title);
outtextxy(WIN_WIDTH / 2 - textwidth(title_play) / 2, WIN_HEIGHT *2 / 5, title_play);
title_playr.left = WIN_WIDTH / 2 - textwidth(title_play) / 2;
title_playr.right = title_playr.left + textwidth(title_play);
title_playr.top = WIN_HEIGHT *2/ 5;
title_playr.bottom = title_playr.top + textheight(title_play);
outtextxy(WIN_WIDTH / 2 - textwidth(title_exit) / 2, WIN_HEIGHT *3 / 5, title_exit);
title_exitr.left = WIN_WIDTH / 2 - textwidth(title_exit) / 2;
title_exitr.right = title_exitr.left + textwidth(title_exit);
title_exitr.top = WIN_HEIGHT *3/ 5;
title_exitr.bottom = title_exitr.top + textheight(title_exit);
EndBatchDraw();
//判断鼠标是否点击了开始或结束
while (true) {
ExMessage mouse_mess;
getmessage(&mouse_mess, EM_MOUSE);
if (mouse_mess.lbutton) //判断鼠标左键是否按下
{
if (is_click(mouse_mess.x, mouse_mess.y, title_playr)) {
return;
//如果在title_playr范围内点击,则返回,return后接着主函数继续运行游戏。
}
else if (is_click(mouse_mess.x, mouse_mess.y, title_exitr)) {
exit(0);
//如果如果在title_exitr范围内点击,直接退出程序
}
}
}
}
void Over(long long& kill)//游戏结束结算
{
printf_s("o");
TCHAR* str = new TCHAR[128];
_stprintf_s(str, 128, _T("击杀数:%llu"), kill);
settextcolor(RED);
outtextxy(WIN_WIDTH / 2 - textwidth(str) / 2, WIN_HEIGHT / 5, str);
// 键盘事件 (按Enter返回)
LPCTSTR info = _T("按Enter返回");
settextstyle(20, 0, _T("黑体"));
outtextxy(WIN_WIDTH - textwidth(info), WIN_HEIGHT - textheight(info), info);
while (true)
{
ExMessage mess;
getmessage(&mess, EM_KEY);
if (mess.vkcode == 0x0D)
{
return;
}
}
}
void CreateEnemy(vector<enemy_plane>& ep_vec, IMAGE& enemy1y, IMAGE& enemy1b,IMAGE& enemy1_down1y, IMAGE& enemy1_down1b, IMAGE& enemy1_down2y, IMAGE& enemy1_down2b)
//创建敌机
{
ep_vec.push_back(enemy_plane(&enemy1y, &enemy1b, &enemy1_down1y, &enemy1_down1b, &enemy1_down2y, &enemy1_down2b, abs(rand()) % (WIN_WIDTH - enemy1y.getwidth())));
}
void EnemyShow(vector<enemy_plane>&ep_vec) //敌机移动显示
{
for (int i = 0; i < ep_vec.size(); i++) {
ep_vec[i].show();
}
}
void CreatePlaneBullet(vector<plane_bullet>& plane_bul_vec,hero_plane& hp,IMAGE& img) //创建玩家飞机子弹
{
plane_bul_vec.push_back(plane_bullet(&img, &hp));
}
void BulletShow(vector<plane_bullet>& bul_vec) //玩家飞机子弹移动显示
{
for (int i = 0; i < bul_vec.size();i++) {
bul_vec[i].show();
}
}
void DeleteEnemy(vector<enemy_plane>& ep_vec) //删除敌方出界的飞机,提高程序效率
{
if (ep_vec.empty()) return;
if (ep_vec[0].GetRect().top >= WIN_HEIGHT) {
ep_vec.erase(ep_vec.begin());
}
}
void DeleteBullet(vector<plane_bullet>& bul_vec) //删除玩家飞机出界的子弹,提高程序效率
{
auto it = bul_vec.begin();
while (it != bul_vec.end()) {
if (it->move > WIN_HEIGHT) {
it = bul_vec.erase(it);
}
else {
++it;
}
}
}
void DeleteEnemyBullet(vector<enemy_bullet>& enemy_bul_vec) //删除敌方飞机出界的子弹,提高程序效率
{
auto it = enemy_bul_vec.begin();
while (it != enemy_bul_vec.end()) {
if (it->move > WIN_HEIGHT) {
it = enemy_bul_vec.erase(it);
}
else {
++it;
}
}
}
void DestroyEnemy(vector<plane_bullet>& bul_vec, vector<enemy_plane>& ep_vec, hero_plane& hp) //成功击中敌机后的操作
{
// 处理敌机与我方飞机的碰撞
for (auto ep = ep_vec.begin(); ep != ep_vec.end();ep++) {
if (RectCrashRect((*ep).GetRect(), hp.GetRect())) {
ep = ep_vec.erase(ep);
hp.HP -= 3;
break;
}
}
// 处理子弹与敌机的碰撞
auto bul = bul_vec.begin();
while (bul != bul_vec.end()) {
auto ep = ep_vec.begin();
while (ep != ep_vec.end()) {
if (RectCrashRect((*bul).GetRect(), (*ep).GetRect())) {
bul = bul_vec.erase(bul);
(*ep).enemy1_hp--;
if ((*ep).enemy1_hp <= 0) {
ep = ep_vec.erase(ep);
hp.point++;
if (ep == ep_vec.end()) {
break;
}
//当删除一个敌机后,ep迭代器可能已经失效,后续的循环可能会出现未定义的行为。
//解决方法可以是在删除敌机后,正确地更新迭代器,确保循环的正确性
}
break;
}
else {
++ep;
}
}
if (bul != bul_vec.end()) {
++bul;
}
}
}
// 实现 enemy_bullet 的成员函数
enemy_bullet::enemy_bullet(IMAGE* img, enemy_plane* ep)//初始化
: bullet_ptr(img), move(0), plane_ref(ep)
{
bullet_rect.left = (ep->width() - img->getwidth()) / 2 + ep->GetRect().left;
bullet_rect.right = bullet_rect.left + img->getwidth();
bullet_rect.top = ep->GetRect().bottom;
bullet_rect.bottom = bullet_rect.top + img->getheight();
}
bool enemy_bullet::show() //敌方飞机子弹的移动显示
{
if (bullet_rect.top <= 0) return false;
bullet_rect.top += 2;
bullet_rect.bottom += 2;
move += 2;
putimage(bullet_rect.left, bullet_rect.top, bullet_ptr);
return true;
}
RECT& enemy_bullet::GetRect() //获取敌方飞机发射的子弹的贴图相关信息
{
return bullet_rect;
}
// 实现 enemy_plane 的成员函数
enemy_plane::enemy_plane(IMAGE* enemy1y, IMAGE* enemy1b, IMAGE* enemy1_down1y, IMAGE* enemy1_down1b, IMAGE* enemy1_down2y, IMAGE* enemy1_down2b, int x)
//初始化
: imgenemy1y(enemy1y), imgenemy1b(enemy1b), imgenemy1_down1y(enemy1_down1y), imgenemy1_down1b(enemy1_down1b),
imgenemy1_down2y(enemy1_down2y), imgenemy1_down2b(enemy1_down2b), enemy1_hp(ENEMY_HP)
{
rect.left = x;
rect.right = x + imgenemy1b->getwidth();
rect.top = -imgenemy1b->getheight();
rect.bottom = 0;
}
bool enemy_plane::show() //敌方飞机的移动显示
{
if (rect.top >= WIN_HEIGHT) return false;
rect.top += 1;
rect.bottom += 1;
if (enemy1_hp == 3) {
putimage(rect.left, rect.top, imgenemy1y, SRCAND);
putimage(rect.left, rect.top, imgenemy1b, SRCPAINT);
}
else if (enemy1_hp == 2) {
putimage(rect.left, rect.top, imgenemy1_down1y, SRCAND);
putimage(rect.left, rect.top, imgenemy1_down1b, SRCPAINT);
}
else if (enemy1_hp == 1) {
putimage(rect.left, rect.top, imgenemy1_down2y, SRCAND);
putimage(rect.left, rect.top, imgenemy1_down2b, SRCPAINT);
}
return true;
}
RECT& enemy_plane::GetRect() //获取敌方飞机贴图相关信息
{
return rect;
}
void RunGame() //主体运行
{
setbkcolor(WHITE);
cleardevice();
IMAGE background;
IMAGE my_plane[2];
IMAGE hpd1[2];
IMAGE hpd2[2];
IMAGE enemy1[2];
IMAGE enemy1_down1[2];
IMAGE enemy1_down2[2];
IMAGE enemy2[2];
IMAGE bullet1;
IMAGE bullet2;
loadimage(&background, _T("./images/bk2.png"),WIN_WIDTH);
putimage(0, 0, &background);
loadimage(&my_plane[0], _T("./images/me1y.png"),80,88);
loadimage(&my_plane[1], _T("./images/me1b.png"), 80, 88);
loadimage(&hpd1[0], _T("./images/hpd1y.png"), 80, 88);
loadimage(&hpd1[1], _T("./images/hpd1b.png"), 80, 88);
loadimage(&hpd2[0], _T("./images/hpd2y.png"), 80, 88);
loadimage(&hpd2[1], _T("./images/hpd2b.png"),80,88);
loadimage(&bullet1, _T("./images/bullet1.png"));
loadimage(&bullet2, _T("./images/bullet2.png"));
loadimage(&enemy1[0], _T("./images/enemy1y.png"));
loadimage(&enemy1[1], _T("./images/enemy1b.png"));
loadimage(&enemy1_down1[0], _T("./images/enemy1_down1y.png"));
loadimage(&enemy1_down1[1], _T("./images/enemy1_down1b.png"));
loadimage(&enemy1_down2[0], _T("./images/enemy1_down2y.png"));
loadimage(&enemy1_down2[1], _T("./images/enemy1_down2b.png"));
BK bk = BK(background);
hero_plane hp(&my_plane[0], &my_plane[1],&hpd1[0],&hpd1[1],&hpd2[0],&hpd2[1]);
vector<enemy_plane>ep_vec;
vector<plane_bullet>plane_bul_vec; //我方飞机子弹
vector<enemy_bullet>enemy_bul_vec; //敌方飞机子弹
ep_vec.push_back(enemy_plane(&enemy1[0], &enemy1[1], &enemy1_down1[0], &enemy1_down1[1], &enemy1_down2[0], &enemy1_down2[1], 50));
int count = 0;
while (hp.HP>0) {
count++;
BeginBatchDraw();
flushmessage();
//开始准备
bk.show();
Sleep(8);
hp.MouseControl();
hp.show();
//生成敌方飞机
if (ep_vec.empty() || ep_vec[ep_vec.size() - 1].GetRect().top>=50) {
CreateEnemy(ep_vec, enemy1[0], enemy1[1], enemy1_down1[0], enemy1_down1[1], enemy1_down2[0], enemy1_down2[1]);
}
EnemyShow(ep_vec);
DeleteEnemy(ep_vec);
//生成我方飞机子弹
if (!plane_bul_vec.size()) {
CreatePlaneBullet(plane_bul_vec, hp, bullet1);
}
else if (plane_bul_vec[plane_bul_vec.size() - 1].move >= 36) {
CreatePlaneBullet(plane_bul_vec, hp, bullet1);
}
//生成敌机子弹
auto ep = ep_vec.begin();
while (ep != ep_vec.end()) {
if ((*ep).GetRect().bottom % 150 == 20) {
//enemy_plane& epn = *ep;
enemy_bullet eb(&bullet2, &(*ep));
enemy_bul_vec.push_back(eb);
}
ep++;
}
//敌机子弹数组移动和击中判定
auto eb = enemy_bul_vec.begin();
while (eb != enemy_bul_vec.end()) {
if (RectCrashRect((*eb).GetRect(), hp.GetRect())) {
hp.HP -= 1;
eb = enemy_bul_vec.erase(eb);
}
(*eb).show();
eb++;
}
BulletShow(plane_bul_vec);
DeleteBullet(plane_bul_vec);
DeleteEnemyBullet(enemy_bul_vec);
DestroyEnemy(plane_bul_vec, ep_vec, hp);
EndBatchDraw();
}
Over(hp.point);
}
main.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "Plane.h"
int main()
{
initgraph(WIN_WIDTH, WIN_HEIGHT);
bool game_status = true;
while (game_status) {
Welcome();
RunGame();
}
return 0;
}