最近一年多的时间几乎都花在一款游戏上面,这款是自己一个人从零到一做起来的,现在已经完成的差不多了,最近准备正式上线,上线之后就会面临很多玩家,对游戏和自己都是很大的挑战。如何去处理好玩家反馈的问题,如何去更好的避免玩家遇到问题等等,这些都是后续需要提升的地方。先不说远了,在游戏正式上线之前就遇到很多问题,在这里记录一下完成这款游戏遇到的问题。

初印象

因为在做这款游戏之前,我自己就参与过两款游戏的开发,因为在之前的游戏开发过程没有遇到过特别难解决的问题,并且在出现的时候也有人可以求助,就导致自己盲目自信,认为自己从零做一款游戏应该问题不大。

随后就与其他人一拍即合,自己做一款游戏,在定下来项目的方向之后,就创建好游戏的 git 仓库,不久就提交项目的 Init commit。完全没有做提前的调研,也没有提前研究游戏的技术难点。直接就开始对动手做项目,这也为后面埋下了很大的坑,导致后续花了很多时间去填补这些坑。

过程的困难

开发引擎的选择

因为之前接触的游戏引擎比较多的就是 Cocos-JS,所以这款游戏也就选用了这个引擎来做开发。Cocos-JS 引擎的特点就是上手容易,但是可能会遇到比较多的坑,这些坑掉进去了就很难爬出来。在开发过程中碰到印象比较深的问题有四个:

  1. cc.eventManager 有个问题,就是有多个监听者的时候,接收的事件参数会有问题,查了很久也没有找到具体问题在哪,最后没办法,因为项目开始还不久,直接就把引擎升级到 3.16 版本来解决。至于升级 Cocos 引擎也是个巨坑。

  2. cc.TableView 删除 cc.TableViewCell 会导致 cell 上添加的 button 失去点击相响应,这个问题最开始我一直以为是我们自己代码有问题,也花费了不少时间,最后查到是引擎的 bug,在 remove cell 的时候 cleanup 了,但是 cell 是复用的,就导致这个被删掉的 cell 复用时不响应事件点击。更详细戳👈

  3. 这个算是比较小众的问题,因为游戏的语言是泰语的,使用 Cocos 引擎会导致一些泰语字上面被截断,如下图所示:
    泰语字体截断

    这个问题是因为 Cocos 引擎在 Android 层对文本尺寸进行计算的时候,高度没有算对,引擎在渲染文本的时候高度不够,导致截断。最后查到的问题是 Cocos2dxBitmap.java 中使用 StaticLayout 计算文本尺寸时有个参数 includepad 设置成了 false,导致 Android 计算时出现了文本截断,将参数改成 true 解决了问题。

  4. Cocos 热更新组件也是存在很多问题的,Cocos 的热更新组件支持同时下载多个热更新包,但是又没办法保证热更新下载解压的顺序,如果你在两个热更新包都包含同一个代码文件,就有可能造成新的热更新的代码文件被老的热更新包的代码文件覆盖。这个问题最开始的解决方案是限制热更新组件的下载,让热更新包串行下载 this._assetsMgr.setMaxConcurrentTask(1);。但是热更新组件还是有个问题,就是热更新组件维护热更新包下载顺序用的 unordered_map,并且插入热更新包资源用的是 push_back,这个就导致虽然热更新包是串行下载的,但是下载的顺序不能保证。解决方案是将维护下载顺序队列的维护成 map

这些是在开发中遇到的印象比较深的问题,还有比较多大大小小的问题。开发中不是怕遇到问题,而是遇到问题之后找不到解决办法,Cocos 开发中遇到的问题有些很难找到解决办法,一方面可能是没有其他人讨论资料不好找,另一方面是有人讨论但是没有最终的解决方案,这些就是比较坑的地方。还有一点就是 Cocos 的引擎升级,基本上在开发初期定下来了引擎版本,后面基本上就没有升级的可能,这也就意味着引擎后续的 bug 修复,性能优化等等,这些都是享受不到。最最坑的地方就是,Cocos 引擎团队放弃了 Cocos 引擎的维护,将重点转到 Cocos Creator,并且在 4.0 版本移除了 JavaScript 的支持,这就意味着,项目中使用的 Cocos-JS 引擎出现问题,后续很难得到引擎团队的解决。

开发中遇到的问题

  1. 大量的冗余代码。这个问题是目前项目中比较严重的问题,因为前期项目时间紧张,就大量使用 ⌘+C⌘+V,最后造成很多重复的代码,只要代码出现问题或者需求出现变动,要改的地方就很多,也容易漏掉,这样也造成了很多 bug。这个问题的原因是前期搭建项目框架时,缺少基础框架的搭建,在有相同功能的代码时,要多使用继承和封装,尽量避免复制粘贴代码。前期需要对项目的基础架构进行封装,确定项目的大概结构,对常用的基础控件进行封装,一方面是保证项目中使用的统一性,另一方面对方便后期对控件进行统一修改。不要一上来就对着业务撸代码,这样会给后期代码维护和扩展造成很多麻烦和难以填补的坑。
  2. 资源管理混乱。这个也是项目做到中后期遇到的比较大的问题,在游戏初期,游戏的玩法少,活动少,所以资源就不多,前期就忽略了资源管理规范的重要性,在游戏开发的中后期需要对整体的资源进行梳理和优化,在后期资源很多的情况也是一件很麻烦的事情。所以在前期定好资源管理的规范和资源加载的处理方式,会对后期的开发有很大的帮助,也对内存占用优化有很大的帮助。
  3. 内存峰值优化。其实对于我们这种体量的游戏,几乎可以忽略这一块。在游戏上线之前都没有重视这一块,但是上线之后崩溃率很高,最开始一直以为是代码逻辑的问题,没有往内存占用这一块考虑。但是最后优化了内存占用之后,崩溃率就稳定在比较合理的水平。这个是因为游戏发行在泰国,泰国玩家的手机机型普遍不高端,所以手机内存少,就导致游戏在运行期间容易发生崩溃。但是游戏如果发行在国内,这种情况会好很多。所以内存占用要提前考虑好策略,在编写代码的同时也要兼顾到内存占用的优化。

公测中遇到的问题

  1. 玩家反馈卡。游戏在上线测试之后,总是有玩家反馈网络卡顿。这个点我们花了很多时间去定位问题,然后去修复,都收效甚微,最终发现问题是因为前端在网络波动的情况下检测弱网和网络重连处理很慢,就网络波动的时候一直出现 loading,也不会触发重连,或者弱网出现重连的时间很久。就导致玩家的感受是游戏很卡,但是玩家反馈的很卡,其实有很多方面,游戏性能问题导致游戏卡顿,弱网情况下网络层出现问题导致玩家玩的时候很卡。所以在解决问题时,要先确定玩家具体的问题,再去检查问题,修复问题。前端发现问题之后,就很好修复,减少心跳包的间隔,建立 WebSocket 连接的检测等待时间缩短,压后台之后回到前台的策略优化。最后基本上解决了弱网情况下的游戏体验问题。
  2. 游戏崩溃率高。在游戏初期上线的时候,bugly 统计的崩溃率很高,这个有明确 log 上报,所以很好解决。但是后面玩家多了之后,崩溃率还是比较高,这个问题也是花了很多时间,从各个角度都考虑去解决,但是收效也是很小,最后还是内存占用的问题,因为泰国手机机型的特点,需要去优化内存占用。优化内存占用的时候也做了很多,动态加载图片资源,释放音频资源,最后都是没有把内存占用减下来。最后原因是 JS 引擎自动回收太慢的问题,导致游戏在运行期间不停生成中间对象,最后内存占用越来越高,泰国手机机型内存小,就很容易导致闪退。最后的解决方案就是 cc.sys.garbageCollect() 强制 JS 引擎回收内存,就能导致内存占用的峰值降下去,随后崩溃率就下降了很多。

游戏开发的基本步骤

  1. 游戏引擎的调研,这个是非常重要的,因为这个关系着后面游戏的性能,引擎 bug 的修复,以及开发中遇到问题的解决效率。
  2. 确定游戏的设计分辨率,一开始就需要确定下来,与美术约定好设计分辨率,保证适配市面上大部分机型。
  3. 确定游戏的总体框架 (MVC),整体的游戏框架需要在撸代码之前就要确定下来,游戏如果没有整体的设计模式,会在后期的代码维护上面遇到很大问题。
  4. 基础控件的封装,这个一方面可以方便开发,另一方面可以减少冗余代码,在后期统一修改游戏的控件风格时,这种封装的优势就体现出来了。之前的项目,要统一调整按钮的样式,因为没有做封装,导致修改这个按钮样式费时费力。
  5. 资源加载和释放,这个问题在游戏开发中是很重要的一环,需要在最开始就考虑好项目的资源管理方式,异步加载资源还是同步加载资源,如果管理资源的释放,是一款游戏的基本素质。虽然这个玩家开不见摸不着,但是对玩家的影响还是比较大的,如果资源加载处理的不好,会导致耗电严重,手机发烫等等的问题。所以不管游戏体量的大小,每个游戏都应该要重视资源管理。
  6. 内存峰值的优化,这里有两个点需要注意,一个就是避免内存占用陡增,另一个就是使用的内存要释放,不能有内存泄漏。内存占用陡增一方面是会造成游戏卡顿,另一方面也是会增加崩溃的概率。内存泄漏这个是肯定不能有的,或者说影响不会很严重,不然在游戏运行期间内存占用一直会是增加的情况,最后也是会被系统杀掉进程的。
  7. 根据项目实际情况优化游戏性能,这个每个游戏都不一样,根据游戏的实际情况和项目需求对游戏进行优化。例如:图片格式的优化 iOS 使用 PVR,Android 使用 ETC1 等等。

小结

写下这篇文章主要是为了记录下这段时间踩过的坑,遇到的问题,让自己以后碰到类似的情况不至于再踩一次坑。目前能想到的点都写上去了,后续有想到其他的点,或者是新踩的坑,也会尽量更新到这里来。

从零做一款游戏看似简单,但实际上还是会有许多问题。这也让我认识到自己的不足,也学习到了很多知识,果然人还是要踩过坑才知道做事情有难度。