基于C语言实现的飞机大战小游戏【100011221】

一、设计目的与意义

总所周知,C++由来写游戏是再合适不过了,因为其面向对象的特性使得游戏设计起来更加简单方便快捷,维护起来更加轻松容易,其继承多态的属性也使得代码可扩展性与可复用性大大增强。通过写游戏代码可以加强巩固面向对象知识。我创作此游戏的原因有二,其一,为了加深对面向对象技术的掌握,深入理解其思想。 其二,为了圆自己一个小小的梦想。

二、游戏简介

本游戏为飞机大战游戏,控制台类型游戏,采用C++图形界面库graphics.h来实现界面功能。由于并非采用win32或MFC来实现游戏界面显示以及事件处理功能,所以玩起来有些卡顿,但却另有一番风趣。本游戏为通关类型游戏,玩家可以自己选择出战飞机,以键盘的方向键来控制飞机上下左右移动,飞机会自行发射子弹,无需玩家控制。敌机按照预先设定好的指令进行运动,每架飞机拥有自己的血量,受伤血量减少,当血量减少到0时,飞机被击毁。只有不被敌机击毁并打败最终BOSS就可以赢得游戏(目前只有一关,设定运行轨迹需要精力时间)。用来模拟小时候所玩的飞机大战,回忆童年再合适不过了。

2.1 游戏分析与设计

2.1.1 结构体设计

在计算机里,所有运动的物体都是通过一张张图形图像不断的显示刷新来做到的,而所有图形图像都是由一个一个的像素点组成的。

本游戏中,所有的物体(包括飞机,子弹,背景图等)都本质都是一个矩形。在屏幕上,要想表示一个矩形得知道它的坐标以及长宽。对于一个矩形,坐标我用结构体Point来表示,长宽用结构体Size来表示。

typedef struct point
{
int x;	//x轴坐标
int y;	//y轴坐标
}Point;
typedef struct size
{
int width;	//宽
int height;	//高
}Size;

控制敌机移动轨迹的指令结构体

typedef struct order	//指令
{
direction dir;	//方向枚举
int num;	//指令执行步数
}Order;

2.1.2 枚举设计

为了更好的表示一架飞机类型,子弹类型,方向类型。特此设计了一批枚举类型

enum bulletKind { SPOT, ADD, STAR, CHARO, LINE, DOUBLESTAR };	//子弹类型
enum myPlaneKind { myPlane1, myPlane2, myPlane3};	//我机类型
enum enemyPlaneKind { enemyPlane1, enemyPlane2, enemyPlane3, boosPlane };	//敌机类型
enum direction { UP = 72, LEFT = 75, RIGHT = 77, DOWM = 80, STOP = 0 };	 //方向

2.1.3 飞机子弹类设计

类是面向对象最基本的东西,如何设计类,是本游戏的重中之重。

2.1.3 PointSize类

单单一矩形是无法表示一个物体的,还得附上一些属性及方法。为此设计一个最基本的基类是必须的,PontSize类因此而生,它是本游戏飞机子弹类的基类。

class PointSize
{
public:
PointSize() = default;
PointSize(const Point& point, const Size& si)	//构造函数
:size(si), pos(point), exists(true) {}
Size getSize() const { return size; }	//得到大小
Point getPoint() const { return pos; }	//得到位置
bool isExists() const { return exists; }	//判断是否存在
void Destroyed() { exists = false; }	//销毁
virtual void move(int key, int speed);	//移动
protected:
bool exists;	//存在与否
Point pos;	//位置
Size size;	//大小
};

它有一个虚函数move,用来改变位置,参数key是方向枚举,speed是速度(即一个单位时间的运动位移)。

基本的属性与方法设计好后,便可以设计之后的类。

2.1.3 Bullet类(子弹基类)

Bullet类继承于PointSize类。子弹类拥有攻击力、速度、自身类型属性。

class Bullet: public PointSize	//子弹
{
public:
Bullet(const Point& point, const Size& size, int da, int spe, bulletKind k) :
PointSize(point, size), damage(da), speed(spe), kind(k) {}	//构造函数
int getDamage() const { return damage; }	//得到攻击力
int getSpeed() const { return speed; }	//得到速度
bulletKind getKind() const { return kind; }	//得到子弹类型
void move(int dir) { PointSize::move(dir, speed); }	//重写基类方法
protected:
int damage;	//子弹攻击力
int speed;	//子弹速度
bulletKind kind;	//子弹类型
};

成员kind是判断某子弹对象所属于的子弹类型,便于刻画其特征。

2.1.3 Airplane类(飞机基类)

每架飞机都拥有子弹夹、血量、移动速度、射击时间等属性。

class Airplane:public PointSize
{
public: 
Airplane() = default;
Airplane(int bl, int sp, bulletKind bk, int time, const Point& point, const Size& size, LPCTSTR file)

: blood(bl), speed(sp),bulletkind(bk),

shootIntervalTime(time),times(0),  PointSize(point,size),hited(false),boomNum(10)
{ 
if (file != NULL) 
loadimage(&img,file, size.width, size.height);
//图形库API,用来加载图片
}
int getBlood() const { return blood; }	//得到剩余血量 
IMAGE& getImage() { return img; }	//得到图像的引用
int getBulletsNum() const { return bulletsClip.size(); }	//得到子弹数量
Bullet* getBullet(int i) const {
if (i >= bulletsClip.size()) return NULL;
return bulletsClip[i];
}	//得到第i颗子弹的引用
int getIntervalTime() const { return shootIntervalTime; }	//得到射击间隔时间
int getTimes() const { return times; } //得到距离射击时间
int getBoomTimes() const { return boomTimes; }	//得到爆炸秒数
void hurted(int num) { blood -= num; }	//受伤
void move(int dir) { PointSize::move(dir, speed); }	//移动
virtual void bulletsMove(int dir);	//子弹移动
virtual void shooting();	//射击
void reduceBoomTimes() { boomTimes--; }	//减少爆炸秒数
void addTimes() { times++; }	//增加距离射击时间
bool isHited() { return hited; }	//是否被击中
void setHited(bool is) { hited = is; }	//射击被击中状态
void timesToZero() { times = 0; }	//射击时间归0
void clear();	//清楚所有子弹,释放内存
virtual ~Airplane() { clear(); }
protected:
std::vector<Bullet*> bulletsClip;	//子弹夹
bulletKind bulletkind;	//子弹夹里的子弹类型
IMAGE img;	//一个由图像库提供的结构体,用来显示飞机图片
int blood;	//剩余血量
int speed;	//移动速度
int shootIntervalTime;	//射击间隔时间
int times;	//离本次射击时间
bool hited;	//被击中
int boomTimes;	//爆炸秒数
};

成员bulletsClip 采用容器vector来保存每颗子弹的指针,每颗子弹都是动态分配的,clear函数和析构函数用来释放这些资源。

射击子弹即是动态申请一个子弹对象,把其指针添加到子弹夹中。

2.1.3 MyAirplane类(我方飞机类)

主要是重写基类方法,还能添加属性(暂未添加)。

class MyAirplane :public Airplane
{
public:
MyAirplane() = default;

MyAirplane(int bl, int sp, bulletKind bk,int time, const Point& point,const Size& size, LPCTSTR file) : Airplane(bl, sp, bk,time, point, size, file) {}

void move(int key);	//重写基类方法
void bulletsMove(int dir = UP);	//重写基类方法,默认向上移动
void shooting();	//重写基类方法
};

2.1.3 EnemyAirplane类(敌方飞机类)

增加了指令相关的成员与方法,达到敌机能够按照预先设计好的轨迹行动,还能添加属性(暂未添加)。

class EnemyAirplane : public Airplane
{
public:
EnemyAirplane() = default;
EnemyAirplane(int bl, int sp, bulletKind bk, int time, const Point& point,const Size& size,
LPCTSTR file) :Airplane(bl, sp, bk,time, point, size, file),index(0), iExecuteTimes(0) {}
void shooting();	//重写基类方法
int getIndex() const { return index; }	//得到当前指令在指令集的下标
int getIExeTimes() const { return iExecuteTimes; }//得到当前指令以执行步数
int getOrderNum() const { return orders.size(); }	//得到指令集数
Order getOrder(int i) const {	//得到第i条指令
if (i < orders.size()) {
return orders[i];
}
}
void addOrder(direction dir, int num) {	//添加指令
orders.push_back({ dir, num });
}
void addIndex() { index++; }	//将下标移动下一条指令
void addIExeTimes() { iExecuteTimes++; }	//增加当前指令执行步数
void ExeTimesToZero() { iExecuteTimes = 0; }	//将指令执行步数清零
private:
std::vector<Order> orders;	//指令集
int index;	//执行第i条指令
int iExecuteTimes;	//第i条指令以执行数
};

2.1.3 具体飞机,子弹类

每种飞机、子弹对应都有具体的类

class PointBullet : public Bullet	//点型子弹
{
public:
PointBullet(Point pos = { 0,0 }) :
Bullet(pos, { 2,2 }, 5, 8, SPOT) {}
};
class AddBullet :public Bullet	//+型子弹
{
public:
AddBullet(Point pos = { 0,0 }) :
Bullet(pos, { 4,4 }, 10, 6, ADD) {}
};
class StarBullet :public Bullet	//星型子弹
{
public:
StarBullet(Point pos = { 0,0 }) :
Bullet(pos, { 6,6 }, 15, 6, STAR) {}
};
class DoubleStarBullet :public Bullet	//双星型子弹
{
public:
DoubleStarBullet(Point pos = { 0,0 }) :
Bullet(pos, { 18,6 }, 30, 8, STAR) {}
};
class MyAirplane1 : public MyAirplane	//我方飞机1号
{
public:
MyAirplane1(Point pos = { 0,0 })
: MyAirplane(200, 12, ADD, 12, pos, { 50,50 }, 
TEXT("image\\myPlane1.PNG")) {}
};
class MyAirplane2 : public MyAirplane //我方飞机2号
{
public:
MyAirplane2(Point pos = { 0,0 })
: MyAirplane(250, 6, STAR, 12, pos, { 50,50 },
TEXT("image\\myPlane2.PNG")) {}
};
class MyAirplane3 : public MyAirplane //我方飞机3号
{
public:
MyAirplane3(Point pos = { 0,0 })
: MyAirplane(300, 6, DOUBLESTAR, 12, pos, { 50,50 },
TEXT("image\\myPlane3.PNG")) {}
};
class EnemyAirplane1 : public EnemyAirplane //敌方飞机1号
{
public:
EnemyAirplane1(Point pos = { 0,0 })
: EnemyAirplane(30, 2, SPOT,160, pos, { 50,50 }, 
TEXT("image\\enemyPlane1.PNG")) {}
};
class EnemyAirplane2 : public EnemyAirplane	//敌方飞机2号
{
public:
EnemyAirplane2(Point pos = { 0,0 })
: EnemyAirplane(50, 2, ADD, 60, pos, { 50,50 },
TEXT("image\\enemyPlane2.PNG")) {}
};
class EnemyAirplane3 : public EnemyAirplane	//敌方飞机3号
{
public:
EnemyAirplane3(Point pos = { 0,0 })
: EnemyAirplane(80, 2, STAR, 60, pos, { 50,50 },
TEXT("image\\enemyPlane3.PNG")) {}
};
class BoosAirplane : public EnemyAirplane //BOSS
{
public:
BoosAirplane(Point pos = { 0,0 })
: EnemyAirplane(800, 2, DOUBLESTAR, 120, pos, { 150,150 },
TEXT("image\\boosPlane.PNG")) {}
};

2.1.4 地图玩家敌人类设计

有了飞机和子弹后,剩下的就是如何管理这些飞机子弹以及设计游戏地图与规则,于是引入地图玩家敌人类。

2.1.4 Map类(地图类)

为了使游戏更加生动,加入地图背景是必要的,但一张静态的背景图是没啥用的。于是引入地图类,让背景图自己动起来。背景图向下运动,使得飞机看上去有种向前飞的感觉。

class Map
{
public:
Map(int x = 0, int y = 0) : posX(x), posY(y) {};
void init(LPCTSTR mapFile, int w, int h);
void setPos(int x, int y) {	//设置坐标
posX = x;
posY = y;
}
int getPosY() const { return posY; }	//得到y轴坐标
void move() {	//向下移动
posY += speed;
}
void drawMap() {	//显示图片
putimage(posX, posY, &img);
}
private:
IMAGE img;	//存储图片
int posX;	//图片y轴坐标
int posY;	//图片y轴坐标
public:
const static int speed;	//运动速度
const static int mapWidth;	//图片宽度
const static int mapHeight;	//图片高度
};

2.1.4 Player类(玩家类)

用来操作飞机

class Player
{
public:
Player(int kind = myPlane1, Point pos = { 0,0 }) {
pMe = getMyAirplane(kind, pos);
pme = pMe;
}
void controlPlane();	//控制我方飞机
void draw();	//显示飞机和子弹
void shoot();	//发射子弹
MyAirplane& getPlane() { return *pMe; }
void clear() {	}//释放资源
~Player() { clear(); }
private:
MyAirplane* pMe;	//指向我方飞机的指针
};

2.1.4 Enemy类(敌人类)

用来统一管理敌方的资源

class Enemy
{
public:
Enemy(int num = 0) : planeNum(num) { planes.clear(); }
void init(LPCTSTR planeFile);	//初始化敌机
void move();	//敌机的移动
void draw();	//敌机的显示
void shooted();	//敌机被我方机射击情况
void shootMe();	//敌机射击我方机情况
void clear();	//释放资源
bool isShow();	//敌机是否出现
int getEnemyNum() const { return planeNum; }	//得到敌机存活数量
~Enemy() { clear();  }
private:
std::vector<EnemyAirplane*> planes;	//敌机数组
int planeNum;	//敌机存活数量
};

成员planes 保存所有的敌机。

2.1.5 普通函数

除了类方法外。还设计出普通函数来实现一些操作。其中一个很重要的函数void beginPlay();用来控制地图的移动刷新,关卡的刷新。

动态申请飞机、子弹对象并返回指针的函数

MyAirplane* getMyAirplane(int kind, Point pos = { 0,0 });	//获得我方飞机
EnemyAirplane* getEnemyAirplane(int kind, Point pos = { 0,0 });//获得敌方飞机
Bullet* getBullet(int kind, Point pos = { 0,0 });	//获得子弹

参数kind表示飞机或子弹类型,pos表示生成时的坐标。

飞机、子弹互相碰撞的检测。

bool impactText(const PointSize& lhs, const PointSize& rhs);	//碰撞检测

各种绘画显示函数

void drawBullet(Bullet& bullet);	//刻画子弹
void drawAirplane(Airplane& plane);	//刻画飞机
void drawRect(const Point& pos,const Size& size);	//刻画矩形
void drawBoom(const Point& pos, const Size& size);	//刻画爆炸

还有游戏开始界面选择函数

int gameBeginGUI();	//游戏开始界面
bool choose(int key, POINT& pos, int up, int bottom, int dis);	//选择选项
int choosePlane(int op = 1);	//选择出战飞机
void showPlane(IMAGE* pimg, int n);	//显示所有我方飞机

2.2 图片

开始界面

选择飞机界面

打飞机中

打BOOS中

2.3 设计特色

2.3.1 多线程 :

控制台不像MFC和win32那样本身具备多线程和消息事件功能。对本游戏来说,无论是敌机、我机、子弹还是地图的运动都是通过不断刷新来实现的。如果只有一个主线程,图片刷新帧数为30毫秒一次,玩家按下键盘到飞机真实运动开始之间至少需要30毫秒再加上需要处理很多东西,这便会导致延迟,所以玩家控制的飞机运动将会十分卡顿,体验感极差。但加入多线程的话,情况会有大有改善。地图,敌机,子弹的刷新归一个线程管理,而玩家操作飞机归另一个线程管理。飞机运动起来便会顺畅很多(依旧会卡顿)。

2.3.2 指令控制敌机运动:

飞机运动问题一直困扰着我很久,在计算机组成原理这么课程上,我获得灵感。既然计算机是由指令和数据组成这个,那么运动轨迹不也可以由指令控制吗,运动指令分为两部分一是方向二是运动时间,如上面的Order结构体所示。把指令提前写好在文件中,等玩家到达某一关卡时,将指令从文件中读取出来放到敌机对象的指令集成员中,敌机按照指令进行运动。

2.3.3 易扩展性

敌机、我机、子弹类已经设计好,代码中的飞机、子弹对象都是这些类的实例的指针或引用,要想增加一种物品,只需要在添加一个子类即可。这是多态性的好处。

三、总结与体会

一开始我是想用MFC来重写实现去年的大作业的(也是游戏,但是是控制台文字游戏),但发现自己根本不会这门技术,虽然暑假有学过一些,但终究只是皮毛。在天大的困难面前我还是放弃了。MFC写一下管理系统我到还行,游戏什么我用MFC根本不可能, 但我又不想写什么管理系统的。于是我将目光转向win32,毕竟很多游戏可以用它来实现,可我又不会,又没时间学。犹豫徘徊很久之后决定使用C++一个轻量级图形界面库Graphics.h来实现这个游戏。

刚开始动手时,脑袋乱七八糟的,有种无从下手的感觉。要从哪里开始做起、做什么、怎么做统统不知道。冷静一阵子后,先从构造类开始。飞机、子弹、图片等等所有游戏中出现的物体该如何用类来表示,他们拥有什么属性、什么方法、各类之间关系如何我也不知从何下手。多态性、封装性、复用性等等又该如何体现,也不知道。从一开始的全局乱构乱造,到局部改善,到慢慢发现并使用更好的方法,再到全局整体代码提升的道路上,渐渐找到道路方向,有了整体的布局,一切便清晰了。

代码不算得很长,比去年的算是简洁多了。毕竟去年的是纯控制台、功能太多。吸取去年的教训,这次不做那么复杂(虽然脑袋中有很多想法),毕竟功能越多付出时间精力也就越多,但乐趣却是去年无法比拟的,无论是在敲代码中、测试中都会让我有种成就感,因为这种有图像界面的我是第一次系统做,看着那些运动的东西,会觉得一些新奇(知道的多了,未知与神秘也会随之增多)。其实还有很多更加有趣的功能可以添加,因为时间关系,这个游戏也许就到此为止了吧。

♻️ 资源

在这里插入图片描述

大小: 22.8MB
➡️ 资源下载:https://download.csdn.net/download/s1t16/87553498
注:如当前文章或代码侵犯了您的权益,请私信作者删除!

猜你喜欢

转载自blog.csdn.net/s1t16/article/details/131488623
今日推荐