[导读]本系列博文内容链接如下:
【C++】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值
【C++】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动
【C++】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动【C++】做一个飞机空战小游戏(四)——给游戏添加背景音乐(多线程技巧应用)
【C++】做一个飞机空战小游戏(五)——getch()控制两个飞机图标移动(控制光标位置)
【C++】做一个飞机空战小游戏(六)——给两架飞机设置不同颜色(cout输出彩色字符、结构体使用技巧)
在第五节【C++】做一个飞机空战小游戏(五)——getch()控制两个飞机图标移动(控制光标位置)中实现了通过键盘控制两架飞机的效果,但是两架飞机颜色造型一样,很难区分,所以需要给两架飞机选用不同的颜色、不同造型,这样就好辨别了。
本节之前飞机只有位置、造型两个属性,所以可以用独立的变量来声明即可。随着程序设计的深入,飞机的属性越来越多,就需要构造飞机的结构体,把各项属性都放到飞机结构体中。这样,可以减少声明变量的个数,减少自定义函数的形参。使程序的逻辑性更清晰,代码量更少。
目录
一、结构体
(一)定义
(二)使用结构体需要注意几点
1、结构体是一种数据类型
2、结构体需要先构造后使用
3、结构体的成员也可以是结构体类型数据
4、结构体是可以扩展的
(三)参考博文
二、本文用到的结构体
(一)位置坐标结构体
1、构造形式
2、功能
3、用途
(二)飞机结构体
1、构造形式
2、功能
3、用途
三、优化程序
(一)声明数据类型
1、声明全局结构体数组
2、优化按键指令存储枚举变量
(二)已有自定义函数的优化
1、形参数量减少
2、返回型函数类型减少
(三)新增自定义函数
1、新增获取按键键值线程函数
2、新增彩色字符输出函数
3、新增隐藏光标函数
(四)优化后的程序代码
1、主函数
2、头文件control_plane.h
3、库函数control_plane.cpp
五、运行效果
一、结构体
(一)定义
结构体就是一个可以包含不同数据类型的一个结构,它是一种可以自己定义的数据类型,它的特点和数组主要有两点不同,首先结构体可以在一个结构中声明不同的数据类型。第二,相同结构的结构体变量是可以相互赋值的,而数组是做不到的,因为数组是单一数据类型的数据集合,它本身不是数据类型(而结构体是),数组名称是常量指针,所以不可以做为左值进行运算,所以数组之间就不能通过数组名称相互复制了,即使数据类型和数组大小完全相同。
(二)使用结构体需要注意几点
1、结构体是一种数据类型
结构体就是把若干种数据类型的放到一起,构造了一个新的数据类型。所以关于结构体变量的操作和其他的数据类型有好多相似的地方。
声明变量的过程实际就是到内存中划分一片存储空间给这个变量使用,变量名就是这片空间的标记符号,不同的数据类型所占空间不同,所以声明变量的时候需要明确数据类型。结构体变量的声明后,其获得的空间就是结构体内所有成员所需的空间。
2、结构体需要先构造后使用
结构体是用户自己根据需求定制的一种数据类型,不是系统就有的数据类型,所以必须先构造一个结构体数据类型,然后再用这个结构体数据类型声明变量,再对变量进行其他的操作。
3、结构体的成员也可以是结构体类型数据
也就是结构体是可以嵌套的,还可以多层嵌套。
4、结构体是可以扩展的
结构体构造完成后,还可以进行增加、减少或者改变成员。
(三)参考博文
关于结构体基础知识介绍的博文有很多,本文不在详细介绍,具体内容可以参考以下博文:
c语言基础语法六——结构体(完结)_卖酒的小码农的博客-CSDN博客
二、本文用到的结构体
(一)位置坐标结构体
1、构造形式
//定义坐标结构体
typedef struct{
int x;
int y;
} Location;
2、功能
这个结构体数据类型的功能是用来存放屏幕上点的坐标。Location为结构体类型名称,其有两个整形的成员:x,y,分别代表x坐标和y坐标。
3、用途
可以用来存储飞机、炮弹、和其他字符信息要显示的位置。
(二)飞机结构体
1、构造形式
//定义飞机结构体
typedef struct{
Location location;
int color;
int icon;
direction_cmd keycmd;
}Plane;
2、功能
这个结构体数据类型的功能是用来存储飞机属性信息的。Plane为结构体类型名称,其有1个Location类型的成员location,用来存储飞机坐标;1个int型的成员color,用来存储飞机的颜色;1个int型的成员icon,用来存储飞机的造型;1个direction_cmd形的成员keycmd,用来存储飞机接收到的键盘指令。
注意:direction_cmd是本例定义的一个枚举型的数据类型,其内部暂时存储了飞机所能接受的"上、下、左、右"和“无”4个键盘指令名称。
typedef enum {none_cmd,up_cmd,down_cmd,left_cmd,right_cmd} direction_cmd;
3、用途
这个结构体的用途就是声明飞机,声明后可以通过改变其成员(属性)的值,定制出风格不同的飞机。还可以在程序里根据属性值的不同做出不同的反应,呈现不同的状态,具有不同的信息,做出不同的动作。
三、优化程序
声明了飞机结构体之后,位置信息、按键指令信息、飞机图标信息、颜色信息都在结构体之内了,以上4个信息都不用单独声明了,声明一个结构体就都包含了。另外,两个飞机就可以用一个结构体数组来表示,飞机的序号可以用数组的角标区分。
(一)声明数据类型
1、声明全局结构体数组
声明全局Plane plane[num_plane],用于存放和传递各种信息。采用数组方式,给赋值带了很大便利,减少代码量。
#define num_plane 2 //飞机架数
extern Plane plane[num_plane];
2、优化按键指令存储枚举变量
typedef enum {none_cmd,up_cmd,down_cmd,left_cmd,right_cmd} direction_cmd;
枚举变量里增加“none_cmd”选项,方向指令只保留一组,不再区分是哪个飞机的指令,使指令更加通用。
(二)已有自定义函数的优化
1、形参数量减少
目前形参只有Plane一个结构体类型了。
//声明刷新飞机位置函数
void show_plane(Plane plane);
//获取键盘指令
void key(void);
//更新每架飞机的坐标
void plane_location_update(void);
//初始化函数(设置飞机的颜色、形状)
void init(void);
2、返回型函数类型减少
因为函数运行后的结果都存在全局Plane结构体中了,所以函数不需要各种返回结果。
(三)新增自定义函数
1、新增获取按键键值线程函数
之前的按键获取函数在主线程中,按键按下后再执行位置更新和画面更新,如果没有按键按下,主程序就一直处于等待按键按下的状态,后边的程序都执行不了。游戏后边需要增加炮弹的功能,需要一直更新炮弹的位置,所以需要把按键程序单放到一个线程里。主线程里只保留位置更新和画面更新功能。
//定义获取按键线程函数
void* thread_key(void* arg);
void getkey();
2、新增彩色字符输出函数
//输出有颜色字符函数
template<typename T> //T表示任何可以被cout输出的类型
void ColorCout(T t, const int ForeColor = 7, const int BackColor = 0);
3、新增隐藏光标函数
//隐藏光标函数
HANDLE han = GetStdHandle(-11);
void hide(){
CONSOLE_CURSOR_INFO cursor;
cursor.bVisible = 0;
cursor.dwSize = 1;
SetConsoleCursorInfo(han,&cursor);
}
隐藏光标函数,可以不再界面上不显示光标所在位置,更新画面时不会出现光标,减少闪烁感。
(四)优化后的程序代码
1、主函数
#include "control_plane.h"
using namespace std;
Plane plane[num_plane];
int main(int argc, char** argv) {
init(); //初始化
bgmusic();//播放背景音乐
getkey();
while(1) //循环等待键盘指令
{
for(int i=0;i<num_plane;i++)
{
show_plane(plane[i]); //刷新飞机1图标
}
if(plane[0].keycmd!=none_cmd ||plane[1].keycmd!=none_cmd)
{
system("cls");
}
plane_location_update();
}
return 0;
}
2、头文件control_plane.h
#ifndef CONTROL_PLANE_H
#define CONTROL_PLANE
#include <iostream>
#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 num_plane 2 //飞机架数
//定义飞机造型
const string icon_plane1[]={" ■","■ ■ ■","■■■■■","■ ■ ■"," ■"," ■■■"};
const string icon_plane2[]={" ■","■ ■ ■","■■■■■"," ■"," ■■■","■■■■■"};
//定义坐标结构体
typedef struct{
int x;
int y;
} Location;
//定义移动方向命令枚举类型
typedef enum {none_cmd,up_cmd,down_cmd,left_cmd,right_cmd} direction_cmd;
//定义飞机结构体
typedef struct{
Location location;
int color;
int icon;
direction_cmd keycmd;
}Plane;
extern Plane plane[num_plane];
//声明刷新飞机位置函数
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);
#endif
3、库函数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;
for(int i=0;i<num_plane;i++)
{
system("cls");
show_plane(plane[i]); //刷新飞机图标
plane[i].keycmd=none_cmd;
}
hide();//隐藏光标
}
//********************************************************************************
//以下三个函数为获得按键指令线程函数
//********************************************************************************
void* thread_key(void* arg)
{
while(1)
{
key(); //获取按键指令
}
}
void getkey()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_key, NULL);
}
//获取键盘指令函数
void key(void)
{
int key_value1,key_value2; //声明两个变量,存放键值
key_value1=getch(); //先获取第一个码值
if(key_value1==224) //如果第一个码值为224,则进行第二个码值的判断
{
key_value2=getch(); //先获取第二个码值
switch(key_value2)
{
case 72: //向上方向键
plane[0].keycmd=up_cmd;
break;
case 80: //向下方向键
plane[0].keycmd=down_cmd;
break;
case 75: //向左方向键
plane[0].keycmd=left_cmd;
break;
case 77: //向右方向键
plane[0].keycmd=right_cmd;
break;
}
}
else
{
switch(key_value1)
{
case 119: //向上方向键
plane[1].keycmd=up_cmd;
break;
case 115: //向下方向键
plane[1].keycmd=down_cmd;
break;
case 97: //向左方向键
plane[1].keycmd=left_cmd;
break;
case 100: //向右方向键
plane[1].keycmd=right_cmd;
break;
}
}
}
//********************************************************************************
//以下三个函数为播放背景音乐功能
//********************************************************************************
//播放一遍背景音乐
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 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减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;
}
}
}
五、运行效果
(未完待续)