游戏开发 | 基于 EasyX 库开发经典90坦克大战游戏

知识共享许可协议 版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons


写在前面

昨天晚上突发奇想,想设计一款小游戏,自己设计就算了(O(∩_∩)O),还是来款经典的,在网上找到了奇牛学院 Martin 老师的视频课程,做了一晚上,基本的开发现在已经完成了。游戏开发与普通的程序开发设计还是有一些区别的,因为它的算法设计和实现更多样化,本篇博文就详细此款游戏的实现思路与算法设计,为自己做个记录~


安装EasyX图形库

EasyX 是针对 C++ 的图形库,可以帮助我们快速上手图形和游戏编程,官网安装即可:https://easyx.cn/

现在最新版本已经适配到 VS2019,注意安装时要把帮助手册安装上,这样便于我们快速查阅EasyX为我们提供的各种库函数接口。

本项目就是基于EasyX库开发90坦克大战游戏。


项目需求与概要设计

项目需求

实现一款和经典的《90坦克大战》一样的游戏,任务是消灭敌对坦克,保护己方领地。防止敌方打破我方围墙而把Boss鹰打败。

下面是此款游戏的实现界面:

下面是成功的情景:己方坦克消除了出现在地图上的所有敌对坦克(不一定和敌对坦克的总量相同,有可能下台坦克还没有出场,地图中的所有坦克就被全部消灭)
在这里插入图片描述

下面是失败的情形之一:己方坦克被敌对坦克击中(还有一种是Boss被击中,之后的讲解中会演示这种情景)
在这里插入图片描述

概要设计

整体概要设计图如下:
在这里插入图片描述


游戏初始化与开始界面设计

游戏初始化部分,主要是在游戏进入的菜单界面设计。如下图所示:
在这里插入图片描述
游戏的初始化主要完成以下几个模块:

  • 画布:绘图环境
  • Logo:美工图片,游戏标志
  • 按钮:实现“说明”和“开始”导航
  • 说明: 美工图片,操作说明

首先我们需要进行画布的大小设计,在这里我们采用650 * 650像素的大小,注意这个画布大小非常重要,因为我们之后的地图设计和游戏算法实现,需要计算像素点位置。

然后我们需要添加我们的logo图片,还要绘制两个矩形框,里边是说明和启动游戏,可以直接使用EasyX 库为我们提供的一系列绘图函数即可:

上面的坐标参数都是经过详细的测试得出的,是适用于画布大小的。注意不同的画布大小,每个元素的坐标参数都应该进行测试,最终得到合适的元素位置。

画出整体界面之后,我们便可以定义鼠标事件了,EasyX 库也为我们提供了一些捕获鼠标事件的函数,我们主要用到了下面的函数:

// 这个函数用于获取一个鼠标消息。如果当前鼠标消息队列中没有,就一直等待。
MOUSEMSG GetMouseMsg();

上述的MOUSEMSG结构体这用于保存鼠标消息,因此我们在程序中必须定义它,
下图是这个结构体的uMsg成员的取值:

我们主要用到了鼠标移动事件、鼠标左键点击事件:


游戏地图设计与初始化

上述搞定了之后,点击“开始”按钮,我们便进入了游戏界面,如下图:

那么首先分析,不考虑坦克,我们的地图有以下几个元素:

  • 可消除的墙
  • 不可消除的墙
  • 己方领地、Boss

这些元素都是以矢量图的形式存在的,那么我们如何将它们拼接在一起呢?我们可以继续使用我们绘制画布时提到的像素概念,我们将整个画布分为26*26的像素单位,如下图所示:

如上图,我们将整个地图(画布)分为了很多小的元素块,因此我们便可以进行地图的绘制了,建立坐标系,计算每一个小元素块的坐标位置,我们只需计算有元素的地方,比如可消除的墙、不可消除的墙、己方领地、boss鹰等。

我们采用了二维数组的方式,以数字0表示空地,数字1表示可消除的墙,数字2表示可消除的墙,数字3表示Boss位置。

那么对于地图的绘制就可以完全按照地图数组中定义的元素位置来进行了,根据地图数组中的不同值输出不同的元素图片即可。

注意,我们的Boss占了4个元素块,这对于我们之后的炮弹碰撞检测是很重要的条件限制。


己方坦克初始化

地图绘制好后,就该是我方坦克出场了,如下图:

它的位置在己方领地左侧,这个很好实现,我们只需计算出它在地图画布中的位置,然后将其输出到屏幕上即可,这里坦克同一占4个元素块(很重要,之后碰撞检测要用到)

我们接下来分析一下我们应该为己方坦克定义哪些成员:

  • 己方坦克是由玩家动态控制的,因此必须有一个方向变量标识其位置(事实上,我们给己方和敌方坦克都提供了上下左右四幅图像进行输出)
  • 己方坦克若被敌对坦克炮弹击中,那么游戏失败,因此我们必须能够知道己方坦克实时的位置,便于进行炮弹的碰撞检测。
  • 坦克应该有它的生命状态,初始应该是生存状态,被击中后成为死亡状态。

基于上述分析,我们定义的坦克结构体如下(请注意x,y的位置示坦克所属的四个元素块的左上方的坐标位置):


我方坦克控制(热键控制实现)

在这里插入图片描述

根据上图,可以看到,我们可以实现热键控制己方坦克的上下左右运动,原地转向,并且若有墙等阻挡物坦克是不能前进的,下面讲一下设计思路。

我们的坦克是四个元素块,在地图数组map中我们有标识所有障碍物,0表示空地,因此,坦克能否前进必须要进行一些条件判断,如是否走到了地图的边界?是否有障碍物阻挡等等,那么这就分为了四种情况。

  • 坦克要向左行进,是否走到了地图左侧边界,左侧是否有障碍物阻挡
  • 坦克要向右行进,是否走到了地图右侧边界,右侧是否有障碍物阻挡
  • 坦克要向上行进,是否走到了地图上侧边界,上侧是否有障碍物阻挡
  • 坦克要向下行进,是否走到了地图下侧边界,下侧是否有障碍物阻挡

下面是条件判断示意图:

一定要注意x,y的位置在坦克所属的四个元素块的左上方,因此向右和向下的条件判断需要给当前坐标增加2而不是1。

坦克的动态运动过程也很容易实现,就是根据方向提供相应的矢量图,例如,向前行进一步,之前的坦克区域被黑色覆盖,矢量图在新的位置出现。关于坦克的动态运动有两种情况:

  • 坦克运动的方向与之前不同,即发生转就地转向,不行进。
  • 坦克运动的方向与之前相同,即向前行进一步即可。

下面是动态实现的代码片段:

下面是向上和向下前进代码片段:


子弹定义和初始化

我们先来看一下子弹的运动过程:
在这里插入图片描述

坦克初始化完毕后,子弹该上场了,己方坦克由玩家自己控制炮弹的发射,而敌方坦克是由系统自动控制发炮的。我们这里先讲解己方炮弹的设计过程。

炮弹也是动态的,因此它的运动过程和坦克一样,因此它设计过程如下:

  • 炮弹具有生命期,打中敌对坦克、墙体、飞出地图外、打中BOSS鹰都会销毁
  • 炮弹具有方向,而且炮弹已经发出,其方向直至生命期结束都不会改变,即炮弹是不会转向的
  • 炮弹必须知道其坐标,因为后面要进行碰撞检测,也就是,炮弹到底是否已经击中目标。

基于上述分析炮弹的定义如下:


敌方坦克初始化与出场设计

我们己方坦克和子弹都已经设计完毕了,那么敌方坦克该出场了。它与我们上面讲述的己方坦克最大的区别就是它的出场、前进、发射炮弹都是由系统自动控制的,
这里我们简单起见,游戏设置敌方坦克的数量为15个,每个敌方坦克出场后在map地图数组上的值为100 - 114,之前我们设计的己方坦克出场后在地图数组上的值为200,这样便将他们区分开来,方便我们之后进行碰撞检测。

那么敌方坦克的结构定义与己方坦克是相同的。我们在初始化时默认出场三台坦克,它们的位置分别位于左中右三个部分。默认的方向是向下的,之后每隔5s出场一台坦克。当然每台坦克都有要攻击的目标(这里有两个:己方坦克与老鹰Boss),具体的选路算法我们之后在讲解。

这里我们主要看一下三台坦克出场后,第四台坦克的位置问题,首先,坦克的出场位置一定是在地图最上的一行,默认情况下所有的坦克依次从左边、中间、右边的位置出场,但是有可能某台坦克按照之前的约定位置出场,但是该位置已经有其他坦克了,因此新坦克不能再这里出场,我们设计算法不断的在第一行进行搜索,直到找到了空闲位置便出场。


敌方坦克选路算法

请注意我光标所指的坦克的运动轨迹
在这里插入图片描述
敌方坦克攻击目标(Boss或己方坦克)只有四种情况(在同行我们可归结为其中任意一种)

  • 目标在敌方坦克的左上方位置
  • 目标在敌方坦克的左下方位置
  • 目标在敌方坦克的右上方位置
  • 目标在敌方坦克的右下方位置
我们对敌方坦克的控制主要在于对Boss和己方坦克的攻击,这里我们可以采用这样的策略,就是偶数出场的坦克攻击Boss,奇数出场的坦克攻击己方坦克。如下:

具体的选路算法也很容易理解,如果我们要攻击Boss,由于Boss的坐标是固定的(12,24),因此我们只需要知道敌方坦克的位置便可以确定要攻击的目标在坦克的哪个方位,例如Boss在敌方坦克的左下角,那么敌方坦克只有两种选择,向左或者向下,具体选择哪种方案我们交给随机值去处理,使得游戏更具有随机化。
如果要攻击己方坦克,那么己方坦克是运动的,但是其在map数组中的位置也是动态同步变化的,因此我们完全可以和处理攻击boss的方案一样去处理该类情况。

下面我们给出目标位置在左边的情况:


子弹运动与碰撞检测

接下来便是真枪实弹的开火了~

子弹也是个动态运动的元素,因此它的动态实现和坦克的动态实现大致相似,这里不再赘述。

由于我们对于己方坦克炮弹的碰撞检测和敌对坦克的碰撞检测在同一函数中实现,因此我们关注以下问题:

  • 必须能够区分炮弹是己方坦克发射的还是敌对坦克发射的。
  • 己方坦克攻击Boss不会产生任何影响,敌对坦克攻击Boss,游戏失败。
  • 敌对坦克有多个,因此若敌对坦克的炮弹击中了另一个正在运动的敌对坦克,对游戏不产生影响。
  • 己方坦克炮弹击中敌方坦克,该敌方坦克被销毁;敌方坦克炮弹击中己方坦克,游戏失败。
  • 炮弹可以打出地图外,但是其生命终止。
  • 炮弹打中可消除墙后,根据打中的位置,击中的墙体消失(打中两面墙的中间位置,两面墙都销毁;打中一面墙,该面墙销毁)

下面请注意己方坦克攻击墙体的销毁情况(最后一行):
在这里插入图片描述

下面是核心代码框架:


敌方坦克炮击算法

敌方坦克炮击算法的描述如下:

  • 刚开始出场的坦克直接携带炮弹并发射
  • 之后每隔2s发射一次炮弹,并且在前一个发射的炮弹生命期未结束时,不能发射下一个炮弹
  • 遍历所有敌方炮弹数组,只对处于生存状态的炮弹进行碰撞检测。
  • 基本的炮击算法和上面的碰撞检测算法相同,可参考上面的讲解。

游戏结束控制

游戏结束有下面几种情况:

  • boss老鹰被敌对坦克击中 - 游戏失败
  • 己方坦克被敌对坦克击中 - 游戏失败
  • 己方坦克消灭了所有的敌对坦克 - 游戏成功

那么我们只需要在碰撞检测函数中返回相应的退出码即可,然后在外部调用处接收。我们根据不同的退出码向屏幕打印不同的退出界面。

展开阅读全文

没有更多推荐了,返回首页