游戏开发中的小问题/技巧总结 -- 持续更新


1:游戏CutScene中有类似电影字幕的字幕效果,但是呢,要求最后一个字幕播放完毕之后,需要消失,而不是就常驻在屏幕上。目前先后字幕是这种关系:后面一个字幕直接覆盖前面字幕的内容。 那么借此关系,我们可以很容易的取巧:让策划配置一条通用的内容为“”的字幕,然后设置为最后一个字幕,这样子,就可以使用这个空字幕去覆盖掉上一条有文字内容的字幕了,达到了播放完毕清除的效果。

字幕

 

2:游戏中CutScene/CG等播放过程中,如果出现断线重连,体验上其实是一件很恼人的事情,而且断线重连开始需要Pause,重连成功则需要Resume,这在逻辑上也比较恼人。取巧一下:我们让在CutScene/CG等播放过程中的时候,直接就不允许出现断线重连UI即可,但是底层还是需要继续心跳的。最极端的情况就是:CutScene/CG结束的时候,会有一个重连过程,但是这是可以接受的。

 

3:游戏中会存在一些镜像的图片表现,那么如果去实现镜像呢?有很多技术文章,但是其实也没那么麻烦,直接使用UGUI的RawImage的uv配合纹理图片的ImportSetting的WrapMode,完全可以做到不止镜像的效果。

 

4:游戏开发阶段,经常会在底层逻辑系统中提前使用一些表格数据去初始化一些逻辑对象。这在开发阶段问题不大,倒还简单,但是到了上线阶段,表格过大,导致的初始化耗时长的问题就凸显了。比如任务系统,在初始化的时候,就经常去提前初始化一些任务元素,然后后期任务表过大,就会导致任务系统初始化耗时过长。最好可以做到类似cow(copy on write)的机制,使用的时候才去初始化。这在大多数游戏项目后期,都是一个优化项。

 

5:游戏中,经常会有不规则的地图出现,同时策划会要求,点击不同地图进入不同的二级面板,如下图。那么如何判定点击的是哪个地图呢?方法有几个:

(1):在后台,给每个不规则地图都衬底一个纯色[伊米高原:红色, 冰封森林:蓝色]的图片,形状与地图一模一样,然后上层射线检测碰撞点的颜色从而去区分。

(2):重写UGUI的Image的IsRaycastLocationValid配合PolygonCollider2D去实现

(3):UGUI的Image的IsRaycastLocationValid中,有个alpha成员alphaHitTestMinimumThreshold会影响检测结果,也就是如果纹理的alpha低于阈值,就不会响应。反向分析,我们只要给地图Image的纹理的无效范围的alpha低于阈值,那么自然这些范围就不会响应,达到了我们的目标。唯一不利之处在于:需要设置纹理为read/write,因为cpu需要读取纹理的alpha去比较。

 

6:游戏中处理时区不稳妥,那就经常会出现问题,因为服务器不一定架设在北京时区的地区,客户端也不一定在北京时区的地区,尤其对于出海的这种涉及到活动类时间显示/计算的游戏。那么最佳处理方式就是:服务器在和客户端进行时间同步的时候,需要将

  1. 当前server所在时区与UTC时间的差值秒数 以及 server的时区时间 或者
  2. 当前server的utc时间

告知客户端, 客户端得知这些信息,就能基于当前客户端所在时区去做事情了。

 

7:怪物出生的时候有时候会有无敌状态,那么如何高效的去处理这个无敌状态呢?有人会在怪物出生的时候特殊处理这个无敌状态,但是更加合理规范化的方式其实是使用buff去做,出生的时候给一个无敌buff,反正每个游戏都会有buff系统。

8:客户端写代码的时候,经常需要把一些特殊情况考虑到,比如奔溃,断线重连,切场景,网络波动时区,客户端调时间/时区,弱网环境,两个账号登录一个设备,一个账号登录多个设备,连续重入(比如异步资源加载,比如请求server,比如UI)等,考虑不到就可能出现奇怪情况。比如,任务系统中在某个任务完成的时候触发一个cutscene,cutscene结束之后需要跳转到另外一个地图。那么如果cutscene播放过程中,奔溃,然后再次登入游戏之后,玩家需要在哪个地图呢?而且因为这个任务已经完成了,这个时机已经过去了,那么这个未完全播放完毕的cutscene是不会被再次播放的,这是策划希望看到的吗?

 

9:客户端任务系统,有时候需要使用已完成任务去做一些判断校验,那么就需要保存这些已完成任务的id。之前的做法是每次玩家登入server的时候,server将这些已完成任务列表同步给client,client在内存维护这个列表,但是不记录本地文件。但是后期随着玩家完成的任务越多,这个列表越长,同步的数据量也就会越大。 经过优化的做法是:client本地磁盘也记录这个列表数据,同时client和server各自记录一个该列表数据的版本号version,客户端每次登入游戏的时候,就使用本地的version去请求server,如果server判定两者version一样,则客户端使用记录在本地磁盘的的列表数据。否则server将差异性数据或者全量数据 以及版本号version全部同步给client,client无脑增删改即可。

 

10:使用protobuf进行双端通信的时候,关于数据压缩,默认值,大小端这些事情,基本上protobuf都帮助我们处理了,目前看下来,唯有协议结构重用需要我们自己处理。也就是一个协议包,没有一个clear数据的操作,导致不能重用,幸亏有轮子

 

11:unity中某些检测socket状态的代码 以及 网络消息分发的代码 在update中执行,这就可能会导致同帧问题。 比如 一个server给client踢人的包 先于 server断开socket执行,但是两者在同一帧,然后因为上述逻辑,就会导致踢人的包client无法去处理。需要小心这种基于轮询的状态的逻辑设计。

 

12: 最近在做表格相关的工作,发现这部分其实也有很多可道之处.首先一个表格,需要支持: 字段都为readonly, 可系列化显示在Unity的Inspector面板中方便调试, 支持自定义enum类型的字段定义,自定义字段,数组,表格继承[比如NPC,Player等表格继承Actor表格],[浮点数(因为不同平台浮点数精度处理不一样,所以需要用万分比等操作支持浮点数,或者自己实现IEEE的浮点数标准)],字段过滤[比如一些表现性字段server不需要,那么就需要在server端过滤],以uint和以string为key的表格定义,支持json, 以及前面提到过的系统初始化时非全量表格读取...... 在优化的时候,要考虑到: enum如果都按照int去处理,会耗内存.自定义字段比如CSVVector2,如果大多数都不填写那就是默认值{0, 0},如果按部就班的将这些默认值全部解析到内存也很耗,可以使用string的优化方式,给每一个自定义类型定义一个public statci readonly Default的成员,这样所有默认值都引用这个值, 同样的道理,表格中一些资源路径前缀重复度很高,最好动态拼接. 建立数组的时候,就直接new固定长度,因为list等实现中,可能会有一些空内存占用,比如new List<int>();然后Add,其实此时真正内存只需要一个int,但是实际list分配了4个int的空间. 通常任务中,每一次执行的任务操作都不一样,对应在表格中就是参数都不一样,那么传统固定字段名字的方式去填写参数就显得不方便,而且有时候会平白消耗无效字段内存.那么此时就需要有kv形式的json来处理了,json配合C#的反射,直接创建对应对象,方便.

部分方式参考这里

表格优化的原则就是:预处理,将一些可以在编译前执行的操作,都提前处理掉,不要放到运行时候处理.

 

13: 在做采集的任务的时候,有可能出现游戏奔溃的情况,检查代码发现可能是在不存在采集物的时候,一直递归执行采集行为导致的。简单做法就是如果暂时没有可采集的物品,则直接停止任务自动执行即可。但是这种体验不是很好,因为采集物是每间隔几秒钟就出现一批次的,所以如果时不时的任务就停下来,体验较差,策划希望就是采集物不存在的时候就一直等待,当采集物重新生成出来的时候,再自动驱动任务执行采集操作。一个很简单的符合要求的做法就是:采集之前先wait一会儿,然后不管是否存在采集物,都开始执行采集操作,如果不存在采集物,那么就再wait一会儿。这样子就避免了之前的递归情况,而且符合了策划的需求。对应的巡逻杀敌之类的任务也可以这样子处理。

 

14: 随机引发的死循环, 有需求:相邻位置不能使用同一个id,那么在一个长度为1的id池中随机必然就会导致死循环。

 

15: 小心项目中的timer。时刻记住timer要在什么时候cancel。coroutinue,callback等亦然。

 

16: 转换update中的轮询判断为event 通知。有需求:进入爱心任务区域的时候,自动追踪爱心任务,离开的时候,自动取消追踪。 同时在爱心区域之外,还可以手动追踪爱心任务, 在爱心区域之内,也可以收到取消追踪。手动操作的在进入/离开区域的时候还会收到自动逻辑的控制。如果使用update的轮训方式去实现,那么很难实现。 所以这里需要转换为event通知的方式。也即是在进入区域/离开区域的时候通知外界。

StateChecker

 

17: 对象池中的元素,一定不要在外部还存在引用。因为入池的元素,本质上就被认为是:和外界脱离了联系,只有出池的时候,才能确立新的联系。违背这个原则,几乎肯定会出问题,而且难查。

 

18: 任务系统中,某些操作不能被立即执行,需要等到合适的时机才能续上执行,比如:战场中杀敌的时候,杀敌数满足了当前任务的要求,那么按照正常自动任务的逻辑,此时就应该驱动主角进行任务的后续步骤了,但是很显然,战场中不能这样做,需要等到退出战场才能去续上自动任务的执行。

自动任务驱动逻辑

除了任务系统,其他逻辑系统,或者UI层面,肯定也会有这种需求。那么我们有必要像Timer一样进行一个抽象。

 

19: 使用ILRuntime作为热更框架的项目,并且热更工程独立于Unity框架工程,之前一直在想如何快速切换热更执行模式 和 原生执行模式。了解到有做文件夹软连接的,有拷贝代码的。但是最快方式就是直接将热更dll拷贝/删除到assets下面。虽然看起来存在循环引用的问题,但是执行上不存在任何问题,因为没有构成一个执行的闭环结构。

 

20: 将断线重连 集成 到 逻辑系统/UI中,使用Interface的形式,想用就实现Interface,不想用就不实现。

 

21: 底层逻辑系统存储游戏中使用的大多数逻辑数据,如果不断点调试查看 的话,几乎没有其他方式查看这些逻辑数据。 所以需要添加快捷方式,比如按键A键,就可以将所有系统的逻辑数据输出到Json文件中。

原文地址:https://www.cnblogs.com/grassgarden/p/13749935.html