游戏开发之路:经验分享与总结

发表时间: 2022-06-22 00:08

一、卡顿问题的处理

1. 如果玩家触发卡顿,先上传这个玩家的log,看他的帧率,然后定位帧率低的时间,玩家做了什么操作。

2. 如果是能复现的卡顿,执行profile(用于记录一帧所有函数的执行时间),复现卡顿,然后根据profile结果进行分析。

3. 如果是帧率没有突变的地方,是一直都很低,则优先确定是否有定时器使用不当。

4. 如果是以前没有卡顿,最近才出现的卡顿,可以查svn记录,看最近改动的内容。

二、游戏相关算法

1. 分离轴算法:用于实现多边形碰撞检测(引擎自带的碰撞检测效率低,并且对于速度过快物体会出现穿透现象)

2. 碰撞检测将物体分为N叉树:一个场景有很多碰撞体,但是不可能让所有物体两两进行碰撞检测,因此需要把场景的物体按N叉树划分开来,这样可以减少碰撞检测的次数。

3. 基于有向距离场(SDF)地图碰撞系统:类似于王者荣耀那种物理移动效果的实现。一般开发时,不会使用unity自带的物理碰撞系统(因为不可控并且效率低)。

4. Two-pass跳到最大联通区域算法:一般用于随机生成迷宫的游戏,迷宫会有阻挡,为了避免玩家卡在阻挡位置,因此需要用这个算法让玩家跳到最大联通区域。

5. 时间轮算法:一般用于定时器的实现。我们每新建一个定时器,都需要放入一个列表中,然后每帧计算哪些定时器需要执行。如果通过遍历所有定时器的方式来确认哪些定时器需要执行,那么效率太低了。而使用时间轮算法就可以不遍历定时器就能获取那些定时器需要执行。

6. A*寻路算法:自动寻路AI,主角寻路到NPC必不可少的算法。

7. 射线检测算法射击游戏、某些碰撞检测、各种图形学算法等地方都会用到。

三、游戏相关性能优化常用手段

1. 闲时加载(异步加载):当一帧的逻辑过多时,把部分可以延迟执行的逻辑放到空闲的帧执行,避免卡顿。

2. 场景分块加载:对于大场景,进入游戏是不会把整个场景所有物体都加载进来的,而是根据玩家当前位置,选择附近的物体加载。

3. 遮挡剔除:当多个物体发生重叠时,玩家实际上能看到的只有最前面的物体,后面被遮挡的物体是看不到的,这个时候就可以把那些被遮挡的物体剔除,提高运行效率。

4. 协议管理:对服务端返回的协议进行管理,对于不需要立刻执行的协议,可以使用闲时加载的方式来执行(处理不好容易出各种bug)。

5. 合图、合批:一个界面有多个图片,我们如果一张图一张图的读取,那么cpu和gpu的交互次数会很多。但是如果把多个图片合成为一张图,那么cpu和gpu就只需要交互一次(当然这不一定是一次,主要看合图是否包含所有图片),就可以把所有的图片都读取到。同理,针对3D模型网格的合批也是这个意思。

6. 对象池:每次实例化一个对象,都会有较大的消耗,因此需要使用对象池,对于反复创建的对象,销毁时不真正的销毁(仅隐藏对象)而是放入池中,当再次需要使用的时候,直接从池里获取。

7. 复用滚动框:一般我们看到的滚动框都是复用滚动框,就是说滚动框里的Item的个数实际上是只显示我们看到的那么多个,并不是有多少个条目就有多少个Item。

8. 预加载:对于一些资源加载时可能比较耗时,容易在触发时造成卡顿(例如一个华丽的技能,释放的时候加载很多特性导致卡顿),那么可以提前加载好,然后隐藏。需要的时候直接显示。

9. GPU Instancing:对于相同的网格,假如要渲染1w个,那么就需要向GPU传1w次数据。如果使用合批,那么只需要传1次数据,但是传的数据量不变还是1w条数据。但是这1w条数据是一样的,所有完全可以传1条数据给GPU,然后GPU直接渲染1w个出来即可。

四、内存优化手段

1. 触发内存预警时,自动清理掉部分资源缓存。(主要是纹理,也就是图片)

2. 资源引用为0时,立刻清理掉资源。(但是这个资源可能很快又用到,需要根据情况来选择处理方式)

3. 尽可能减少粒子数量。

五、调试手段

1. 进游戏就黑屏,那就看本地的log文件。

2. Ios可以通过爱思软件看log文件。

3. 安卓可以直接连接adb查看游戏的实时log。

4. 本地调试真机代码的时候,可以直接用热更替换的方式进行调试。(因为改一次代码就打一次包或者补丁太慢了,直接热更调试快一些)

5. 对于收到热更代码前的调试(热更代码是服务端下发的,这里指服务端下发热更代码前),可以在游戏进入的时候,默认读取本地的一段代码文本直接执行。

6. Shader执行的时候没法加打印,如果想看数据可以用因特尔gpa查看。

六、调试技巧

1. 最直接的就是加打印看一个函数是否被调用,还有执行到这里时的数值。

2. 要定位一个功能的代码,可以先触发这个功能,然后看多了哪些打印,接着通过console选中打印往回追溯,就可以定位到触发这个功能的时候相关的代码走向。(类似断点,但是断点会把进程卡住,并且执行下一断点后,无法跳回去前一个断点查看)

3. 对于没有打印的功能,要先联想他最终结果的涉及的一些功能,例如npc走路的函数我不知道是在哪里,但是我知道它最终结果是npc走路,也就是必定会对npc执行SetPos函数,那我就可以重写它的SetPos函数,并加上打印,然后就可以通过console反查npc走路是怎么执行下来的。

4. 定位一些界面所在的代码位置时,可以直接ctrl+鼠标左键点击界面跳转到该界面对应的代码。(这个功能是自己实现的,其实只要监听点击,然后获取点击到的界面对象,就可以获取到这个对象的类,然后直接跳转ide)

5. 对于打印过多,但是只想分析一个对象时,可以设置符合这个对象的数据才打印。

6. 打印可以多加一些显眼的字母,方便我们找到自己的打印。(可以用console过滤)

七、其他问题

1. ios无端端杀进程,一般是游戏内存占用过高。

2. Pc上没问题,真机有问题,可以检查补丁是否包含修改内容,还有逻辑是否有环境判断。

3. 安卓没问题,ios有问题(或者安卓有问题,ios没问题),这种一般是资源问题较多,需要检查一下各个平台读取的资源是否一致,还要检查云效自动化生成的资源是有问题。

4. 不稳定的功能外放的时候,可以选择随机一定概率做灰度测试,这样就算出问题,玩家只要重登就有一定几率切换回正常的状态。

5. 外挂检测不应该检测出来就直接封号,检测出来之后应该推迟几天再封,这样让外挂制作人不方便调试。

6. 一个界面第一次打开不显示某图片,第二次打开正常显示,这种情况一般是异步加载导致的。异步加载资源加载完成之后没有设置回去或者还没加载完就调用设置函数。

7. 一个界面打开之后无法关闭,这种一般是界面的引用出问题,没有把引用归0导致的。

8. 游戏内存占用十分高,这一般是某些资源循环引用或者引擎打印了大量错误提示导致的。

9. GPU Instancing要求网格相同,相当于一份网格数据给到GPU绘制多次。合批不要求网格相同,他是把所有网格数据汇总起来一次性发给GPU,让GPU绘制。