[导读]本系列博文内容链接如下:
【C++】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值
【C++】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动
【C++】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动【C++】做一个飞机空战小游戏(四)——给游戏添加背景音乐(多线程技巧应用)
【C++】做一个飞机空战小游戏(五)——getch()控制两个飞机图标移动(控制光标位置)
【C++】做一个飞机空战小游戏(六)——给两架飞机设置不同颜色(cout输出彩色字符、结构体使用技巧)
【C++】做一个飞机空战小游戏(七)——两组按键同时检测平滑移动(GetAsyncKeyState()函数应用)
【C++】做一个飞机空战小游戏(八)——生成敌方炮弹(rand()函数应用)
【C++】做一个飞机空战小游戏(九)——发射子弹的编程技巧
【C++】做一个飞机空战小游戏(十)——子弹击落炮弹、炮弹与飞机相撞
今天介绍飞机发射子弹的内容。发射的子弹和敌方炮弹有些类似,炮弹和子弹死亡的时候不在屏幕出现,存活期内位置由系统确定,不再受玩家控制,都是是沿着直线匀速运动,定时更新等等。但也有很多不同的地方,区别如下。
目录
一、发射子弹与敌方炮弹的区别
(一)死亡期间位置不同
1、敌方炮弹
2、飞机子弹
(二)存活期初始位置
1、敌方炮弹
2、飞机子弹
(三)出现时间不同
1、敌方炮弹
2、飞机子弹
(四)数量不同
1、敌方炮弹
2、飞机子弹
(五)飞行方向不同
1、敌方炮弹
2、飞机子弹
二、程序代码
(一)新增子弹相关代码
1、构造Bullet结构体
2、新增子弹相关函数
(二)最终程序代码
1、主程序
2、头文件
3、库函数
三、运行效果
一、发射子弹与敌方炮弹的区别
(一)死亡期间位置不同
1、敌方炮弹
敌方炮弹死亡期间的位置x坐标是随机生成的,y坐标为游戏可见区域下一行。
2、飞机子弹
飞机子弹死亡期间的位置x坐标为0,y坐标为游戏可见区域下两行。
(二)存活期初始位置
1、敌方炮弹
敌方炮弹存活期初始位置x坐标与死亡期间一致,y坐标为0。
2、飞机子弹
飞机子弹存活期初始位置x坐标为飞机正中心,y坐标为飞机y坐标上一行。
(三)出现时间不同
1、敌方炮弹
敌方炮弹死亡后的复活时间是随机生成的,复活时间减少到0时,炮弹出现在屏幕上,出现时间由系统决定。
2、飞机子弹
飞机子弹是在飞机的开火键按下后出现的,出现时间由玩家控制。
(四)数量不同
1、敌方炮弹
敌方炮弹同一波数量在同一关是固定的,死亡一个复活一个。屏幕中出现的总的炮弹数量保持不变。在存放炮弹的数组中,数组的角标是固定不变的。
2、飞机子弹
飞机子弹是数量由玩家开火的频率确定,数量不固定。但是炮弹的数量有上限,每架飞机发射的子弹最密集为每行一个子弹,最少为0个。所以存放子弹数组的角标不固定,本例采用的方法为从头开始查询,哪个位置的子弹是死亡状态,那么飞机开火后的存活子弹就存放到这个位置,这个位置赋值为存活状态。当子弹冲出屏幕上方或者击中敌方炮弹,存放子弹的位置赋值为死亡状态。
(五)飞行方向不同
1、敌方炮弹
敌方炮弹从屏幕上方向下飞行,y坐标为自增1。
2、飞机子弹
飞机子弹从飞机前端向屏幕上方飞行,y坐标为自减1。
二、程序代码
(一)新增子弹相关代码
1、构造Bullet结构体
//定义子弹结构体
typedef struct{Location location; //子弹位置 bool alive; //子弹是否存活 int color; //子弹颜色string icon; //子弹图标 int hp; //hp=hit point 生命值,子弹击中炮弹或冲出屏幕上方hp直接降为0,子弹死亡 int dam; //dam=damage 伤害值int type; //子弹类型
}Bullet;
2、新增子弹相关函数
void bullet_location_update(); //子弹位置更新
void* thread_bullet(void* arg); //子弹线程函数
Bullet init_bullet(Bullet bullet); //单个子弹初始化
void init_bullets(void); //所有子弹初始化
Bullet fire(Plane plane,Bullet bullet); //飞机开火函数,确定子弹出现的起始位置
void show_bullet(Bullet bullet); //显示子弹图标
(二)最终程序代码
1、主程序
#include "control_plane.h"
#include "quenue.h"
using namespace std; Plane plane[eq_plane];
Game game;
Bomb bomb[eq_bombs_round];
Bullet bullet[eq_plane][eq_bullets_round];int main(int argc, char** argv) { init(); //初始化 bgmusic();//播放背景音乐getkey(); bomb_location_update(); bullet_location_update();while(1) //循环等待键盘指令 {if(plane[0].keycmd!=none_cmd ||plane[1].keycmd!=none_cmd ||game.bomb_move ||game.bullet_move){game.bomb_move=false;game.bullet_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]);}}for(int i=0;i<eq_plane;i++){for(int j=0;j<eq_bullets_round;j++){ if(bullet[i][j].alive){show_bullet(bullet[i][j]);}} } } }return 0;
}
2、头文件
#ifndef CONTROL_PLANE_H
#define CONTROL_PLANE
#include <iostream>
#include <ctime>
#include <string>
#include<stdlib.h>
#include<windows.h>
#include <pthread.h>//导入线程头文件库
#include <mmsystem.h> //导入声音头文件库
#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 plane_width 9
#define plane_height 6#define eq_plane 2 //飞机架数
#define eq_bombs_round 23 //eq=end quantity最终炮弹数量
#define eq_rt 10 //复活最大时间
#define eq_bullets_round 20 //eq=end quantity飞机一次发射最多子弹数量 //定义飞机造型
const string icon_plane1[]={" ■","■ ■ ■","■■■■■","■ ■ ■"," ■"," ■■■"};
const string icon_plane2[]={" ■","■ ■ ■","■■■■■"," ■"," ■■■","■■■■■"};//定义炮弹造型
const string icon_bomb="■";//定义子弹造型
const string icon_bullet="■";//定义坐标结构体
typedef struct{int x;int y;
} Location;//定义移动方向命令枚举类型
typedef enum {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; //炮弹是否移动bool bullet_move;
}Game;//定义飞机结构体
typedef struct{Location location;int color;int icon;direction_cmd keycmd;bool fire;int cnt_bullets; //按一次开火键加1,然后初始化bullet[cnt_bullets],子弹死亡一个减1
}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;//定义子弹结构体
typedef struct{Location location; //子弹位置 bool alive; //子弹是否存活 int color; //子弹颜色string icon; //子弹图标 int hp; //hp=hit point 生命值,子弹击中炮弹或冲出屏幕上方hp直接降为0,子弹死亡 int dam; //dam=damage 伤害值int type; //子弹类型
}Bullet;extern Plane plane[eq_plane];
extern Game game;
extern Bomb bomb[eq_bombs_round];
extern Bullet bullet[eq_plane][eq_bullets_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<typename T> //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);void bullet_location_update(); //子弹位置更新
void* thread_bullet(void* arg); //子弹线程函数
Bullet init_bullet(Bullet bullet); //单个子弹初始化
void init_bullets(void); //所有子弹初始化
Bullet fire(Plane plane,Bullet bullet); //飞机开火函数,确定子弹出现的起始位置
void show_bullet(Bullet bullet); //显示子弹图标 #endif
3、库函数
#include <iostream>
#include "conio.h"
#include <string>
#include "control_plane.h"
#include<windows.h>
using namespace std;//彩色输出函数
template<typename T> //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.bullet_move=false;game.bomb_interval=1000; init_bombs();init_bullets();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;if (GetAsyncKeyState('P') & 0x8000) plane[0].fire = true;if (GetAsyncKeyState('F') & 0x8000) plane[1].fire = true;
}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(y<t_b) //限定y值最小值为0{y=t_b;}break;case down_cmd:y++; //字符下移一行,行值y加1if(y>b_b) //限定y高度 {y=b_b;}break;case left_cmd:x--; //字符左移一列,列值x减1if(x<l_b){x=l_b; //限定x最小值为0; }break;case right_cmd:x++; //字符右移一列,列值x加1if(x>r_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].rt<=0){bomb[i].alive=true;bomb[i].location.y=0;} }if(bomb[i].location.y>b_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;hide();//隐藏光标gotoxy(x,y);ColorCout(bomb.icon,bomb.color);} //发射子弹
Bullet fire(Plane plane,Bullet bullet)
{game.bullet_move=true;bullet.location.x=plane.location.x+plane_width/2; bullet.location.y=plane.location.y-1;bullet.alive=true; return bullet;
}//单个子弹初始化函数
Bullet init_bullet(Bullet bullet)
{bullet.icon=icon_bullet;bullet.location.x=0;bullet.location.y=b_b+7; bullet.alive=false;bullet.color=4;bullet.dam=1;bullet.hp=1; return bullet;
}//所有子弹初始化函数
void init_bullets(void)
{game.bullet_move=false; for(int i=0;i<eq_plane;i++){for(int j=0;j<eq_bullets_round;j++){bullet[i][j]=init_bullet(bullet[i][j]); } }
}//子弹位置更新 线程
void* thread_bullet(void* arg)
{while(1){Sleep(game.bomb_interval);for(int i=0;i<eq_plane;i++){if(plane[i].fire){ game.bullet_move=true; for(int j=0;j<eq_bullets_round;j++){if(!bullet[i][j].alive){bullet[i][j]=fire(plane[i],bullet[i][j]);break;} }}plane[i].fire=false;}for(int i=0;i<eq_plane;i++){for(int j=0;j<eq_bullets_round;j++){ if(bullet[i][j].alive){bullet[i][j].location.y--;if(bullet[i][j].location.y<0){bullet[i][j].hp=0; }if(bullet[i][j].hp<=0){//bullet[i][j].alive=false;bullet[i][j]=init_bullet(bullet[i][j]); } }} } }
}
//子弹位置更新
void bullet_location_update()
{pthread_t tid; pthread_create(&tid, NULL, thread_bullet, NULL);
}子弹图标刷新函数
void show_bullet(Bullet bullet) //预先定义字符定位显示函数,x是列坐标,y是行坐标,原点(x=0,y=0)位于屏幕左上角
{int x,y; x=bullet.location.x;y=bullet.location.y;hide();//隐藏光标gotoxy(x,y);ColorCout(bullet.icon,bullet.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);
}
三、运行效果
运行效果如下,注意本节只介绍了子弹实现的方法,后边会介绍子弹与炮弹相撞、炮弹与飞机相撞的算法。
(未完待续)