C++ EGE 实现飞机大战小游戏图形界面
飞机大战因为没有了地图的限制,所以相比较坦克大战而言稍微简单一些。
而游戏的可玩性和复杂度一般应该是成正比的。
但是飞机大战并没有因为地图上的省略而变得没有可玩性,因为这个程序把像坦克大战那样需要地图的游戏的“空间”上的复杂,转换为了“时间”上的复杂。
该程序会根据不同时期的得分和进度,改变不同的策略。
技术环节:
编译环境:Windows VS2019
需求:
可控制飞机进行上下左右移动,所有飞机自动根据时间间隔发射子弹,己方子弹攻击到对方飞机,对敌人进行增添、我方道具使用、累加到一定分数出现最终boss及子弹攻击到对方飞机显示爆炸效果等。
当前游戏还增加了背景音乐和打击音效。
思路:
根据我方飞机和地方飞机各自的特性写出各自的类。
敌方飞机对象使用vector动态数组容器进行存储,所有敌方飞机移动实际上就是遍历一次敌机容器,让被一个飞机移动,同时便于向数组中尾插新的敌人,和删除生命值为空的敌人。
同样的思想可以用在子弹的发射上,只是子弹发射需要根据距上一次子弹发射后的时间来进行下一次子弹发射。
击中敌人的子弹和越过屏幕边界的子弹会被删除。
难点:
根据子弹发射时的时间减去子弹发射后一定时间的差,来进行无缝衔接的子弹发射。
将图片对象插入到数组中,将数组放入到游戏循环,根据击中飞机时为true的标记显示固定帧数的爆炸效果。
注意:
包含<graphics.h>图形库需要提前配置EGE图形库。
如要在其他graphics图形库下编译,可能需要修改部分代码。
图片素材来源于网络。
如有您需要图片素材,可以私信博主。
运行效果:
#include <graphics.h> //图形库
#include <ctime> //clock
#include <vector> //动态数组容器
using namespace std;
//设置图片对象宽高全局函数
//将设置图片对象宽高封装为一个函数
void setimage(int pwidth, int pheight, PIMAGE img_1)
{
//获取参数图片对象的宽高
int whidth = getwidth(img_1), height = getheight(img_1);
//创建一个临时图片对象,这个新的图像对象的宽高为要重新设置的图像的宽高
PIMAGE tempimg = newimage(pwidth, pheight);
putimage(tempimg, 0, 0, pwidth, pheight, img_1, 0, 0, whidth, height); //将原本img中的图像拉伸绘制到img_2中
getimage(img_1, tempimg, 0, 0, pwidth, pheight); //img再获取temp中的图像
delimage(tempimg); //使用完毕将释放掉
}
struct bulxy //子弹坐标和属性全局结构
{
int b_x; //横坐标
int b_y; //纵坐标
int prop; //子弹的属性
};
//飞机父类
class Plane
{
public:
int health = 0; //飞机血量
protected:
int width = 0, height = 0; //飞机的宽高
int m_x = 0, m_y = 0; //飞机横纵坐标
clock_t now_1 = clock(); //发射子弹开始时间
clock_t now_2 = 0; //发射子弹后时间
int timediff = 0; //发射子弹时间差 now_2 - 1
int sign = 0; //区分飞机变量
bulxy temp = { 0 }; //用于向数组中插入结构
vector<bulxy> bullvec; //子弹动态数组
public:
int getwidth() //获取飞机宽
{
return width;
}
int getheight() //获取飞机高
{
return height;
}
};
//敌人飞机类
class Planehose: public Plane
{
private:
PIMAGE hoseimg_1 = newimage(); //敌人小兵
PIMAGE bulletimg = newimage(); //子弹图片对象
int planeprop = 0; //飞机属性
int speed_x = 0, speed_y = 0; //子弹速度每帧x轴和y轴移动单位数
int attack = 0; //敌机攻速(攻击间隔)
//新建子弹函数,根据对象飞机属性新建固定的子弹
inline void pushlaunch() //向数组插入\新建子弹
{
//根据属性确定子弹发射的位置和方式
switch (planeprop)
{
case 1: //小兵
case 2: //大兵子弹
temp.b_x = m_x + 51, temp.b_y = m_y + 60; //确定新子弹的位置
temp.prop = 2; //子弹属性向左
bullvec.push_back(temp); //将子弹插入进数组中
temp.prop = 3; //子弹属性向右
bullvec.push_back(temp);
break;
case 3: //小BOSS子弹
temp.b_x = m_x + 110, temp.b_y = m_y + 180; //确定新子弹的位置
temp.prop = 2; //子弹属性向左
bullvec.push_back(temp);
temp.prop = 3; //子弹属性向右
bullvec.push_back(temp);
temp.prop = 1; //子弹属性向下
bullvec.push_back(temp);
break;
case 4: //最终BOSS子弹
//所有子弹位于y轴位于m_y + 160,子弹属性为1
temp.b_y = m_y + 160, temp.prop = 1;
//分别设置每发子弹的x轴,然后将这枚子弹插入进子弹数组中
temp.b_x = m_x - 20;
bullvec.push_back(temp);
temp.b_x = m_x + 76;
bullvec.push_back(temp);
temp.b_x = m_x + 172;
bullvec.push_back(temp);
temp.b_x = m_x + 268;
bullvec.push_back(temp);
temp.b_x = m_x + 364;
bullvec.push_back(temp);
temp.b_x = m_x + 460;
bullvec.push_back(temp);
}
}
//子弹发射速度
inline void attackspeed()
{
now_2 = clock(); //获取一次系统时间(精确到毫秒)
timediff = now_2 - now_1; //通过上一次发射子弹时间减去当前时间计算出已发射子弹的时间
switch (planeprop) //根据不同飞机,设定飞机发射子弹后多长时间再发射一枚子弹
{
case 1: //小兵
attack = 820; break;
case 2: //大兵
attack = 720; break;
case 3: //小BOSS
attack = 680; break;
case 4: //最终BOSS
attack = 300;
}
if (timediff >= attack) //发射子弹后固定时间后,再次发射子弹 攻击速度
{
pushlaunch(); //新建子弹
timediff = 0; //累计时间重置
now_1 = clock(); //重新获取子弹发射时时间
}
}
//子弹速度函数
inline void laumoveunit()
{
//根据飞机属性,确定飞机子弹的xy轴每帧移动单位
switch (planeprop)
{
case 1:
case 2: //小大兵
speed_x = 4, speed_y = 4; break;
case 3: //精英兵
speed_x = 6, speed_y = 18; break;
case 4: //最终BOSS
speed_x = 0, speed_y = 8;
}
}
//子弹移动方向函数
inline void laudire()
{
//所有子弹移动
//根据子弹的属性,确定不同子弹的运动方向
//通过对子弹的坐标进行+= 或 -=实现
int bullsizetemp = bullvec.size();
for (int i = 0; i < bullsizetemp; i++)
switch (bullvec[i].prop) //判断每一个子弹的属性,根据属性决定子弹移动方向
{
case 1: //子弹1运动垂直向下
bullvec[i].b_y += speed_y; //垂直向下子弹仅移动y轴
break;
case 2: //子弹2运动向左下
bullvec[i].b_y += speed_y, bullvec[i].b_x -= speed_x; //左下子弹y轴逐渐增加,x轴减小
break;
case 3: //右下
bullvec[i].b_y += speed_y, bullvec[i].b_x += speed_x; //右下子弹y轴逐渐增加,x轴增大
break;
}
}
//显示子弹函数
inline void laushow()
{
//遍历子弹数组,显示所有子弹
//这里使用自动数据类型变量遍历结构xy属性
for (auto bullvectempi : bullvec)
putimage_withalpha(NULL, bulletimg, bullvectempi.b_x, bullvectempi.b_y);
}
//删除越界子弹函数
inline void laudelete()
{
//删除敌人越界的子弹
for (vector<bulxy>::const_iterator it = bullvec.begin(); it != bullvec.end(); it++)
if (it->b_y >= 800 || it->b_x + 18 <= 0 || it->b_x >= 500)
{
bullvec.erase(it);
break;
}
}
//封装所有子弹相关函数
void planlaunch()
{
attackspeed(); //子弹射速函数
laumoveunit(); //子弹速度函数
laudire(); //子弹方向函数
laushow(); //显示子弹函数
laudelete(); //删除越界子弹函数
}
public:
int& setgetm_x() //获取和修改敌人飞机xy坐标值
{
return m_x;
}
int& setgetm_y()
{
return m_y;
}
int gethoseprop() //获取和敌人的prop属性*****
{
return planeprop;
}
int gethealth() //获取敌人血量值
{
return health;
}
vector<bulxy>& gethosebullvec() //获取敌人的子弹数组
{
return bullvec;
}
//根据参数,为敌人飞机对象初始化坐标、血量、图片信息
Planehose(int prop)
{
this->planeprop = prop; //将传进来的参数赋给类变量
//根据属性获取不同的子弹图片素材,设置飞机宽高
switch (prop)
{
case 1:
case 2: //大小兵
getimage(bulletimg, "飞机大战程序素材\\敌人子弹.png");
setimage(18, 18, bulletimg);
width = 120, height = 90; //飞机宽高
break;
case 3: //精英飞机
getimage(bulletimg, "飞机大战程序素材\\敌人子弹2.png");
setimage(18, 18, bulletimg);
width = 240, height = 180; //飞机宽高
break;
case 4: //最终BOSS
getimage(bulletimg, "飞机大战程序素材\\敌人导弹.png");
setimage(40, 81, bulletimg);//子弹图片大小
width = 480, height = 360; //飞机宽高
}
m_x = rand() % 380, m_y = -(rand() % 101 + 200); //随机横纵坐标
//飞机素材
switch (prop)
{
case 1:
getimage(hoseimg_1, "飞机大战程序素材\\敌人飞机大兵.png");
health = 150; //血量
break;
case 2:
getimage(hoseimg_1, "飞机大战程序素材\\敌人飞机精英怪.png");
health = 250;
break;
case 3:
getimage(hoseimg_1, "飞机大战程序素材\\敌人飞机BOSS2.png");
health = 440;
break;
case 4:
getimage(hoseimg_1, "飞机大战程序素材\\敌人飞机BOSS.png");
health = 20000; //血量
m_x = 10, m_y = -360; //BOSS从固定位置出发
}
setimage(width, height, hoseimg_1); //设置图片宽高
}
//敌人移动
void move()
{
planlaunch(); //发射子弹
//根据不同的飞机设置不同的在没有出现时,飞机血量锁定
if((m_y + height) <= 0)
switch (planeprop)
{
case 1:
health = 150; break;
case 2:
health = 250; break;
case 3:
health = 440; break;
case 4:
health = 20000;
}
//根据飞机不同属性确定飞机移动速度
switch (planeprop)
{
case 1:
case 2: //大小兵
m_y += 2; break;
case 3: //精英飞机
m_y++; break;
case 4: //最终BOSS 仅缓慢移动一小段距离
if (m_y <= -120) m_y++;
}
putimage_withalpha(NULL, hoseimg_1, m_x, m_y); //显示飞机图片
}
};
//我方飞机子类
class Planefind :public Plane
{
private:
const PIMAGE findplaneimg = newimage(); //我方飞机图片对象
const PIMAGE bulletimg = newimage(); //子弹图片对象
const PIMAGE shieldimg = newimage(); //护盾图片对象
const PIMAGE shieldicon = newimage(); //护盾图标
const PIMAGE unskillimg = newimage(); //必杀图片对象
const PIMAGE unskillicon = newimage(); //必杀图标
char key = 0; //接收键值
int inerdis = 0; //飞机惯性走的距离
bool sign = false; //是否播放爆炸效果
int atemp = 0; //记录子弹爆炸效果的次数
int btarg_x = 0, btarg_y = 0; //记录集中敌人的子弹位置
int btemp = 0; //记录自身爆炸效果的次数
int score = 0; //游戏得分
bool shieldsign = false; //标记护盾开启
clock_t shiledtime_1 = 0; //记录护盾时间
clock_t shiledtime_2 = 0;
int shieldnum = 3; //护盾使用次数
bool unskillsign = false; //标记必杀开启
clock_t unskilltime_1 = 0; //记录必杀时间
clock_t unskilltime_2 = 0;
int unskillnum = 2; //必杀技使用次数
bool upgrade = false; //飞机是否已升级
int lifenum = 3; //我方飞机的生命数量
PIMAGE explimgarr[24] = { 0 }; //爆炸效果图片对象数组
int i = 0; //用于循环
MUSIC music; //音乐对象
//升级飞机和子弹外观
void alterbullplan()
{
getimage(findplaneimg, "飞机大战程序素材\\己方红色飞机.png");
setimage(80, 86, findplaneimg);
getimage(bulletimg, "飞机大战程序素材\\我方导弹.png");
setimage(17, 37, bulletimg);
upgrade = true; //是否已经升级
}
//新建子弹
void pushlaunch()
{
if (upgrade) //如果已经升级
{
temp.b_x = m_x - 10, temp.b_y = m_y + 40;
bullvec.push_back(temp);
temp.b_x = m_x + 31, temp.b_y = m_y + 40;
bullvec.push_back(temp);
temp.b_x = m_x + 73, temp.b_y = m_y + 40;
bullvec.push_back(temp);
return;
}
//没有升级
temp.b_x = m_x + 24, temp.b_y = m_y - 20; //确定新子弹的位置
bullvec.push_back(temp); //在子弹数组中插入新的子弹
}
//子弹移动
inline void launmove()
{
//所有子弹移动
if (upgrade) //如果已经升级飞机则提升弹道速度
for (int i = 0; i < bullvec.size(); i++)
bullvec[i].b_y -= 24; //升级后的速度
else
for (int i = 0; i < bullvec.size(); i++)
bullvec[i].b_y -= 16; //一般速度
//输出所有子弹
for (int i = 0; i < bullvec.size(); i++)
putimage_withalpha(NULL, bulletimg, bullvec[i].b_x, bullvec[i].b_y);
}
//发射子弹,参数区分不同的飞机和图片
void launch()
{
now_2 = clock(); //精确到毫秒获取时间
timediff = now_2 - now_1; //计算子弹发射时间差
int attack = 0; //时间间隔
if (upgrade) //如果已经升级飞机则提升攻速
attack = 180; //升级后的攻速
else
attack = 320; //一般攻速
if (timediff >= attack) //发射子弹后时间大于一定数值(毫秒)则再次发射子弹
{
pushlaunch(); //新建子弹
timediff = 0; //累计时间重置
now_1 = clock(); //重新获取子弹发射时时间
}
launmove(); //子弹移动
//删除我方越界的子弹
for (vector<bulxy>::const_iterator it = bullvec.begin(); it != bullvec.end(); it++)
if (it->b_y <= -80)
{
bullvec.erase(it);
break;
}
}
//攻击敌人与被敌人攻击
void beattack(vector<Planehose>& hoseplanevec, bool& explsign)
{
if (!unskillsign) //在没有释放必杀技时不停发射子弹
launch(); //发射子弹
int hosesizex, hosesizey; //敌人飞机宽高变量
for (vector<Planehose>::iterator hoseit = hoseplanevec.begin(); hoseit != hoseplanevec.end(); hoseit++)
for (vector<bulxy>::const_iterator it = bullvec.begin(); it != bullvec.end(); it++)
{
hosesizex = hoseit->getwidth(), hosesizey = hoseit->getheight(); //敌人飞机的宽高
//如果有一发子弹的坐标与敌人重合,则敌人掉血
if (it->b_x + 17 >= hoseit->setgetm_x() && it->b_x + 17 <= hoseit->setgetm_x() + hosesizex &&
it->b_y >= hoseit->setgetm_y() && it->b_y <= hoseit->setgetm_y() + hosesizey)
{
hoseit->health -= 50; //敌人的飞机血量减少
btarg_x = it->b_x, btarg_y = it->b_y; //记录爆炸效果坐标
sign = true; //控制爆炸效果
if (hoseit->health <= 0) //如果本次击中的敌人的血量<=0
{
switch (hoseit->gethoseprop()) //根据消灭的敌人,增加不同得分
{
case 1:
score++;
break;
case 2:
score += 2;
break;
case 3:
score += 4;
break;
case 4:
score += 21;
}
hoseplanevec.erase(hoseit); //删除与我方子弹重合且血量为空的飞机
//产生一个新的敌军飞机
Planehose hoseplane(rand() % 3 + 1);//敌人
hoseplanevec.push_back(hoseplane); //插入进敌人飞机数组
goto L1; //跳出双重循环
}
bullvec.erase(it); //删除这个击中敌人的子弹
break;
}
}
L1:
//击中敌人时子弹处爆炸效果
if (sign)
{
if (music.GetPlayStatus() == MUSIC_MODE_STOP) //播放击中音效,必须在该音效播放完毕后播放
music.Play(100, 400);
putimage_withalpha(NULL, explimgarr[atemp], btarg_x - 17, btarg_y);
atemp++; //根据爆炸图片数组来显示爆炸效果
if (atemp > 23) //播放完毕停止
sign = false, atemp = 0; //关闭信号,重置数组下标
}
//被敌人击中时自己的爆炸效果,显示在飞机之上
if (explsign)
{
if (music.GetPlayStatus() == MUSIC_MODE_STOP) //播放击中音效,必须在该音效播放完毕后播放
music.Play(80, 450);
putimage_withalpha(NULL, explimgarr[btemp], m_x + 20, m_y + 20);
btemp++; //根据爆炸图片数组来显示爆炸效果
if (btemp > 23) //播放完毕停止
explsign = false, btemp = 0;
}
}
//两个技能
void tuoskill(vector<Planehose>& hoseplanevec)
{
unskilltime_2 = clock(); //必杀开启之后的时间
int temptime_1 = unskilltime_2 - unskilltime_1; //相减
if (unskillsign) //如果必杀开启
{
//同时使用一次护盾,不消耗次数
shieldsign = true; //护盾开启标记
shiledtime_1 = clock(); //护盾开启时的时间
putimage_withalpha(NULL, unskillimg, m_x - 32, m_y - 690); //显示护盾图片
health = 400; //血量恢复且护盾时效内不会掉血
if (temptime_1 >= 6000) unskillsign = false; //必杀有效时间
//必杀技攻击敌人的判定,这里只判断x轴
for (vector<Planehose>::iterator hoseit = hoseplanevec.begin(); hoseit != hoseplanevec.end(); hoseit++)
{
int hosesizex = 0; //敌人飞机的大小xy
switch (hoseit->gethoseprop())
{
case 1:
case 2:
hosesizex = 120;
break;
case 3:
hosesizex = 240;
break;
case 4:
hosesizex = 480;
}
//根据敌机的大小判断位置,这里只判断x轴
if ((m_x - 16 < hoseit->setgetm_x()) || (m_x - 16 > hoseit->setgetm_x() + hosesizex))
continue;
hoseit->health -= 50; //敌人的飞机血量减少
btarg_x = m_x + 30, btarg_y = hoseit->setgetm_y() + 50; //记录爆炸效果坐标,根据敌机坐标确认
if(hoseit->gethoseprop() == 4) //如果击中的敌人是最终BOSS则单独确定爆炸位置
btarg_y += 160;
sign = true; //控制爆炸效果
if (hoseit->health > 0) //如果本次击中的敌人的血量减少后>0 结束本次循环
continue;
switch (hoseit->gethoseprop()) //根据消灭的敌人,增加得分
{
case 1:
score++;
break;
case 2:
score += 2;
break;
case 3:
score += 4;
break;
case 4:
score += 21;
}
hoseplanevec.erase(hoseit); //删除与我方子弹重合且血量为空的飞机
//产生一个新的敌军飞机
Planehose hoseplane(rand() % 3 + 1);//敌人
hoseplanevec.push_back(hoseplane); //插入进敌人飞机数组
break;
}
}
shiledtime_2 = clock(); //护盾开启之后的时间
int temptime_2 = shiledtime_2 - shiledtime_1; //相减
if (shieldsign) //如果护盾开启
{
putimage_withalpha(NULL, shieldimg, m_x - 22, m_y - 10);
health = 400; //血量恢复且护盾时效内不会掉血
if (temptime_2 >= 5000) shieldsign = false; //护盾有效时间
}
}
//飞机接收键值移动
void controlmove()
{
//移动
//键盘按下
if (kbhit())
{
//接收一个键值
key = getch();
switch (key)
{
case 'w': //w飞机向上飞,并且确定一个向上滑行距离
m_y -= 6; //移动距离
inerdis = m_y - 18; //惯性距离
break;
case 'a':
m_x -= 6;
inerdis = m_x - 18;
break;
case 's':
m_y += 6;
inerdis = m_y + 18;
break;
case 'd':
m_x += 6;
inerdis = m_x + 18;
break;
case 'e': //使用护盾
if (shieldnum <= 0) //没有使用次数时不可以使用护盾
break;
shieldsign = true; //护盾开启标记
shiledtime_1 = clock(); //护盾开启时的时间
shieldnum--;
break;
case 'q': //必杀技
if (unskillnum <= 0) //没有使用次数时不可以使用护盾
break;
unskillsign = true; //必杀技开启标记
unskilltime_1 = clock(); //必杀技开始时时间
unskillnum--; //必杀技使用次数
break;
case ' ': //空格键暂停
putimage_withalpha(NULL, findplaneimg, m_x, m_y); //先显示一遍飞机
getch();
}
}
//确定飞机滑行时的速度和限制飞机移动范围
switch (key)
{
case 'w':
if (m_y >= inerdis) m_y -= 2; //飞机结束移动后向特定位置滑行一段距离
if (m_y <= 0) m_y = 0; //飞机坐标越界则重置坐标
break;
case 'a':
if (m_x >= inerdis) m_x -= 2;
if (m_x <= 0) m_x = 0;
break;
case 's':
if (m_y <= inerdis) m_y += 2;
if (m_y >= 674) m_y = 674;
break;
case 'd':
if (m_x <= inerdis) m_x += 2;
if (m_x >= 420) m_x = 420;
}
}
//输出界面信息
void putInterface()
{
//输出当前得分
setfont(30, NULL, "黑体");
xyprintf(0, 0, "得分:%d", score);
setfont(24, NULL, "黑体");
xyprintf(0, 50, "生命值:%d", health);
xyprintf(0, 90, "生命:%d", lifenum);
//图标
setfont(20, NULL, "楷体");
outtextxy(6, 380, "护盾E");
putimage_withalpha(NULL, shieldicon, 10, 410); //护盾图标
xyprintf(24, 460, "%d", shieldnum); //护盾剩余使用次数
outtextxy(1, 510, "必杀技Q");
putimage_withalpha(NULL, unskillicon, 12, 540); //必杀图标 ++
xyprintf(26, 590, "%d", unskillnum); //必杀剩余使用次数
}
public:
//构造初始化图片和坐标等,只会执行一次
Planefind()
{
width = 80, height = 86; //飞机宽高
//获取并修改图片素材
getimage(findplaneimg, "飞机大战程序素材\\己方蓝色飞机.png");
setimage(width, height, findplaneimg);
getimage(bulletimg, "飞机大战程序素材\\我方普通子弹.png");
setimage(34, 74, bulletimg);
getimage(shieldimg, "飞机大战程序素材\\护盾.png");
setimage(120, 120, shieldimg);
getimage(shieldicon, "飞机大战程序素材\\护盾.png");
setimage(40, 40, shieldicon);
getimage(unskillimg, "飞机大战程序素材\\激光.png");
setimage(160, 700, unskillimg);
getimage(unskillicon, "飞机大战程序素材\\激光.png");
setimage(40, 40, unskillicon);
for (i = 0; i < 24; i++) explimgarr[i] = newimage(); //开辟内存
//数组中总共8张图片,每张图片有两张重复帧
for (i = 0; i < 3; i++) getimage(explimgarr[i], "飞机大战程序素材\\爆炸1.png");
for (i = 3; i < 6; i++) getimage(explimgarr[i], "飞机大战程序素材\\爆炸2.png");
for (i = 6; i < 9; i++) getimage(explimgarr[i], "飞机大战程序素材\\爆炸3.png");
for (i = 9; i < 12; i++) getimage(explimgarr[i], "飞机大战程序素材\\爆炸4.png");
for (i = 12; i < 15; i++) getimage(explimgarr[i], "飞机大战程序素材\\爆炸5.png");
for (i = 15; i < 18; i++) getimage(explimgarr[i], "飞机大战程序素材\\爆炸6.png");
for (i = 18; i < 21; i++) getimage(explimgarr[i], "飞机大战程序素材\\爆炸7.png");
for (i = 21; i < 24; i++) getimage(explimgarr[i], "飞机大战程序素材\\爆炸8.png");
m_x = 220, m_y = 660; //飞机初始坐标
health = 400; //血量
music.OpenFile("飞机大战程序素材\\击中音效.wma");//打开音乐文件函数
}
//析构释放图片对象内存
~Planefind()
{
delimage(findplaneimg);
delimage(bulletimg);
delimage(shieldimg);
delimage(shieldicon);
delimage(unskillicon);
delimage(unskillimg);
for (int i = 0; i < 24; i++) delimage(explimgarr[i]); //释放爆炸效果数组
}
vector<bulxy> getlaunxy() //获取我方飞机子弹的坐标数组
{
return bullvec;
}
int& setgetplane_x() //获取或设置坐标
{
return m_x;
}
int& setgetplane_y()
{
return m_y;
}
int& setgetscore() //获取或设置得分
{
return score;
}
char& setgetkey() //获取或设置键值
{
return key;
}
int& setgetshieldnum() //获取或设置护盾使用次数
{
return shieldnum;
}
int& setgetunskillnumnum() //获取或设置必杀使用次数
{
return unskillnum;
}
int& setgetlifenum() //获取或设置我方飞机生命数
{
return lifenum;
}
//飞机移动
bool move(vector<Planehose>& hoseplanevec, bool& explsign)
{
//升级飞机
if (score >= 40) //如果得分大于40
alterbullplan(); //升级飞机
putInterface(); //输出界面信息
tuoskill(hoseplanevec); //两个技能相关
//我方飞机生命数量为0时游戏结束
if (!lifenum)
return true;
controlmove(); //接收键值移动飞机
//在飞机坐标位置显示飞机
putimage_withalpha(NULL, findplaneimg, m_x, m_y);
beattack(hoseplanevec, explsign); //攻击敌人与被敌人攻击
return false;
}
};
//游戏开始界面
void begingame()
{
initgraph(500, 760, INIT_RENDERMANUAL); //初始化图形界面
setcaption("C++ EGE飞机大战"); //设置标题
PIMAGE beginimg = newimage();
getimage(beginimg,"飞机大战程序素材\\游戏开始背景.jpg"); //获取图片
setimage(540, 760, beginimg); //设置大小
PIMAGE planetextimg = newimage(); //获取文字图片
getimage(planetextimg, "飞机大战程序素材\\飞机大战字体.png");
setimage(500, 340, planetextimg);
putimage(0, 0, beginimg); //显示背景图
putimage_withalpha(NULL, planetextimg, 0, 0); //显示文字图片
setfont(26, 0, "楷体");
setcolor(WHITE); //设置文字颜色
setbkmode(1); //文字背景色透明
outtextxy(150, 600, "请按任意键继续");
mouse_msg msg = { 0 }; //接收开始游戏的鼠标信息
while (true)
{
msg = getmouse(); //获取一条鼠标信息
if (msg.is_left()) break; //左键按下
delay_fps(70);
}
delimage(beginimg); //释放图片内存
delimage(planetextimg);
}
//游戏结束界面
void windefgame(const bool sign, Planefind &finplane)
{
PIMAGE winbackimg = newimage(); //获取游戏结束背景图片
getimage(winbackimg,"飞机大战程序素材\\游戏结束背景.jpg");
setimage(500, 760, winbackimg);
putimage(0, 0, winbackimg);
setfont(64, 0, "楷体");
sign ? outtextxy(186, 120, "胜利!") : outtextxy(186, 120, "失败!");
setfont(36, 0, "楷体");
finplane.setgetscore() += rand() % 10;
xyprintf(162, 220, "最终得分:%d", finplane.setgetscore());
delimage(winbackimg);
while (true) delay_fps(1);
}
//判断敌方子弹与我方飞机重合全局函数
void hosebullfinplan(const vector<Planehose>::iterator it, Planefind& finplane, bool& sign, int& lifenum)
{
for (vector<bulxy>::const_iterator ait = it->gethosebullvec().begin(); ait != it->gethosebullvec().end(); ait++)
if (ait->b_x >= finplane.setgetplane_x() && ait->b_x <= finplane.setgetplane_x() + 80 &&
ait->b_y + 18 >= finplane.setgetplane_y() && ait->b_y + 18 <= finplane.setgetplane_y() + 86 ||
ait->b_x + 18 >= finplane.setgetplane_x() && ait->b_x + 18 <= finplane.setgetplane_x() + 80 &&
ait->b_y + 18 >= finplane.setgetplane_y() && ait->b_y + 18 <= finplane.setgetplane_y() + 86)
{
finplane.health -= 50; //我方飞机血量减少
it->gethosebullvec().erase(ait); //删除敌人的该子弹
//如果飞机血量为空
if (finplane.health <= 0 && lifenum >= 0)
{
finplane.setgetplane_x() = 210; //坐标重置
finplane.setgetplane_y() = 660;
finplane.health = 400; //血量重置
lifenum--; //生命-1
finplane.setgetkey() = 0; //键值重置
}
sign = true; //爆炸效果标记开启
break;
}
}
//播放音乐函数
void playmusic(MUSIC &music)
{
music.OpenFile("飞机大战程序素材\\背景音乐到死.wma");//打开音乐文件函数
music.Play(0);
}
//主函数
int main()
{
begingame(); //游戏开始界面
MUSIC music; //音乐对象
playmusic(music); //播放音乐函数
PIMAGE gameback = newimage(); //背景图片
getimage(gameback,"飞机大战程序素材\\游戏背景.jpg");
setimage(500, 800, gameback); //设置背景图片大小
Planefind finplane; //我方飞机对象
vector<Planehose> hoseplanevec; //敌人飞机对象数组
bool sign = false; //标记我方爆炸效果显示
srand(time(NULL)); //随机种子,随机数用在敌人飞机xy坐标与飞机属性上
bool state = false; //记录游戏结束时的状态
bool boossign = false; //BOSS标记
//开局产生i个敌人小兵
for (int i = 0; i < 2; i++)
{
Planehose hoseplane(rand() % 2 + 1);//敌人只可能出现小兵和精英兵
hoseplanevec.push_back(hoseplane); //插入进数组
}
//游戏循环
while (true)
{
putimage(0, 0, gameback); //输出背景图片
if (music.GetPlayStatus() == MUSIC_MODE_STOP) //如果背景音乐播放完毕则重新播放
playmusic(music);
//遍历所有敌人
for (vector<Planehose>::iterator it = hoseplanevec.begin(); it != hoseplanevec.end(); it++)
{
if (it->gethoseprop() == 4 && it->gethealth() <= 50) //如果击败最终BOSS游戏胜利
{
state = true; //游戏以胜利状态退出
goto OVER;
}
it->move(); //敌方飞机移动
if (it->setgetm_y() >= 760) //飞机越界则让飞机重新上去
{
it->setgetm_x() = rand() % 380;
it->setgetm_y() = -(rand() % 101 + 200);
}
//判断每一个敌人的飞机的子弹是否和我方飞机坐标重合
hosebullfinplan(it, finplane, sign, finplane.setgetlifenum());
}
if (!(finplane.setgetscore() % 4) && finplane.setgetscore()) //得分是四的倍数且不为0的时候额外增加一个敌人
{
Planehose hoseplane(rand() % 2 + 1); //敌人只可能出现小兵和精英兵
hoseplanevec.push_back(hoseplane); //插入进数组
finplane.setgetscore()++; //得分额外加1
finplane.setgetshieldnum() += 2; //获得两次护盾使用次数
}
if (finplane.move(hoseplanevec, sign)) //我方飞机移动,参数为敌人飞机数组、标记、返回真时表示我方飞机阵亡,以失败退出游戏
{//返回值为true时,以游戏失败状态退出循环
state = false;
break;
}
//得分到达上限则清空所有敌人,挑战最终BOSS
if ((finplane.setgetscore()) >= 120 && (!boossign)) //得分大于一定值且标记为false时执行产生最终boss
{
hoseplanevec.clear(); //清空所有敌人
finplane.setgetunskillnumnum()++; //必杀次数+1
Planehose hoseplane(4); //BOSS
hoseplanevec.push_back(hoseplane); //将BOSS插入进敌人数组中
boossign = true; //只执行一次这段if代码
}
delay_fps(60);
}
OVER://游戏结束标签,用于击败BOSS后跳出多重循环
delimage(gameback); //释放背景图片对象
windefgame(state, finplane); //游戏结束界面
return 0;
}
不足之处:
游戏内容不是特别丰富,因为我懒的加,而且加的内容越多程序就越容易出问题。
因为程序中子弹结构数组是跟敌人是在一起的,所以当一个敌人被消灭后他发射出去的子弹也会消失,目前还没有比较好的解决办法。
我对内存管理掌握的不好,程序有可能存在一些其他问题。
另外子弹对飞机重合的判断我也是使用的非常简陋的方法,只判断一个中心点,这样写的话图片模型之间不真实,但是如果要多加几个判断实现比较完美的功能也没那个必要,毕竟效果都是差不多的。
欢迎大家提出批评和建议。
感谢大家的支持。