[导读]本系列博文内容链接如下:
【C++】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值
【C++】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动
【C++】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动【C++】做一个飞机空战小游戏(四)——给游戏添加背景音乐(多线程技巧应用)
【C++】做一个飞机空战小游戏(五)——getch()控制两个飞机图标移动(控制光标位置)
【C++】做一个飞机空战小游戏(六)——给两架飞机设置不同颜色(cout输出彩色字符、结构体使用技巧)
【C++】做一个飞机空战小游戏(七)——两组按键同时检测平滑移动(GetAsyncKeyState()函数应用)
【C++】做一个飞机空战小游戏(八)——生成敌方炮弹(rand()函数应用)
【C++】做一个飞机空战小游戏(九)——发射子弹的编程技巧
【C++】做一个飞机空战小游戏(十)——子弹击落炮弹、炮弹与飞机相撞
【C++】做一个飞机空战小游戏(十一)——游戏过关、通关、结束的设置
本节介绍游戏的过关、通关和游戏结束(game over)的相关设置。过关的依据是敌方死亡炮弹的数量,当达到设定数目时,即可通过这一关,当通过最后一关时,即完成通关,如果还未通关时,所有飞机的命已经没了,那游戏就结束了。每一关敌方每波炮弹数量、每关总死亡炮弹数量、炮弹移动速度、炮弹血量、炮弹伤害值都是不一样的,是随着关数的增加而递增的,也就是游戏过关的难度越来越高。本节还实现了游戏信息显示、爆炸音效、飞机开火音效、控制台窗口大小设置等功能。
目录
一、游戏过关、通关、结束
(一)过关
1、game结构体新增属性
2、过关的判断条件
3、过关后的游戏属性变化
(二)通关
1、game结构体新增属性
2、判断条件
3、通关后的游戏属性变化
(1)主函数、炮弹更新线程、按键更新线程、子弹更新线程
(2)show_game_complete()函数
(三)游戏结束
1、game结构体新增属性
2、判断条件
3、游戏结束后的游戏属性变化
(1)主函数、炮弹更新线程、按键更新线程、子弹更新线程
(2)show_game_over()函数
二、游戏音效设置
(一)炸弹爆炸音效
1、爆炸音效线程
2、触发条件
(1)炮弹与飞机相撞
(2)子弹与炮弹相撞
(二)飞机开火音效
1、开火音效线程
2、触发条件
三、游戏信息显示
(一)需要显示的信息
1、当前关数
2、剩余炮弹
3、飞机杀敌数目
4、飞机剩余命数
5、飞机复活保护时间
(二)头文件相关信息
(三)显示函数
(四)飞机复活保护程序
1、倒计时线程
2、保护条件
四、漏洞修补
五、控制台窗口设置
(一)大小设置
(二)取消最大化最小化
1、函数调用
2、函数定义
六、最终程序
(一)主函数
(二)头文件control_plane.h
(三)库函数control_plane.cpp
七、运行效果
八、不足之处
一、游戏过关、通关、结束
(一)过关
1、game结构体新增属性
int bombs_round_increase; //下一关敌方每波发射炮弹数量增加值
int bombs_stage_increase; //下一关总计发射炮弹数量增加值
int bomb_interval_reduce //下一关炮弹更新间隔减少值
int last_stage; //游戏一共多少关
2、过关的判断条件
当前已发射并死亡的炮弹数量game.cur_num_bomb>=当前关总计发射并死亡的炮弹数量game.bombs_stage,并且当前关不是最后一关,如果当前关是最后一关,就是通关。
3、过关后的游戏属性变化
敌方每波炮弹数量、每关总死亡炮弹数量、炮弹移动速度、炮弹血量、炮弹伤害值都相应增加。代码如下:
if(game.cur_num_bomb>=game.bombs_stage)
{
if(game.cur_stage<game.last_stage)
{
game.clear=true;
game.cur_num_bomb=0;
game.bombs_stage+=game.bombs_stage_increase;
game.bombs_round+=game.bombs_round_increase;
game.bomb_interval-=game.bomb_interval_reduce;
game.cur_stage+=1;
init_bombs();
init_bullets();
}
else
{
game.complete=true;
}
}
炮弹血量、伤害值的变化在init_bombs()函数中
//单个炮弹初始化函数
Bomb init_bomb(Bomb bomb,int stage)
{
bomb.location.x=rand()%r_b; //x坐标随机
bomb.location.y=b_b+6; //y显示区域底部下一行
bomb.icon=icon_bomb; //设置炮弹图标
bomb.color=6; //设置炮弹颜色为6,黄色
bomb.dam=1+stage/3; //炮弹伤害值每过3关增加1点
bomb.hp=stage; //炮弹血量每过1关增加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],game.cur_stage);
}
}
(二)通关
1、game结构体新增属性
bool show_complete; //是否显示游戏通关信息
2、判断条件
判定条件见过关的判定条件。
3、通关后的游戏属性变化
通关后game.complete=true,然后要显示通关信息。
(1)主函数、炮弹更新线程、按键更新线程、子弹更新线程
while(game.complete)
{
show_game_complete();
}
(2)show_game_complete()函数
//显示游戏通关信息
void show_game_complete(void)
{
if(!game.show_complete)
{
game.show_complete=true;
Sleep(100);
system("cls");
show_info();
gotoxy((l_b+r_b)/2-4,(t_b+b_b)/2);
ColorCout(game_complete_text,game.info_color);
}
}
(三)游戏结束
1、game结构体新增属性
bool show_gameover; //是否显示游戏结束信息
2、判断条件
判断条件就是两架飞机命都为0,具体代码在void* thread_bomb(void* arg)线程中
//以下一段程序作用:判断两架飞机都没有命的情况,即为游戏结束
if(!(plane[0].life>0||plane[1].life>0))
{
game.gameover=true;
}
3、游戏结束后的游戏属性变化
游戏结束后,要显示游戏结束的信息。
(1)主函数、炮弹更新线程、按键更新线程、子弹更新线程
while(game.gameover) //等待游戏结束
{
show_game_over();
}
(2)show_game_over()函数
//显示游戏结束信息
void show_game_over(void)
{
if(!game.show_gameover)
{
game.show_gameover=true;
Sleep(100);
system("cls");
show_info();
gotoxy((l_b+r_b)/2-4,(t_b+b_b)/2);
ColorCout(game_over_text,game.info_color);
}
}
二、游戏音效设置
(一)炸弹爆炸音效
1、爆炸音效线程
//********************************************************************************
//以下三个函数为播放爆炸音效功能
//********************************************************************************
//循环播放爆炸音乐线程函数
void* thread_bomb_music(void* arg) //
{
while(1)
{
if(game.bomb)
{
play_bomb_music();
game.bomb=false;
}
}
}
//创建爆炸音效播放线程,开始循环播放音效
void bomb_music()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_bomb_music, NULL);
}
//播放一遍爆炸音效
void play_bomb_music() {
mciSendString(TEXT("open bomb.mp3 alias s1"),NULL,0,NULL);
mciSendString(TEXT("play s1"),NULL,0,NULL);
Sleep(2*1000);//2*1000意思是2秒,是整首音乐的时长
mciSendString(TEXT("close S1"),NULL,0,NULL);
}
2、触发条件
(1)炮弹与飞机相撞
代码在thread_bomb(void* arg)线程中:
if(collide(plane[j],bomb[i])) //判断飞机是否与炮弹相撞
{
game.bomb=true;
......
}
(2)子弹与炮弹相撞
代码在thread_bullet(void* arg)线程中:
if(shoot(bullet[i][j],bomb[k])) //如果被击中
{
game.bomb=true; //爆炸声音启动
......
}
(二)飞机开火音效
1、开火音效线程
//********************************************************************************
//以下三个函数为播放机开火音效功能
//********************************************************************************
//循环播放飞机开火音效线程函数
void* thread_fire_music(void* arg) //
{
while(1)
{
if(game.fire)
{
play_fire_music();
game.fire=false;
}
}
}
//创建飞机开火音效播放线程,开始循环播放音乐
void fire_music()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_fire_music, NULL);
}
//播放一遍飞机开火音效
void play_fire_music() {
mciSendString(TEXT("open fire.mp3 alias s1"),NULL,0,NULL);
mciSendString(TEXT("play s1"),NULL,0,NULL);
Sleep(1500);//153*1000意思是153秒,是整首音乐的时长
mciSendString(TEXT("close S1"),NULL,0,NULL);
}
2、触发条件
飞机开火键按下,代码在开火函数中:
//发射子弹
Bullet fire(Plane plane,Bullet bullet)
{
game.fire=true;
......
}
三、游戏信息显示
(一)需要显示的信息
1、当前关数
当前所在关的关数game.cur_stage。
2、剩余炮弹
剩余炮弹=该关设定总死亡炮弹数量-当前已死亡炮弹数量。
game.bombs_stage-game.cur_num_bomb。
3、飞机杀敌数目
飞机杀敌数目=该飞机子弹击落炮弹数量+飞机撞落炮弹数量。
4、飞机剩余命数
飞机剩余命数plane.life
5、飞机复活保护时间
防止飞机刚复活时,其初始化位置刚好有炮弹而将飞机撞落,所以设定了复活保护时间为10s。在此期间,炮弹与飞机相撞,飞机和炮弹都不会掉血。
(二)头文件相关信息
//以下为游戏当中要显示的信息文本
#define cur_stage_text "当前关次:"
#define remain_bombs_text "剩余炮弹:"
#define play0_kill_text "飞机0杀敌:"
#define play1_kill_text "飞机1杀敌:"
#define play0_life_text "飞机0命数:"
#define play1_life_text "飞机1命数:"
#define play0_rt_text "飞机0保护时间:"
#define play1_rt_text "飞机1保护时间:"
#define game_complete_text "恭喜,游戏通关!"
#define game_over_text "遗憾,游戏结束!"
extern Location loc_info;
(三)显示函数
//显示游戏信息
void show_info(void)
{
gotoxy(loc_info.x,loc_info.y);
ColorCout(cur_stage_text,game.info_color);
ColorCout(game.cur_stage,game.info_color);
gotoxy(loc_info.x,loc_info.y+2);
ColorCout(remain_bombs_text,game.info_color);
ColorCout(game.bombs_stage-game.cur_num_bomb,game.info_color);
gotoxy(loc_info.x,loc_info.y+10);
ColorCout(play0_kill_text,plane[0].color);
ColorCout(plane[0].kill,game.info_color);
gotoxy(loc_info.x,loc_info.y+12);
ColorCout(play1_kill_text,plane[1].color);
ColorCout(plane[1].kill,game.info_color);
gotoxy(loc_info.x,loc_info.y+20);
ColorCout(play0_life_text,plane[0].color);
ColorCout(plane[0].life,game.info_color);
gotoxy(loc_info.x,loc_info.y+22);
ColorCout(play1_life_text,plane[1].color);
ColorCout(plane[1].life,game.info_color);
gotoxy(loc_info.x,loc_info.y+30);
ColorCout(play0_rt_text,plane[0].color);
ColorCout(plane[0].rt,game.info_color);
gotoxy(loc_info.x,loc_info.y+32);
ColorCout(play1_rt_text,plane[1].color);
ColorCout(plane[1].rt,game.info_color);
}
(四)飞机复活保护程序
1、倒计时线程
//********************************************************************************
//以下两个函数为飞机复活时间计时功能
//********************************************************************************
//飞机复活保护时间线程
void* thread_plane_rt(void* arg) //
{
while(1)
{
Sleep(1000);
for(int j=0;j<game.num_plane;j++)
{
if(plane[j].rt>0)
{
plane[j].rt-=1;
}
}
}
}
//创建飞机复活保护时间线程
void plane_rt()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_plane_rt, NULL);
}
2、保护条件
在炮弹位置更新线程中,判断飞机与炮弹相撞之前,先要判断飞机是否存活并且不在保护期内。
if(plane[j].alive&&!plane[j].rt) //判断的必要条件为飞机存活并且已经出了死亡恢复保护期
{
if(collide(plane[j],bomb[i])) //判断飞机是否与炮弹相撞
{
......
}
}
四、漏洞修补
游戏增加了如果两架飞机至少有一架飞机还有命的情况下,飞机在死亡状态必须复活。因为游戏在两架飞机在有命而不复活的情况下还继续进行,而每关设定的炮弹死亡也包括冲出底部边界情况(此种情况称为坠落),那样就可以不复活而让炮弹只通过坠落就可以实现通关了。
//以下一段程序作用:防止两架飞机都死亡之后,且至少有一架飞机还有命的情况下,不按复活键,让游戏在无飞机的情况下过关
if(!plane[(j+1)%2].alive) //(j+1)%2即为除j之外的一架飞机,j=0,(j+1)%2=1;j=1,(j+1)%2=0
{
if(plane[(j+1)%2].life>0) //判断另一架飞机是否还有命,如果有,则将其复活,如果没有,则将自己复活
{
plane[(j+1)%2]=init_plane(plane[(j+1)%2]);
show_plane(plane[(j+1)%2]);
}
else
{
if(plane[j].life>0)
{
plane[j]=init_plane(plane[j]);
show_plane(plane[j]);
}
}
}
五、控制台窗口设置
对控制台窗口的设置都在初始化函数void init(void)中。
(一)大小设置
system("mode con cols=175 lines=46"); //设置控制台屏幕的大小
(二)取消最大化最小化
1、函数调用
SizeGoAway();
2、函数定义
//取消最大化,最小化
void SizeGoAway() {
SetWindowLongPtrA(
GetConsoleWindow(),
GWL_STYLE,
GetWindowLongPtrA(GetConsoleWindow(), GWL_STYLE) & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX);
}
六、最终程序
(一)主函数
#include "control_plane.h"
#include "quenue.h"
#include "graphics.h"
#include <sstream>
//#include "acllib.h"
using namespace std;
Plane plane[eq_plane];
Game game;
Bomb bomb[eq_bombs_round];
Bullet bullet[eq_plane][eq_bullets_round];
Location plocation[]={{2*r_b/3,b_b},{r_b/3,b_b}};
Location loc_info={r_b+15,t_b+5};
int main(int argc, char** argv) {
init(); //初始化
bgmusic();//播放背景音乐
bomb_music();
fire_music();
getmykey();//循环等待键盘指令
bomb_location_update();
bullet_location_update();
plane_rt();
while(1)
{
while(game.pause)
{
;
}
while(game.complete)
{
show_game_complete();
}
while(game.gameover)
{
show_game_over();
}
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");
show_info();
for(int i=0;i<game.num_plane;i++)
{
if(plane[i].alive)
{
show_plane(plane[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]);
}
}
}
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 <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 140 //图形显示区域右侧边界
#define b_b 40 //图形显示区域下侧边界
#define plane_width 9 //飞机宽度
#define plane_height 6 //飞机高度
#define eq_plane 2 //飞机架数
#define eq_bombs_round 32 //eq=end quantity最后一关每波炮弹数量
#define eq_rt 5 //炮弹复活最大时间
#define eq_bullets_round 40 //eq=end quantity飞机每波发射子弹最大数量
//以下为游戏当中要显示的信息文本
#define cur_stage_text "当前关次:"
#define remain_bombs_text "剩余炮弹:"
#define play0_kill_text "飞机0杀敌:"
#define play1_kill_text "飞机1杀敌:"
#define play0_life_text "飞机0命数:"
#define play1_life_text "飞机1命数:"
#define play0_rt_text "飞机0保护时间:"
#define play1_rt_text "飞机1保护时间:"
#define game_complete_text "恭喜,游戏通关!"
#define game_over_text "遗憾,游戏结束!"
//定义飞机造型
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 cur_stage; //游戏当前关
int bombs_round; //当前关敌方每波发射炮弹数量
int bombs_round_increase; //下一关敌方每波发射炮弹数量增加值
int bombs_stage; //当前关总计发射炮弹数量
int bombs_stage_increase; //下一关总计发射炮弹数量增加值
bool clear; //游戏过关
bool complete; //游戏通关
bool gameover; //游戏结束
int num_plane; //飞机数量
int cur_num_bomb; //当前已发射炮弹数量
int bomb_interval; //炮弹位置更新间隔
int bomb_interval_reduce; //下一关炮弹位置更新间隔减少值
int bullet_interval; //子弹位置更新间隔
bool bomb_move; //炮弹是否移动
bool bullet_move; //子弹是否移动
bool pause; //游戏是否暂停
int last_stage; //游戏最后一关数目
int info_color; //游戏信息文本颜色
bool bomb; //是否调用爆炸音效
bool fire; //是否调用开火音效
bool show_gameover; //是否显示游戏结束信息
bool show_complete; //是否显示游戏通关信息
}Game;
//定义飞机结构体
typedef struct{
Location location; //飞机坐标
int color; //飞机颜色
int icon; //飞机图标编号
direction_cmd keycmd; //飞机移动方向命令
bool fire; //飞机是否开火
int cnt_bullets; //按一次开火键加1,然后初始化bullet[cnt_bullets],子弹死亡一个减1
int hp; //飞机血量
bool alive; //飞机存活标志
int No; //飞机序号
int life; //飞机命数
int kill; //飞机击落炮弹数量
int rt; //rt=respawn time复活时间,在此期间炮弹对飞机无伤害
}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];
extern Location plocation[];
extern Location loc_info;
//声明刷新飞机位置函数
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 getmykey();
//输出彩色字符函数
template<typename T> //T表示任何可以被cout输出的类型
void ColorCout(T t, const int ForeColor = 7, const int BackColor = 0);
void gotoxy(int x, int y);
void init_bombs(void);
Bomb init_(Bomb bomb,int stage);
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); //显示子弹图标
bool collide(Plane plane,Bomb bomb);
bool shoot(Bullet bullet,Bomb bomb);
Plane init_plane(Plane plane);
void init_planes(void);
void* thread_bomb_music(void* arg);
void bomb_music();
void play_bomb_music();
void* thread_fire_music(void* arg);
void fire_music();
void play_fire_music();
void show_info(void);
void show_game_complete(void);
void show_game_over(void);
void plane_rt();
void* thread_plane_rt(void* arg);
void SizeGoAway();
#endif
(三)库函数control_plane.cpp
#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 gotoxy(int x, int y) {
COORD pos = { x,y };
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出设备句柄
SetConsoleCursorPosition(hOut, pos);//两个参数分别指定哪个窗口,具体位置
}
//初始化函数
void init(void)
{
plane[0].No=0; //飞机0序号为0
plane[1].No=1; //飞机1序号为1
plane[0].life=3; //飞机0命数为3
plane[1].life=3; //飞机1命数为3
plane[0].kill=0; //飞机0初始杀敌数为0
plane[1].kill=0; //飞机1初始杀敌数为0
srand(time(NULL)); //设置随机函数种子
game.num_plane=2; //飞机数量
game.bombs_round=5; //第一关炮弹数量置5
game.bomb_move=false; //炮弹是否移动标志置否
game.bullet_move=false; //子弹是否移动标志置否
game.bomb_interval=1000; //炮弹初始移动间隔1秒钟
game.cur_stage=1; //游戏初始关设置为1
game.bombs_stage=10; //游戏初始关炮弹总数设置为10
game.pause=false; //游戏暂停设置为否
game.info_color=5; //游戏信息颜色设置为5,紫色
game.cur_num_bomb=0; //游戏当前死亡炮弹数量设置为0
game.bullet_interval=500; //子弹移动间隔设置为1秒钟
game.bomb=false; //爆炸标志设置为否,此标志为爆炸声音播放标志
game.fire=false; //飞机开火标志,此标志为飞机开火声音播放标志
game.gameover=false; //游戏结束标志设置为否
game.complete=false; //游戏通关标志设置为否
game.last_stage=10; //游戏总关数设置为10
game.bombs_round_increase=3;//每过一关敌方每波发射炮弹数量增加3个
game.bombs_stage_increase=10;//每过一关敌方该关总发射炮弹数量增加10个
game.bomb_interval_reduce=100;
game.show_gameover=false; //显示游戏结束信息标志设置为否
game.show_complete=false; //显示游戏通关信息标志设置为否
system("mode con cols=175 lines=46"); //设置控制台屏幕的大小
SizeGoAway(); //取消控制台屏幕的最大化最小化
init_bombs(); //初始化所有炸弹
init_bullets(); //初始化所有子弹
init_planes(); //初始化所有飞机
system("cls");
for(int i=0;i<game.num_plane;i++)//刷新飞机图标
{
show_plane(plane[i]);
}
hide();//隐藏光标
}
//********************************************************************************
//以下三个函数为获得按键指令线程函数
//********************************************************************************
void* thread_key(void* arg)
{
while(1)
{
Sleep(60); //获取指令延时一定时间,起滤波作用,延缓获取指令的响应速度
key(); //获取按键指令
plane_location_update() ;//获取完指令马上更新飞机坐标
}
}
//按键指令线程启动函数
void getmykey()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_key, NULL);
}
//获取键盘指令函数
void key(void)
{
if (GetAsyncKeyState(VK_SPACE) & 0x8000)
{
Sleep(50);
game.pause=!game.pause;
}
while(game.complete)
{
show_game_complete();
}
while(game.gameover)
{
show_game_over();
}
if(!game.pause)
{
if(plane[0].alive)
{
direction_cmd c=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;
plane[0].keycmd=c; //刷新飞机方向指令
if (GetAsyncKeyState('P') & 0x8000) plane[0].fire = true; //飞机开火指令
}
else
{
if(plane[0].life>0)
{
if (GetAsyncKeyState('O') & 0x8000) //飞机复活指令
{
plane[0]=init_plane(plane[0]);
show_plane(plane[0]);
}
}
}
if(plane[1].alive)
{
direction_cmd d=none_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[1].keycmd=d;//刷新飞机图标
if (GetAsyncKeyState('F') & 0x8000) plane[1].fire = true;
}
else
{
if(plane[1].life>0)
{
if (GetAsyncKeyState('Q') & 0x8000)
{
plane[1]=init_plane(plane[1]);
show_plane(plane[1]);
}
}
}
}
}
//飞机图标刷新函数
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减1
if(y<t_b) //限定y值最小值为0
{
y=t_b;
}
break;
case down_cmd:
y++; //字符下移一行,行值y加1
if(y>b_b) //限定y高度
{
y=b_b;
}
break;
case left_cmd:
x--; //字符左移一列,列值x减1
if(x<l_b)
{
x=l_b; //限定x最小值为0;
}
break;
case right_cmd:
x++; //字符右移一列,列值x加1
if(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,int stage)
{
bomb.location.x=rand()%r_b; //x坐标随机
bomb.location.y=b_b+6; //y显示区域底部下一行
bomb.icon=icon_bomb; //设置炮弹图标
bomb.color=6; //设置炮弹颜色为6,黄色
bomb.dam=1+stage/3; //炮弹伤害值每过3关增加1点
bomb.hp=stage; //炮弹血量每过1关增加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],game.cur_stage);
}
}
//炮弹位置更新 线程
void* thread_bomb(void* arg)
{
while(1)
{
Sleep(game.bomb_interval); //更新间隔
game.bomb_move=true; //更新一次炮弹移动一次
while(game.pause) //等待游戏暂停
{
;
}
while(game.complete) //等待游戏通关
{
show_game_complete();
}
while(game.gameover) //等待游戏结束
{
show_game_over();
}
//依次查询炮弹状态、更新位置,查询炮弹是否死亡,记录炮弹死亡数目,计算是否过关,是否通关。
//还查询炮弹是否与飞机相撞,计算飞机血量,死亡状态,飞机命数,游戏是否gameover。
for(int i=0;i<game.bombs_round;i++)
{
if(bomb[i].alive) //如果炮弹存活,则进行移动,如果死亡则进行复活前的计时准备复活
{
bomb[i].location.y++; //炮弹移动一步
if(bomb[i].location.y>b_b+5) //查询炮弹是否冲出底部边界
{
bomb[i].hp=0;
}
//此循环用于查询炮弹是否与飞机相撞
for(int j=0;j<game.num_plane;j++)
{
if(plane[j].alive&&!plane[j].rt) //判断的必要条件为飞机存活并且已经出了死亡恢复保护期
{
if(collide(plane[j],bomb[i])) //判断飞机是否与炮弹相撞
{
game.bomb=true; //爆炸声音启动
bomb[i].hp=0; //炸弹血量置0
plane[j].hp-=bomb[i].dam; //飞机血量减少值为炮弹伤害值
plane[j].kill+=1; //飞机杀敌数加1
if(plane[j].hp<=0) //判断飞机血量是否降到0以下
{
plane[j].alive=false; //飞机死亡
plane[j].life-=1; //飞机命数减1
plane[j].keycmd=none_cmd; //飞机移动指令置空
plane[j].location.x=0; //飞机x坐标置0
plane[j].location.y=b_b+8; //飞机y坐标置底部边界下3行
//以下一段程序作用:防止两架飞机都死亡之后,且至少有一架飞机还有命的情况下,不按复活键,让游戏在无飞机的情况下过关
if(!plane[(j+1)%2].alive) //(j+1)%2即为除j之外的一架飞机,j=0,(j+1)%2=1;j=1,(j+1)%2=0
{
if(plane[(j+1)%2].life>0) //判断另一架飞机是否还有命,如果有,则将其复活,如果没有,则将自己复活
{
plane[(j+1)%2]=init_plane(plane[(j+1)%2]);
show_plane(plane[(j+1)%2]);
}
else
{
if(plane[j].life>0)
{
plane[j]=init_plane(plane[j]);
show_plane(plane[j]);
}
}
}
//以下一段程序作用:判断两架飞机都没有命的情况,即为游戏结束
if(!(plane[0].life>0||plane[1].life>0))
{
game.gameover=true;
}
}
}
}
}
if(bomb[i].hp<=0)
{
bomb[i]=init_bomb(bomb[i],game.cur_stage);
game.cur_num_bomb++;
if(game.cur_num_bomb>=game.bombs_stage)
{
if(game.cur_stage<game.last_stage)
{
game.clear=true;
game.cur_num_bomb=0;
game.bombs_stage+=game.bombs_stage_increase;
game.bombs_round+=game.bombs_round_increase;
game.bomb_interval-=game.bomb_interval_reduce;
game.cur_stage+=1;
init_bombs();
init_bullets();
}
else
{
game.complete=true;
}
}
}
}
else //复活前的倒计时
{
bomb[i].rt--;
if(bomb[i].rt<=0) //倒计时结束
{
bomb[i].alive=true; //复活
bomb[i].location.y=t_b; //将炮弹移动到界面顶部
}
}
}
}
}
//启动炮弹位置更新线程
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;
game.fire=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]);
}
}
}
//单个飞机初始化函数
Plane init_plane(Plane plane)
{
plane.alive=true;
plane.hp=3;
plane.keycmd=none_cmd;
plane.location=plocation[plane.No];
plane.color=plane.No+1;
plane.icon=plane.No+1;
plane.rt=10;
return plane;
}
//所有飞机初始化函数
void init_planes(void)
{
for(int i=0;i<game.num_plane;i++)//刷新飞机图标
{
plane[i]=init_plane(plane[i]);
}
}
//子弹位置更新 线程
void* thread_bullet(void* arg)
{
while(1)
{
while(game.pause) //等待游戏暂停指令
{
;
}
while(game.complete) //等待游戏通关
{
show_game_complete();
}
while(game.gameover) //等待游戏结束
{
show_game_over();
}
Sleep(game.bullet_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; //飞机开火完成后,开火指令置0
}
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<t_b) //子弹冲出顶部边界
{
bullet[i][j].hp=0;
}
//此循环用于查询炮弹是否被子弹击中
for(int k=0;k<game.bombs_round;k++)
{
if(shoot(bullet[i][j],bomb[k])) //如果被击中
{
game.bomb=true; //爆炸声音启动
bomb[k].hp-=bullet[i][j].dam; //炮弹血量减少值为子弹伤害值
bullet[i][j].hp-=bomb[k].dam; //子弹血量减少值为炮弹伤害值
if(bomb[k].hp<=0) //判断炮弹是否被击落
{
plane[i].kill+=1; //记录击落炮弹的飞机,该飞机杀敌数加1
}
}
}
//如果子弹血量值降到0以下,则子弹初始化,等待下次被激活
if(bullet[i][j].hp<=0)
{
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);
}
//********************************************************************************
//以下函数功能:判断炮弹是否被飞机撞击
//********************************************************************************
bool collide(Plane plane,Bomb bomb)
{
bool cpb,cx0,cx1,cy0,cy1;
cx0=bomb.location.x>=plane.location.x-1;
cx1=bomb.location.x<=plane.location.x+plane_width;
cy0=bomb.location.y>=plane.location.y;
cy1=bomb.location.y<=plane.location.y+plane_height;
cpb=cx0 && cx1 && cy0 && cy1;
return cpb;
}
//********************************************************************************
//以下函数功能:判断炮弹是否被子弹击中
//********************************************************************************
bool shoot(Bullet bullet,Bomb bomb)
{
bool sbb,sx0,sx1,sy0,sy1;
sx0=(bomb.location.x>=bullet.location.x-1);
sx1=(bomb.location.x<=bullet.location.x+1);
sy0=(bomb.location.y>=bullet.location.y-1);
sy1=(bomb.location.y<=bullet.location.y+1);
sbb=sx0 &&sx1&&sy0&&sy1;
return sbb;
}
//********************************************************************************
//以下三个函数为播放背景音乐功能
//********************************************************************************
//播放一遍背景音乐
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);
}
//********************************************************************************
//以下三个函数为播放爆炸音效功能
//********************************************************************************
//循环播放爆炸音乐线程函数
void* thread_bomb_music(void* arg) //
{
while(1)
{
if(game.bomb)
{
play_bomb_music();
game.bomb=false;
}
}
}
//创建爆炸音效播放线程,开始循环播放音效
void bomb_music()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_bomb_music, NULL);
}
//播放一遍爆炸音效
void play_bomb_music() {
mciSendString(TEXT("open bomb.mp3 alias s1"),NULL,0,NULL);
mciSendString(TEXT("play s1"),NULL,0,NULL);
Sleep(2*1000);//2*1000意思是2秒,是整首音乐的时长
mciSendString(TEXT("close S1"),NULL,0,NULL);
}
//********************************************************************************
//以下三个函数为播放机开火音效功能
//********************************************************************************
//循环播放飞机开火音效线程函数
void* thread_fire_music(void* arg) //
{
while(1)
{
if(game.fire)
{
play_fire_music();
game.fire=false;
}
}
}
//创建飞机开火音效播放线程,开始循环播放音乐
void fire_music()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_fire_music, NULL);
}
//播放一遍飞机开火音效
void play_fire_music() {
mciSendString(TEXT("open fire.mp3 alias s1"),NULL,0,NULL);
mciSendString(TEXT("play s1"),NULL,0,NULL);
Sleep(1500);//153*1000意思是153秒,是整首音乐的时长
mciSendString(TEXT("close S1"),NULL,0,NULL);
}
//显示游戏信息
void show_info(void)
{
gotoxy(loc_info.x,loc_info.y);
ColorCout(cur_stage_text,game.info_color);
ColorCout(game.cur_stage,game.info_color);
gotoxy(loc_info.x,loc_info.y+2);
ColorCout(remain_bombs_text,game.info_color);
ColorCout(game.bombs_stage-game.cur_num_bomb,game.info_color);
gotoxy(loc_info.x,loc_info.y+10);
ColorCout(play0_kill_text,plane[0].color);
ColorCout(plane[0].kill,game.info_color);
gotoxy(loc_info.x,loc_info.y+12);
ColorCout(play1_kill_text,plane[1].color);
ColorCout(plane[1].kill,game.info_color);
gotoxy(loc_info.x,loc_info.y+20);
ColorCout(play0_life_text,plane[0].color);
ColorCout(plane[0].life,game.info_color);
gotoxy(loc_info.x,loc_info.y+22);
ColorCout(play1_life_text,plane[1].color);
ColorCout(plane[1].life,game.info_color);
gotoxy(loc_info.x,loc_info.y+30);
ColorCout(play0_rt_text,plane[0].color);
ColorCout(plane[0].rt,game.info_color);
gotoxy(loc_info.x,loc_info.y+32);
ColorCout(play1_rt_text,plane[1].color);
ColorCout(plane[1].rt,game.info_color);
}
//显示游戏通关信息
void show_game_complete(void)
{
if(!game.show_complete)
{
game.show_complete=true;
Sleep(100);
system("cls");
show_info();
gotoxy((l_b+r_b)/2-4,(t_b+b_b)/2);
ColorCout(game_complete_text,game.info_color);
}
}
//显示游戏结束信息
void show_game_over(void)
{
if(!game.show_gameover)
{
game.show_gameover=true;
Sleep(100);
system("cls");
show_info();
gotoxy((l_b+r_b)/2-4,(t_b+b_b)/2);
ColorCout(game_over_text,game.info_color);
}
}
//********************************************************************************
//以下两个函数为飞机复活时间计时功能
//********************************************************************************
//飞机复活保护时间线程
void* thread_plane_rt(void* arg) //
{
while(1)
{
Sleep(1000);
for(int j=0;j<game.num_plane;j++)
{
if(plane[j].rt>0)
{
plane[j].rt-=1;
}
}
}
}
//创建飞机复活保护时间线程
void plane_rt()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_plane_rt, NULL);
}
//取消最大化,最小化
void SizeGoAway() {
SetWindowLongPtrA(
GetConsoleWindow(),
GWL_STYLE,
GetWindowLongPtrA(GetConsoleWindow(), GWL_STYLE) & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX);
}
七、运行效果
八、不足之处
本系列博文到此告一段落。本程序对于专业做游戏的高手来说,都是雕虫小技,本人只是初学c++,随手做了一个小游戏,权当练习了。本游戏中,定有好多不足之处,欢迎给位大神随时指正。本程序最大的不足之处就是由于屏幕刷新引起的闪烁情况。经查阅资料,屏幕刷新引起的闪烁问题可以通过双缓存方式予以解决,本人已经测试过,这种方法只适用于字符是系统默认的白色,如果是彩色字符则无能为力。所以后续还需要继续探索其他方法,如有大神知道解决方法,也敬请赐教。