从事游戏行业1年多了,个中心酸不知从何说起。抛开非技术的不说,一个开发者需要面对的最大问题,可能就是和策划频繁改变的需求做斗争了吧,这时候就体现了设计模式的重要性,抛开正式的设计方式不说,先讲讲我1年多以来遇到的问题和想到的解决策略吧
最容易卡死的功能:结算界面
几乎游戏中所有的游戏模式都需要一个对应的结算界面,来展示玩家本关的通关情况,每种战斗的结算有着相似的动画流程,但是却不尽相同的样式。
由于历史原因,第一个版本的结算被我改成了图中所示的结构,乍一看好似也很清晰,但是这个结构中存在2个致命的问题:
1.战斗类型的增加导致代码和UI无法维护
由于最初的几个结算界面显示是极其相似的,大概涉及到几种固定的类型:文本、可滚动的数字、砸星动画、获得的货币列表、获得的道具列表、人物经验进度条、确定按钮。于是我把所有结算的代码放在了同一个文件下,这样他们就可以共用很多基本相同的函数,稍有变化的地方,就根据战斗类型进行特判,大致的形式会是这样:
function SetJieSuanXXX()
if BattleType == GameMode_NORMA then
--相应逻辑
elseif BattleType == GameMode_ClimbTower then
--相应逻辑
elseif BattleType == GameMode_Esoterica then
--相应逻辑
end
end
在界面很少的时候,这很节省工作量,假如现在滚动数字的动画需要统一修改,我只需要修改一个函数就可以了。但是策划们并不会满足于统一使用的效果,在后续增加的10多种结算中,出现了各种复杂的需求:
1.有些道具列表需要根据是否有同军团的人参加来决定是否显示
2.某些模式的经验条显示的不是人物经验,而是该活动对应的经验进度
3.某些模式如果突破了某个记录,则需要显示盖章特效
4.对于某种战斗,如果在今日活动的时间内,结算界面显示为A,如果不在今日活动时间内,结算界面显示为B
5.对于某种战斗的结算,需要先显示翻牌界面,再显示结算(类似DNF)
这些只是简单举些例子,对于原来写在同一个代码文件中的做法来说,这无疑是个噩梦,增加一种战斗类型的成本越来越高,你需要特判各种战斗类型和是否有活动来决定UI的最终样式。冗余的代码越来越多,事实的情况是,代码长度从最初的800行,在几个月后变成了2000行。这样的代码根本无法交接,对一个不熟悉该模块的人来说,与其维护,不如重写。
2.触发结算并不是单线逻辑
战斗结束后,客户端会做两件事:1.向服务器请求结算。2.播放剧情动画。而结算界面的显示受制于这两个条件,在剧情动画播完,并且服务器的结算结果已经返回的条件下,再显示结算。这无疑增加了代码的复杂度,可能剧情先结束,也可能服务器先返回数据,而这两个条件又分别包含了一部分结算需要的数据。曾经线上出现玩家的结算界面无法显示,我们要花大量的时间定位出现BUG的位置,因为有太多种可能性了:
1.断线重连丢包,导致服务器的结算协议没有收到,没有触发条件2
2.先触发了条件1,缓存了条件1包含的数据,然后再触发条件2,但是缓存的数据有问题,导致最终没有显示
3.先触发了条件2,因为某种原因,没有收到条件1的事件
对于线上的BUG案例,我们并不能拿到详细的LOG文件,过多的可能性导致一个BUG的查找难度大大增加。把最近新增的代码全看一遍可能可以找出问题,但是大多数情况是根本没有一个明确的方向。
基于上述的情况,一开始的几个月,我几乎不想再碰这块代码,不管是策划增加新的需求,还是线上出现了BUG,都会消耗大量的时间。繁杂的工作迫使我必须改变它,我需要一个更加简单,更加容易维护的结构。于是就出现了下面的版本。
这里有2个重要的改动:
1.触发结算界面显示的逻辑改为单线
2.增加一个结算入口,只负责转发结算需要的数据,根据战斗类型的不同,把数据分发到真正对应的结算界面。每种结算有着自己的代码文件。
于是今后的卡死再也不用纠结是服务器消息没收到还是剧情没有播放完毕了,只需要看看服务器的消息日志就可以快速定位。增加新的界面也变得简单,只要在入口之下再新建一个文件,来描述新增的结算类型就大功告成,不会跟之前的结算功能互相影响,即使出现问题也只需要修改新增的结算。