游戏开发中的原则性问题总结(持续更新)

游戏行业是程序员最理想的行业
我入行时间不长,工作之中也没什么人指点,总是犯下许多错误。有的错误是行业经验欠缺导致的,属于结构性错误;有的则是代码规模经验缺失导致的,属于原则性错误。这两种错误互为因果,我把一些原则性的问题记录下来,既反省了自己也愉悦了他人。

游戏开发问题篇

术语不统一问题:

  • 这个问题往往是策划不够专业导致的。策划做的文案,常常带有很强的主观臆想,用一些“情景式”、“过程式”的方法描述一个概念。
  • 比如在做副本系统的时候,策划给的术语“每一大章的每一小关里面分前置关、普通关、精英关三种关卡”,这里面的很多概念都没有成文的叫法,没法对应到大家熟悉的英文,导致配表、协议中出现各种版本的术语。前端的同学干脆用汉语拼音了事,实在有违各种开发规范。。
  • 方案,必须在配表、定协议之前先统一术语,把各种需要复杂描述的概念统一起来,对应成一个简单的单词。比如,“第一章”里面的,叫chapter。“第一节”里的,叫section前置关town普通关country精英关city。这些单词有些是杜撰出来的含义,但是可识别度很高,容易记忆不会造成歧义,就足够了。编程毕竟是要写英文字母的工作,汉语里的概念对应到英文的时候相当于一个抽象翻译过程,在团队协作的时候先把英文术语统一起来可以减少很多沟通成本

数组最大长度问题:

  • 很多结构体的最后都是一个不定长的数组,在代码中是用最大长度表示的,实际上用不到这么大,我们需要一种真正精简的方案。
  • 譬如,stItem _items[MAX_ITEM_NUMBER];如果MAX_ITEM_NUMBER等于100,运行时这个对象就占100*sizeof(stItem)的空间,而实际运行中可能经常在50左右,浪费了很多内存,还可能导致不必要的copy。
  • 方案,使用柔性数组作为容器,但是相应的必须有一个配套的内存池,才能保证不频繁的申请释放内存。上例改成stItem[0];就可以任意扩展stItem的个数,不受MAX_ITEM_NUMBER限制,也不必占用MAX_ITEM_NUMBER个元素空间。注意不能直接在栈空间使用柔性数组,会把函数调用栈破坏。

集合大文件问题:

  • 像配表读取、协议处理、结构体定义等的代码往往天生容易扎堆到某个文件中,使其膨胀巨大化。
  • 文件巨大的唯一好处就是容易定位文件,但缺点是不容易定位目标代码,改动频繁,每次改动都要重新编译次文件,生成目标文件过大。如果是头文件巨大化,就会导致频繁的编译。
  • 于是当我们的集合文件超过了其应有的清晰整洁度之后,就要走向“合久必分”的不归路。在拆分集合文件的时候我发现最好是有个索引文件把所有分散文件的关键参数聚合起来,方便统一处理。这时候我基本是靠宏定义,尽管用一个宏函数把我关心的参数包起来,等需要统一处理的时候定义这个宏的真实意义就好了。

手工序列化问题:

  • 通信协议采用的是二进制数据块的形式,收发消息也就是结构体序列化问题。如果用手工序列化,无疑会导致很多枯燥无味又极易出错的工作量。
  • 方案,客户端使用unity,借助C#的反射机制可以实现自动序列化,只要保证client和server的结构体一致即可。C++部分由于大部分序列化操作都是直接memcpy对象,问题不大,就没有做。按理应该用模板技术做一套自动序列化和反序列化的工具,如果有实现的同学让我参考一下就好了:)

客户端和服务器的消息同步问题:

  • 我们的c/s通信采用的是短连接,虽然是基于tcp的,但是完全不可靠。客户端的消息可能丢失,服务器返回的消息也可能丢失,而双方必须能感知到消息是否丢失,否则就没法玩了。
  • 方案,给每个消息加上序列号。客户端从序号1开始发送消息,服务器从0开始对收到的消息计数。服务器收到每个玩家的第一个消息序号必须是1,然后服务器序号自增1,这样客户端下一个消息就是2,保证客户端序号始终是服务器序号+1 。
  • 这样如果客户端序号大于服务器序号+1,服务器就能判断出来客户端丢包,这时,客户端发来的任何消息都不处理,返回“服务器丢包消息”并带上服务端的当前序号。客户端收到“服务器丢包消息”后比较序号,回退到序号+1消息重发(需要缓存消息队列)。客户端重发的消息将和服务器序号对应上。
  • 如果客户端序号等于服务器序号,服务器就认为客户端重发了已经处理的消息(说明服务器返回的消息丢包),那么服务器直接返回上次的消息即可(服务器总是缓存上一条发出的消息)。
  • 其他情况都视为逻辑错误。

事件累加计数法和事件更新计数法

  • 现在手游中的任务系统、成就系统,所有的达成条件都可以视为事件计数问题。于是我做了一套事件系统,任务、成就生成后全部注册到事件系统中,事件触发后就给注册了此事件的所有任务、成就计数+1(也可能不是1,当成参数传给回调函数就行了)。计数达成后,认为任务、成就完成。
  • 那么问题来了,上面的事件计数方案是建立在先生成任务后发生事件的基础上的,如果反过来先发生事件后生成任务就傻逼了。比如我们的主线任务玩家的成长是一条主线,10级的任务完成后,不领取奖励,这个任务就永远不消失,后面成长到15级的任务就不会出现。
  • 为此就需要事件更新计数法,也就是事件发生的时候对应的任务去刷新其关心的计数值。比如上例中玩家14级的时候领奖10级的任务,这时刷出了15级任务,这个任务就先刷新一次计数,得到14,任务没完成。等到下次升级事件发生,任务再刷新一次计数,得到15,任务完成。当然前提是更新计数的值是一个全局唯一可获取可累加的值,如果是挑战竞技场100次这种条件先挑战过99次后出来了任务,除非单独记录这个次数否则是搞不了的。
posted @ 2015-08-22 01:01  mjwk  阅读(520)  评论(0编辑  收藏  举报