???[導讀]本系列博文內容鏈接如下:
【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].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);
}
四、更新后的程序代碼
(一)主函數
#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 <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 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;//定義移動方向命令枚舉類型
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; //炮彈是否移動
}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<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);#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 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(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;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);
}
(未完待續)