Cocos2d-x类COC手游与RTS游戏的编程实践总结
概述
先来看一段4分钟的技术demo演示视频:[视频链接](http://www.tudou.com/programs/vi ... sourceId=0_07_10_28)。在2014.08.13 - 2014.09.15这一个月左右的时间里,我独自一人在家使用Cocos2d-x引擎完成了该视频中的技术演示demo。我接触Cocos2d-x引擎已有一年时间。
demo中的图片资源是我从网上搜集的,而按钮等UI则是我用PS绘制的,主要是因为网上难以找到合适的UI素材。下方的方形按钮,是我模仿COC的样式绘制的。
以下分别是该demo在Windows开发环境(VS2013)和安卓手机上的运行效果截图:
从上面的视频和截图可以看出,在这一个月里,我在技术层面针对类COC游戏的制作进行了一些尝试和探索。此前我较少参与游戏开发,通过这次尝试,能够发现编写这类游戏可能遇到的问题,并思考相应的解决办法,这也算是一种经验积累。
大家对COC应该并不陌生。Supercell公司在3年前成立,随后推出了一款名为Clash of Clans的移动游戏,简称COC。这款手游在2014年成为全球最赚钱的手游,Supercell公司的市值也飙升至30亿美元。关于COC的报道,大家可以查看以下两篇文章:
如果大家感兴趣,也可以在网上自行搜索关于COC的更多信息。
最初,我打算模仿制作一款类COC的手游(最终可能与COC有较大差异),并且融入RTS游戏的元素,例如可以直接选中并控制士兵的移动和攻击,以增强操作感。我之所以想加入RTS元素,是因为我曾经是一名魔兽3遭遇战玩家,在BN亚洲战网排名前100 - 200。
在视频中,我已经实现的游戏功能包括:摆放建筑、拖曳和缩放游戏场景、单位兵种移动和简单攻击。目前我尚未加入任何游戏的业务逻辑代码,主要是想搭建一个基础框架,理清开发思路。熟悉这类游戏的编写后,该框架可以改造成RPG(角色扮演)、RTS(即时战略)、SLG(策略)、塔防等多种类型的游戏。
试想一下,如果运用RTS的技术来制作塔防游戏,那将会非常酷。RTS对现实世界的模拟更为逼真,但技术难度也肯定高于《保卫萝卜》等游戏。这也是我之前提到“最终可能与COC有较大差异”的原因。
COC这类游戏相较于其他手游,编写难度要复杂许多。下面我将总结目前已知的编写类COC游戏会遇到的技术点。
编写类COC游戏的一些技术点
操作的策划
在开发过程中,我们面临一个选择:是先定义好操作规则再进行实现,还是边开发边思考边修改操作规则?我认为,如果不进行创新,完全照搬已有产品,那么可以先定义好规则;但通常情况下,一开始很难将整个系统和规则考虑周全。由于我是独自开发,所以选择了边做边想边改的方式。毕竟在开发初期,很难考虑到所有细节,先进行开发可以在实践中发现问题并及时调整。
腾讯的《城堡争霸》是一款仿COC的产品,其操作与COC有所不同。例如,在移动建筑时,《城堡争霸》玩家手指放开后建筑就会直接摆放,而COC则需要玩家再次点击确认。在操作上,COC多了一次点击的步骤。
操作代码的编写
COC这类游戏包含众多游戏元素,如树、石头、建筑、各种兵种角色单位等。通常有以下三种基本操作:
- 单个手指滑动:代表拖曳游戏场景。
- 两个手指操作:代表缩放游戏场景。
- 点击屏幕:代表选中一个单位。
腾讯的《城堡争霸》在双指捏合缩放的实现上与COC不同。《城堡争霸》采用的是“中心缩放”,在缩放时不能同时移动游戏场景;而我的实现方式模仿了COC,在缩放场景的同时还能控制场景移动。
拖动与缩放操作需要进行限制,例如不能将场景移动到游戏世界区域之外,避免看到游戏世界外面的黑块,同时也不能进行无限放大和缩小。目前我仅实现了缩放限制,移动限制尚未完成。移动限制的实现较为复杂,它与缩放操作存在关联。
观察COC可以发现,如果玩家在一个建筑上按下手指但未拖动,那么该建筑会被选中;如果进行了拖动操作,则不会选中该建筑,而是拖动游戏场景。因此,在程序中需要进行判断和记录,以区分是否存在拖动操作。
由于我在游戏中加入了RTS控制元素,允许选择行动体单位并控制其移动和攻击,这使得操作控制代码变得更加复杂。对于这种复杂的操作,我采用状态机来解决。在onTouchesBegan、onTouchesMoved、onTouchesEnded这三个触摸函数中都引入了状态机。触摸函数采用多点触摸方式,否则无法实现双手指捏合缩放功能。为了降低操作控制的复杂度,我新建了一个触摸层专门处理触摸操作。
以下是我onTouchesMoved的代码截图(此处应补充实际代码或详细描述代码功能):
外层代码首先判断是单点触摸还是两点同时触摸,然后在两个判断分支中都使用switch - case语句进行状态机处理。对于熟悉设计模式的同学来说,使用switch - case编写状态机代码可能不够优雅,但在尝试阶段,当思路尚未清晰时,这种方式是可行的。等我思路清晰后,可能会将其改为状态模式实现。
角色的AI
COC中存在一些“行动自治体”,例如某些角色会在游戏场景中四处走动,触摸树木或石头;空降兵到达敌人领地时会自动发起攻击。这些功能是三消、跑酷类游戏所没有的。编写这些AI可以让玩家感受到游戏世界的真实感,这也是COC相对于其他类型手游额外的开发内容。基本上,使用状态机建模可以解决这些AI问题,但目前我的实现中尚未加入“行动自治体”相关功能。
寻路属于AI的一部分,求最短路径的A算法是人工智能领域常用的算法。实际上,COC中的寻路相对简单,与红警、帝国、魔兽等真正的RTS游戏相比,其寻路逻辑没有那么复杂。我的游戏中也采用了A算法进行寻路,关于A算法,大家可以参考我写的这篇文章:[《Cocos2d-x地图行走的实现3:A算法》](此处应补充实际链接)。
我曾经尝试将寻路算法放在一个线程中进行计算,以避免界面卡顿,这种方式在传统软件开发中较为常见。但在修改过程中遇到了不少问题,所以暂时搁置了该方案。
编写真正的RTS游戏的技术点
COC实际上并非真正的RTS游戏,而是一款经营策略类游戏。前面提到,我想在类COC游戏中加入RTS元素,下面我们来看看RTS游戏有哪些技术点。
不允许行动体互相穿越的限制
COC中的人物允许互相穿越,即两个行动单位可以重叠在一起;而在魔兽3中,行动单位不允许互相穿越,这种设计更符合现实模拟。正是因为不允许穿越,魔兽3才出现了“卡位”“M围杀”等操作技巧。
在实践过程中,我发现实现不允许穿越的限制较为复杂。我猜测Supercell可能认为这个问题处理起来过于繁琐,所以选择允许人物穿越,不进行过于真实的模拟。允许穿越实际上会大大降低程序的编写复杂度,原因如下: 不允许穿越通常通过碰撞检测来实现。如果游戏场景较大,为了降低碰撞检测的时间复杂度,需要进行空间分割处理。此外,不允许穿越还会引发更多的AI问题,例如“移动碰头死锁”。这是我自创的名词,用于描述这样一种场景:在一个狭小且长的通路中,如桥、巷子、树林间隙甚至空地,有两个单位A和B,A从左向右行走,B从右向左行走,且两个单位处于同一水平线上,就会出现“你不让我,我不让你”的情况。
以下是我的游戏截图(此处应补充实际截图),展示了A和B相向而行的场景,在某个时刻,它们会“碰头”(此处应补充“碰头”时的截图)。 如果没有AI控制,就会出现互不相让的“死锁”,双方会不断向对方的方向“推”。为了解决这个问题,我们需要编写AI来决定A和B如何谦让,然后再移动到各自的目的地。目前我的处理方式较为简单,可能会让一个行动体停下来,另一个行动体绕过它,或者两个行动体都停下来,等待玩家重新控制行动。这种做法会让玩家觉得游戏中的人物不够智能,操作起来也比较麻烦。
在“集体行动”时,控制也较为复杂。当控制一群人向一个地方移动时,他们之间不允许互相重叠。在碰撞检测中,如果发生碰撞,需要判断是“碰头”情况还是“大家往同一个方向走”的情况。对于非碰头情况,我的做法是:等待前面的单位移动出空位后,后面的单位再向前移动一点。
以下是相关代码截图(此处应补充实际代码或详细描述代码功能):从代码可以看出,我只是暂停了Node节点的移动Action。如果检测到没有碰撞,就恢复执行之前的MoveTo动作。目前我的实现还存在缺陷,在某些情况下,集体行动会出现“穿越”问题,但在大多数情况下可以保持不穿越。
实际上,防穿越还涉及更多细节问题,这里不再一一列举。由此可见,加入防穿越功能会使程序复杂度大幅增加。从复杂度、开发成本等多方面考虑,这可能是Supercell允许人物穿越的原因。
行军算法、布阵算法
在魔兽3中,控制部队到达指定地点时,部队会形成一定的行动队形。目前我尚未深入研究如何实现这一功能,但这是RTS游戏中确实存在的需求。另一个问题是布阵,仔细观察魔兽3可以发现,选中8个农民到某地后,他们会在目的地形成一个类似矩形的阵型。
对于规则布阵问题,我还没有进行深入思考。但随机阵型是不可避免需要考虑的情况,即选中N个单位并指挥它们到某处时,由于受到防穿越限制,这些单位不能集中在一个点上,那么如何确定每个单位的目标点呢?我的做法是在目标点进行BFS(宽度优先搜索),寻找每个单位可以容纳的位置。选择BFS算法是因为它具有圆形向外扩展的特性。
以下是我的代码截图(此处应补充实际代码或详细描述代码功能): 在实际操作中,还需要考虑如何保持原先的集体“布局”。例如,A、B、C在整个选中的单位集合中有各自的位置,将这些单位移动到另一处时,较好的做法是保持它们在集体中的相对位置。这样做可以使整体行军的总体移动成本最小,总体移动成本是指集体中每个行动单位的行动耗时、路径长度等之和。总体移动成本越小,玩家的游戏体验就越和谐。我在实践中发现了这个问题,并进行了一些简单处理,但目前效果并不理想。
“A过去”如何做
玩过魔兽的朋友应该知道“A过去”的含义,它指的是“攻击过去”,源于魔兽3的A键控制。“过去攻击”包括两个步骤:一是移动到目标地点,二是到达攻击范围后开始攻击。“A过去”有两个作用点:一是玩家指定的地板位置,二是某个游戏实体,如敌军单位、建筑等。
- 当玩家指定的是地板时,AI逻辑为:控制行动体移动到指定目标点,如果在移动过程中有敌军进入攻击范围,则发起攻击,歼灭敌军后继续向目标点移动。
- 当玩家指定的是某个游戏实体时,AI逻辑为:记住被指定的目标实体,追着它攻击,直到目标被歼灭。
在红警中,坦克会用炮弹攻击地面;而在魔兽3中,只有控制投石车才能攻击地面。目前我尚未实现“A过去”的逻辑。
单位大小通过限制
在魔兽3中,食尸鬼可以通过的地方,剑圣不一定能通过,这是因为食尸鬼、剑圣、尸魔等单位的体积不同。单纯的A算法只能找到连通的最短路径,但无法考虑单位大小的限制。那么,剑圣的寻路该如何实现呢?是否需要在A算法作用的瓦片方格上,将剑圣不能通过的地方标记出来,再执行A*算法呢?这个问题我还没有想清楚,目前我的游戏也未处理这方面的内容。
从以上四点可以看出,像魔兽这样的RTS游戏编写难度较大。实际上,编写真正的RTS游戏远不止这四点,还涉及战争迷雾、地形等诸多因素,可能还有一些未知的技术难点等待我们去发现。
考虑用数据驱动的方式编写游戏
所谓数据驱动,是指通过一些配置文件来控制游戏的显示和行为。我的demo目前使用了COC和其他游戏的素材,若以后要将其改造成RPG、塔防等其他类型的游戏,肯定不能继续使用他人的美术资源。
为了方便进行游戏换皮,我对游戏实体的显示、动画的显示等都采用了配置文件进行控制。例如,我使用了动画表、精灵表、行动体的显示控制表等,目前共有10个配置表。这些配置表使用Excel制作,然后另存为CSV格式,程序通过解析CSV文件数据来加载配置信息。关于CSV数据的解析,我写过一个较为完善的类,有兴趣的同学可以查看我的这篇博客:《CSV文件格式解析器的实现:从字符串Split到FSM》。目前,我的demo仅使用了CSV类型的数据进行配置,并且使用了之前博客中编写的CSV类进行解析。
思考的过程
在此与大家分享一下我的思考过程。《暗时间》这本书中提到,思考需要借助笔和纸,记录当前探索和思考的步骤及环境,以便在回溯思维时,不会忘记推理到哪一步。
在独立开发过程中,很多时候是自己与自己对话,提出设想并进行论证。这种情况下,笔和纸就显得尤为重要。以下是我的一些草稿纸截图(此处应补充实际截图):
最后
目前市面上尚未发现有专门教授如何编写RTS游戏的书籍。经过一个月的尝试,我认为这次实践是有价值的。
仅仅制作类COC游戏,感觉难度并不是很大;但一旦加入真正的RTS元素,就能体会到红警、帝国、魔兽等游戏的技术含量之高。腾讯等公司可以模仿出COC,如《城堡争霸》《陌陌争霸》等,但目前还无法开发出“类魔兽3”“类星际2”这样的游戏。魔兽争霸3不仅是一款真正的现代RTS游戏,还是3D游戏。我估计,不是这些公司不想开发,而是“类魔兽3”这类游戏的开发难度确实较大。
目前我进入游戏领域的时间不长,还是一名新手,欢迎游戏同行与我交流讨论。