写这边文章的契机是之前一直没有时间记录(外包岗不允许想github推送提交),所以一直拖延到现在。现在终于有了时间可以整理一下过去自己做过的东西,所以更多算是一个记录与回顾吧。
其实我做的第一个小游戏的契机非常的巧合,在之前的公司里,我们主管想我表达了公司想要研发小游戏的意向,但是其实当时我已经提了离职了,我还是趁着这个空档期去学习了一下如何开发一个小游戏(cocos creator)。本以为离开了这家公司后面可能也不会做小游戏这块。
但是在另一家公司里(UC),由于印度新冠疫情的影响(主要做的是东南亚的业务),导致大部分人闲置在家无所事事,所以产品们打算快速推出一款小游戏项目,一块类似于飞行棋的印度游戏,供印度用户在家跟朋友联机玩。
当时研发主管向组内的成员询问谁有小游戏经验,最终因为我曾经学习过一段时间(大概2周),并给他看了我做的demo之后,就拍板让我做。
我的学习小游戏时做的demo是下面这样的,是学习的B站的一个教程:
说实话,虽然当时有点慌,毕竟时间是比较赶的,因为要赶在疫情还没结束前上线,如果疫情结束了,那么这个游戏的效果就会大打折扣。所以当时的预期是一个月内上线,对于一个还没有正式开发过一个游戏的人来说,这个时间是很赶的,而且因为团队内也没有人做过游戏,所以没有任何可以复用的逻辑。
但是我内心是很兴奋的,因为能够自己做一款游戏出来,大概每一个男生都会感到开心兴奋吧。我当时想的只是我一定可以的。
因为这个游戏是一个已经是很成熟的游戏了,所以在游戏规则和UI方面是可以向其他游戏借鉴的。
就是上面这款ludo king,玩法很简单,跟飞行棋高度类似,就是四个玩家轮流摇骰子。谁的四个棋子最先全部到达终点,谁就获胜。在路途中,踩到别人的棋子可以将别人送回家。
当然,其中还是有一些比较细节的规则,比如需要摇到6点才能从基地里出来,或者在路途中会有一些安全区域不必担心会被踩回去等。但是基本玩法大概就这样。
为了后期的可维护性,项目开始开发前我还是做了比较细致的模块设计的,总的来说分为以下这些模块。
简单介绍一下:
- 后端服务就是实际意义上的后端服务,由一位后端同事负责。
- 机器人模块包含了本地的机器人以及在线的机器人,它们俩是同一个模块(集成为npm包的形式),里面集成了一些机器人走棋策略的封装和整体流程控制转移的逻辑。
- 服务请求处理模块就不必多讲了,就是对请求模块的封装,里面包含了像登录/排行榜信息查询/活动配置等接口封装。
- 游戏IO控制其实就是基于Socket.IO的封装,对游戏的IO消息进行流程化封装,确保消息序列正确,以及处理掉线重连逻辑等。
- 数据层也是比较简单的,主要储存了游戏中的数据,比如当前走棋玩家,当前棋盘,分数,金币数等。
- UI表现模块和效果控制也很好理解,控制UI以及动作效果的。
- 最下面就具体到每一个节点,比如棋盘,棋子,消息等。具体的逻辑以及执行是封装到各个节点内的。
其实这里比较有意思的事对于棋盘数据的设计,如何在频繁的IO消息中准确的描述出棋子的位置并节省内容大小呢。
这里其实是记录16个棋子的坐标,就如下图所示:
这里每个玩家都有一个从自己基地门口到终点的路径,这个路径是不会变的,我们只需要记住每个棋子当前在什么位置就可以知道整个棋盘的状态,而不同玩家间的棋子通过转换为同一坐标系,就可以判断是否发生了踩踏事件。
而且这样做还有一个优点是当你摇动到对应的点数时,你只需要在当前棋子坐标加上对应点数就可以得到它移动后的位置,非常简单,然后再根据这个坐标通过一个坐标对应棋盘的映射,就可以直接渲染到视图中了。
那么这样描述出来,整个棋盘的数据就是一个4*4的数组:
[
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
]
数组里只有包含0-57的数值,我们通过简单的数值转换就能知道整个棋盘的状态了。
而对于这种流程很长的游戏,这样棋盘数据结构也使我们很好的控制整个游戏的流程,比如我们需要在测试的时候测试最后胜利状态是否正常。就不需要从头开始玩,只需要通过暴露到全局的测试方法直接修改棋盘的数据,很快我们就到了最后一步:
[
[57, 57, 57, 56],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
]
在开发中还是遇到了不少问题,这里记录两个比较大的问题。
- 网络差导致的玩家掉线或者丢消息
因为印度的网络不比国内,很多地区的网络情况是很差的,所以比较常见的是玩家玩着玩着就卡住了,或者是因为某个玩家丢了一条消息,所以导致他本地的棋盘数据不准确。因这类问题引起的体验效果非常差。
针对这种情况我们也采用了一些手段去优化:
- 对IO消息增加序号,避免了消息丢失或延时带来的数据渲染异常问题。
- 并且再每次棋子动作之后都需要将行动者的棋盘数据同步给所有人,让其他人同步该棋盘。
- 对于掉线的人给予服务端机器人托管,避免一场游戏因多人掉线无法进行下去。
- 游戏加载缓慢
这同样也是由于印度网络差引起的,在一开始游戏上线(还没开始推,只在一些小流量入口进行尝试)的时候,发现游戏加载时长的数据很差,平均加载的时间都能达到十几秒。
对于这种情况,我们也采取了一些措施,最后将加载时长降到4s以下:
-
梳理整体资源,确保首页只加载首屏必须资源,其他资源通过懒加载的方式进行加载(比如一些不会一加载就立即弹出的弹窗资源)
-
域名预链接,主要基于Link标签的preconnect。以及在一些活动落地页通过iframe提前将游戏资源加载。
-
主要资源离线。其实这个主要还是依赖于原声APP端的能力,通过将一些静态资源打成zip包,然后APP通过接口将这个资源包预先加载到本地,并通过一定的规则对资源进行拦截,从而达到优化页面加载的效果。当然这种手段也是最直接最有效的。
-
模拟进度条。技术上做的优化也是会有一定局限性的,比如一些登录请求,js脚本执行,页面初始化等不可避免的流程也会造成首屏的加载耗时。针对这种情况,设计了进度条模拟的方案,通过进度条在95%之前流畅增加的方式,增加用户的耐心,让他认为,他离进入游戏仅省一步之遥了。
其中一个比较有意思的点是机器人的实现。因为一款飞行棋类的游戏,人机对战其实也会占比较大的比重。尤其是在印度这种网路状况差的地区。
其实机器人实现起来也非常的简单,我们拥有一份本地的棋盘数据。同时我们的IO层、数据层、交互层并不关心你是不是机器人,只要对接上跟服务器一样的IO接口,那么就能让整个游戏动起来。
那接口方面我们直接用跟服务端一样的接口,但是怎么让机器人动起来呢?
其实也非常简单,我们根据当前的棋盘数据跟生成的随机骰子点数进行分析,分析出当前机器人拥有的四颗棋子中,究竟应该移动哪一颗,这里我们用到简单的权重计算:
- 假设每颗可移动的棋子的初始权重为0
- 棋子位于安全的位置上(拥有保护罩,不怕被踩回家),此时最好不要移动这个棋子,权重-2
- 所得骰子点数使某颗棋子可以刚好踩死别人的棋子,那么每可踩死一个棋子权重+3
- 某颗棋子后面6格内有别人的棋子,且棋子本身不在安全的位置,(此时可以表示这颗棋子有被踩回家的危险),应该尽快逃离,后面6格内每有一方地方的棋子则权重+3
根据这些策略,将这些权重进行组合,我们就得到了一个比较简单的机器人走棋逻辑,将那个4*4的棋盘坐标跟随机生成的骰子点数传入,就能得到权重最高的棋子。
当然这个方案看起来非常的简略,其实本来也有后续的设计,比如通过操控生成的骰子点数,增加机器人精准击杀的概率。 但是在这个权重计算实际使用之后,发现不需要那么复杂。因为光是有了这个权重计算的策略我们研发团队就已经很难在4人局中获胜,甚至后面还得在取得最优权重棋子之后通过一个随机概率去确定要不要放弃走这步最优解,以此来降低游戏难度。
大概我的第一个游戏项目就是这个情况,项目上线之后高峰时刻也有将近20w的日活。但是比较可惜的是游戏稳定运行了一个多月之后印度方便就宣布禁止了几十款的APP,而UC Browser赫然在列。所以导致这个面向印度的小游戏项目流量大跌,仅剩下部分印尼用户以及少量的全球用户访问。后面的版本迭代自然无需再提。
但是我从这个项目中也学习到了很多,最关键的是我开发了一款小游戏,这让我感到编程的快乐。在我小的时候,玩着小霸王的时候肯定没有想过,我将来居然也成功开发出来一款游戏,这实在令人感到自豪。
除此之外,我也参与开发了另一款小游戏 -- 夸克浏览器2021高考活动叠叠高游戏。
因为其实逻辑都差不多,并且由于这款游戏是单机的,没有太复杂的逻辑,就不再展开了。