[导读]本系列博文内容链接如下:

【C++】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值

【C++】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动
【C++】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动

【C++】做一个飞机空战小游戏(四)——给游戏添加背景音乐(多线程技巧应用)

【C++】做一个飞机空战小游戏(五)——getch()控制两个飞机图标移动(控制光标位置)

【C++】做一个飞机空战小游戏(六)——给两架飞机设置不同颜色(cout输出彩色字符、结构体使用技巧)

【C++】做一个飞机空战小游戏(七)——两组按键同时检测平滑移动(GetAsyncKeyState()函数应用)

【C++】做一个飞机空战小游戏(八)——生成敌方炮弹(rand()函数应用)

前边7讲都是介绍飞机控制的方法,从今天开始介绍敌方炮弹生成的方法。炮弹生成所用到的rand()函数详细介绍见:【c++】rand()随机函数的应用(一)——rand()函数详解和实例_一只爬爬虫的博客-CSDN博客

目录

一、生成敌方炮弹的几个关键问题

(一)炮弹的数量

1、每轮炮弹数量(bombs_round)

2、每关总炮弹数量(bombs_stage)

3、最大同屏炮弹数量(end_bombs_round)

(二)炮弹出现的时间(rt)

(三)炮弹的起始位置

1、y坐标

2、x坐标

(四)炮弹移动速度(bomb_interval)

(五)炮弹与飞机相撞或被飞机子弹击落

二、构造结构体

(一)游戏结构体Game

(二)炮弹结构体Bomb

三、新增与炮弹相关的函数

(一)单个炮弹初始化函数

(二)所有炮弹初始化

(三)炮弹位置更新线程函数

(四)炮弹位置更新线程启动函数

四、更新后的程序代码

(一)主函数

(二)头文件control_plane.h

(三)库函数control_plane.cpp


一、生成敌方炮弹的几个关键问题

(一)炮弹的数量

1、每轮炮弹数量(bombs_round)

每轮炮弹数量,也就是屏幕当中出现的最多炮弹数量,也就是敌方同一波发射的最多炮弹数量,数量越多,难度也越大,这个数量可随关数的增加而增加。

2、每关总炮弹数量(bombs_stage)

每关发射的炮弹总数量不一样,关数越高,炮弹总数量也越多。

3、最大同屏炮弹数量(end_bombs_round)

最大单轮炮弹数量,也就是最后一关,敌方同一波发射炮弹数量。

(二)炮弹出现的时间(rt)

同一波炮弹出现的时间不能一样,否则所有炮弹呈现一排,游戏的体验感较差,所以,炮弹出现的时间也应该是随机的,也就是炮弹的y值是随机的。如下图所示,黄色方框为敌方炮弹。

为了实现这个效果,炮弹设置了一个炮弹坠地(炮弹到达屏幕最下方)、被击落或者与飞机相撞后死亡复活时间(respawn time,缩写为rt),在复活期间,炮弹不出现在屏幕。这个复活时间是随机的,这个时间会自减1,当这个时间降为0的时候,炮弹复活,才从屏幕上方出现。

(三)炮弹的起始位置

1、y坐标

炮弹开始出现在屏幕的位置位于屏幕最上方,也就是y=0处,但因为炮弹有复活时间,炮弹的起始位置不能在y=0处,防止炮弹还未复活就被击落。所以,炮弹的起始位置设置在屏幕最下侧再往下一行,也就是b_b+6,b_b为飞机坐标的下边界,6为飞机图标的高度。这样,炮弹在复活期间就不会与飞机相撞,也不会被飞机发射的子弹击中。

所以,炮弹从死亡开始,到复活前,其位置为:x坐标随机,y=b_b+6,而且在复活期间,位置不发生移动。复活后x坐标不变,y=0,然后y值自加1。

2、x坐标

x坐标在死亡后复活前和复活后都是随机生成的,且坐标值一样。为了简化游戏难度,炮弹出现在屏幕中后,x坐标值不发生变化,仅有y值再增加,也就是炮弹是竖直下落的。

(四)炮弹移动速度(bomb_interval)

炮弹移动速度,由炮弹位置更新线程函数内的Sleep函数的参数决定,数值越大,位置更新的间隔越大,移动速度就越小,反之就越快。

(五)炮弹与飞机相撞或被飞机子弹击落

本文今天暂时没考虑,后边会实现这个功能。

二、构造结构体

(一)游戏结构体Game

//定义游戏结构体 typedef struct{int stage;//游戏当前关 int bombs_round;//敌方每轮发射炮弹数量 int bombs_stage;//每关总计出现炮弹数量bool complete;//游戏通关 bool gameover;//游戏结束int num_plane;//飞机数量 int cur_num_bomb;//当前已发射炮弹数量 int bomb_interval; //位置更新间隔 bool bomb_move;//炮弹是否移动 }Game;

(二)炮弹结构体Bomb

//定义敌方炮弹结构体 typedef struct{Location location;//炮弹位置 bool alive;//炮弹是否存活 int color;//炮弹颜色string icon;//炮弹图标int rt;//rt=respawn time复活时间 int hp;//hp=hit point 生命值,此值<=0时,敌方炮弹死亡,敌方炮弹被飞机子弹击中hp会减少,坠地或与飞机相撞hp直接降为0 int dam;//dam=damage 伤害值int type;//炮弹类型 }Bomb;

三、新增与炮弹相关的函数

(一)单个炮弹初始化函数

这个函数在单个炮弹死亡后调用。

//单个炮弹初始化函数 Bomb init_bomb(Bomb bomb){bomb.location.x=rand()%r_b;bomb.location.y=b_b+6;bomb.icon=icon_bomb;bomb.color=6;bomb.dam=1;bomb.hp=1;bomb.alive=false;bomb.rt=rand()%(eq_rt+1)+1;//eq_rt为复活最长时间return bomb;}

此处,init_bomb函数的形参为结构体,形参传输函数计算后的结果,并不能直接返回给全局结构体,所以必须采用返回值的方法将初始化的值传递给结构体。

(二)所有炮弹初始化

这个函数在游戏启动或者游戏过关后调用。

//所有炮弹初始化函数 void init_bombs(void){game.bomb_move=false;for(int i=0;i<game.bombs_round;i++){bomb[i]=init_bomb(bomb[i]);}}

(三)炮弹位置更新线程函数

这个函数在线程启动函数中启用。

//炮弹位置更新 线程 void* thread_bomb(void* arg){while(1){Sleep(game.bomb_interval);game.bomb_move=true;for(int i=0;i<game.bombs_round;i++){if(bomb[i].alive){bomb[i].location.y++;}else{bomb[i].rt--;if(bomb[i].rtb_b+5){bomb[i].hp=0;}if(bomb[i].hp<=0){bomb[i]=init_bomb(bomb[i]);}}}}

(四)炮弹位置更新线程启动函数

这个函数在主函数中调用启动,然后一直运行。

//炮弹位置更新线程启动函数void bomb_location_update(){pthread_t tid; pthread_create(&tid, NULL, thread_bomb, NULL);}

四、更新后的程序代码

(一)主函数

#include "control_plane.h"using namespace std;Plane plane[eq_plane];Game game;Bomb bomb[eq_bombs_round]; int main(int argc, char** argv) {init();//初始化bgmusic();//播放背景音乐getkey();bomb_location_update();while(1)//循环等待键盘指令 {if(plane[0].keycmd!=none_cmd ||plane[1].keycmd!=none_cmd ||game.bomb_move){game.bomb_move=false;system("cls");for(int i=0;i<game.num_plane;i++){show_plane(plane[i]);//刷新飞机图标}for(int i=0;i<game.bombs_round;i++){if(bomb[i].alive){show_bomb(bomb[i]);}}}}return 0; }

(二)头文件control_plane.h

#ifndef CONTROL_PLANE_H#define CONTROL_PLANE#include #include #include #include#include#include //导入线程头文件库#include  //导入声音头文件库#pragma comment(lib,"winmm.lib")//导入声音的链接库#define _CRT_SECURE_NO_WARNINGS using namespace std; #define t_b 0//图形显示区域上侧边界 #define l_b 0//图形显示区域左侧边界#define r_b 100//图形显示区域右侧边界#define b_b 20//图形显示区域下侧边界#define eq_plane 2//飞机架数#define eq_bombs_round 23//eq=end quantity最终炮弹数量 #define eq_rt 10//复活最大时间 //定义飞机造型 const string icon_plane1[]={"■","■■■","■■■■■","■■■","■","■■■"};const string icon_plane2[]={"■","■■■","■■■■■","■","■■■","■■■■■"};//定义炮弹造型const string icon_bomb="■"; //定义坐标结构体 typedef struct{int x;int y;} Location;//定义移动方向命令枚举类型 typedefenum {none_cmd,up_cmd,down_cmd,left_cmd,right_cmd} direction_cmd; //定义游戏结构体 typedef struct{int stage;//游戏当前关 int bombs_round;//敌方每轮发射炮弹数量 int bombs_stage;//每关总计出现炮弹数量 bool clear;//游戏过关 bool complete;//游戏通关 bool gameover;//游戏结束int num_plane;//飞机数量 int cur_num_bomb;//当前已发射炮弹数量 int bomb_interval; //位置更新间隔 bool bomb_move;//炮弹是否移动 }Game; //定义飞机结构体 typedef struct{Location location;int color;int icon;direction_cmd keycmd;}Plane;//定义敌方炮弹结构体 typedef struct{Location location;//炮弹位置 bool alive;//炮弹是否存活 int color;//炮弹颜色string icon;//炮弹图标int rt;//rt=respawn time复活时间 int hp;//hp=hit point 生命值,此值<=0时,敌方炮弹死亡,敌方炮弹被飞机子弹击中hp会减少,坠地或与飞机相撞hp直接降为0 int dam;//dam=damage 伤害值int type;//炮弹类型 }Bomb; extern Plane plane[eq_plane];extern Game game;extern Bomb bomb[eq_bombs_round]; //声明刷新飞机位置函数void show_plane(Plane plane); //获取键盘指令 void key(void); //更新所有飞机坐标void plane_location_update(void); //初始化函数 void init(void); //播放背景音乐线程 void* thread_bgmusic(void* arg);void play_bgmusic();void bgmusic(); //获取按键指令线程void* thread_key(void* arg);void getkey(); //输出彩色字符函数template//T表示任何可以被cout输出的类型 void ColorCout(T t, const int ForeColor = 7, const int BackColor = 0);void init_bombs(void);Bomb init_(Bomb bomb);void* thread_bomb(void* arg);void bomb_location_update();void show_bomb(Bomb bomb);#endif

(三)库函数control_plane.cpp

#include #include "conio.h"#include #include "control_plane.h"#includeusing namespace std;//彩色输出函数template//T表示任何可以被cout输出的类型void ColorCout(T t, const int ForeColor = 7, const int BackColor = 0){//0 = 黑色1 = 蓝色 2 = 绿色 3 = 浅绿色 4 = 红色 5 = 紫色 6 = 黄色 7 = 白色//8 = 灰色9 = 淡蓝色10 = 淡绿色11 = 淡浅绿色12 = 淡红色13 = 淡紫色14 = 淡黄色15 = 亮白色SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), ForeColor + BackColor * 0x10);cout << t;SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);}//隐藏光标函数HANDLE han = GetStdHandle(-11);void hide(){CONSOLE_CURSOR_INFO cursor;cursor.bVisible = 0;cursor.dwSize = 1;SetConsoleCursorInfo(han,&cursor);} //初始化函数 void init(void){plane[0].location={2*r_b/3,b_b};plane[1].location={r_b/3,b_b};plane[0].color=1;plane[1].color=2;plane[0].icon=1;plane[1].icon=2;srand(time(NULL));game.num_plane=2;game.bombs_round=3;game.bomb_move=false;game.bomb_interval=1000;init_bombs();system("cls");for(int i=0;i<game.num_plane;i++)//刷新飞机图标{show_plane(plane[i]);plane[i].keycmd=none_cmd;}//game.num_plane=2;game.bombs_round=3;hide();//隐藏光标} //******************************************************************************** //以下三个函数为获得按键指令线程函数 //******************************************************************************** void* thread_key(void* arg){while(1){Sleep(60); //获取指令延时一定时间,起滤波作用,延缓获取指令的响应速度 key();//获取按键指令plane_location_update() ;//获取完指令马上更新飞机坐标 }}void getkey(){pthread_t tid; pthread_create(&tid, NULL, thread_key, NULL);} //获取键盘指令函数void key(void){direction_cmd c=none_cmd;direction_cmd d=none_cmd;if (GetAsyncKeyState(VK_UP) & 0x8000)c = up_cmd;if (GetAsyncKeyState(VK_DOWN) & 0x8000)c = down_cmd;if (GetAsyncKeyState(VK_LEFT) & 0x8000)c = left_cmd;if (GetAsyncKeyState(VK_RIGHT) & 0x8000)c = right_cmd;if (GetAsyncKeyState('W') & 0x8000)d = up_cmd;if (GetAsyncKeyState('S') & 0x8000)d = down_cmd;if (GetAsyncKeyState('A') & 0x8000)d = left_cmd;if (GetAsyncKeyState('D') & 0x8000)d = right_cmd;plane[0].keycmd=c;plane[1].keycmd=d;}void gotoxy(int x, int y) {COORD pos = { x,y };HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出设备句柄SetConsoleCursorPosition(hOut, pos);//两个参数分别指定哪个窗口,具体位置}//飞机图标刷新函数 void show_plane(Plane plane)//预先定义字符定位显示函数,x是列坐标,y是行坐标,原点(x=0,y=0)位于屏幕左上角 {int x,y;int i,j;int rows;x=plane.location.x;y=plane.location.y;switch(plane.icon){case 1://第一种造型 rows=sizeof(icon_plane1)/sizeof(icon_plane1[0]);for(i=0;i<rows;i++) {gotoxy(x,y+i);ColorCout(icon_plane1[i],plane.color);}break;case 2://第二种造型 rows=sizeof(icon_plane2)/sizeof(icon_plane2[0]);for(i=0;i<rows;i++){gotoxy(x,y+i);ColorCout(icon_plane2[i],plane.color);}break;}}//更新两个飞机的坐标 void plane_location_update(void){ for(int i=0;i<2;i++){if(plane[i].keycmd!=none_cmd) {int x,y; x=plane[i].location.x; y=plane[i].location.y; switch(plane[i].keycmd){case up_cmd:y--;//字符上移一行,行值y减1if(yb_b)//限定y高度 {y=b_b;}break;case left_cmd:x--;//字符左移一列,列值x减1if(xr_b){x=r_b;//限定x宽度}break;}plane[i].location.x=x; plane[i].location.y=y; plane[i].keycmd=none_cmd;}} }//单个炮弹初始化函数 Bomb init_bomb(Bomb bomb){bomb.location.x=rand()%r_b;bomb.location.y=b_b+6;bomb.icon=icon_bomb;bomb.color=6;bomb.dam=1;bomb.hp=1;bomb.alive=false;bomb.rt=rand()%(eq_rt+1)+1;return bomb;}//所有炮弹初始化函数 void init_bombs(void){game.bomb_move=false;for(int i=0;i<game.bombs_round;i++){bomb[i]=init_bomb(bomb[i]);}}//炮弹位置更新 线程 void* thread_bomb(void* arg){while(1){Sleep(game.bomb_interval);game.bomb_move=true;for(int i=0;i<game.bombs_round;i++){if(bomb[i].alive){bomb[i].location.y++;}else{bomb[i].rt--;if(bomb[i].rtb_b+5){bomb[i].hp=0;}if(bomb[i].hp<=0){bomb[i]=init_bomb(bomb[i]);}}}}//炮弹位置更新 void bomb_location_update(){pthread_t tid; pthread_create(&tid, NULL, thread_bomb, NULL);}炮弹图标刷新函数void show_bomb(Bomb bomb)//预先定义字符定位显示函数,x是列坐标,y是行坐标,原点(x=0,y=0)位于屏幕左上角 {int x,y;x=bomb.location.x;y=bomb.location.y;gotoxy(x,y);ColorCout(bomb.icon,bomb.color);} //******************************************************************************** //以下三个函数为播放背景音乐功能 //********************************************************************************//播放一遍背景音乐void play_bgmusic() { mciSendString(TEXT("open hero.mp3 alias s1"),NULL,0,NULL);mciSendString(TEXT("play s1"),NULL,0,NULL); Sleep(153*1000);//153*1000意思是153秒,是整首音乐的时长 mciSendString(TEXT("close S1"),NULL,0,NULL); } //循环播放音乐线程函数 void* thread_bgmusic(void* arg) //{ while(1){play_bgmusic();}}//创建音乐播放线程,开始循环播放音乐 void bgmusic(){pthread_t tid; pthread_create(&tid, NULL, thread_bgmusic, NULL);}

(未完待续)