本文简述了自己查找定位 Bug 过程中的一点思考
某日发现游戏(UE5)项目中出现了一个 Bug,表现大概是这样的:
怪物可以正常在地面上移动,但是在死亡时却会'诡异'的从地面上掉下去 ...
自己对于游戏项目的逻辑不是特别熟悉,大概跟踪了一下相关代码,发现怪物死亡时大体会走下面的逻辑流程:
- 首先开启怪物模型(Mesh)的物理碰撞
- SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics)
- 接着调整怪物模型(Mesh)的碰撞响应
- SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block)
- 最后开启怪物模型(Mesh)的物理模拟
- SetSimulatePhysics(true)
逻辑看上去还是挺完备的,似乎没有什么问题,但是几轮验证下来,确认只要关闭了上述逻辑,怪物的坠落问题就不存在了,所以一定是物理模拟引起了问题,但是问题究竟出在哪里呢 ? 一度让人费解 ...
好在这个 Bug 是可重现的(以我的经验,一般可重现的 Bug 都是能最终追溯到原因的,只是花费的时间长短不同),初步判断原因应该还是出在物理碰撞上,只是 UE 的碰撞处理相对复杂,跟踪物理碰撞的问题确实要花不少精力.
总之经历了多轮的 猜测验证, AI辅助, 断点调试 等操作,最终还是发现了问题点:
- SetShapeCollisionEnabled
你可能会有些疑惑,上述的逻辑流程中并没有什么 'SetShapeCollisionEnabled' 的调用,问题怎么会出在这个接口上呢 ?
原来在怪物初始化时,项目逻辑会对碰撞 Shape 调用 'SetShapeCollisionEnabled' 来与 怪物模型(Mesh) 本身的 CollisionEnabled 做同步,而怪物模型(Mesh)本身的 CollisionEnabled 是 ECollisionEnabled::QueryOnly,这就导致碰撞 Shape 的 CollisionEnabled 也变为了 ECollisionEnabled::QueryOnly.
接着当怪物死亡处理物理碰撞时,逻辑虽然会设置 怪物模型(Mesh) 的 CollisionEnabled 为 ECollisionEnabled::QueryAndPhysics,但是碰撞 Shape 的 CollisionEnabled 依然还是初始化时设置的 ECollisionEnabled::QueryOnly,而 UE 在处理碰撞 Shape(碰撞 Shape 参与实际的物理碰撞检测)最终的 CollisionEnabled 时做了"最小化"处理(即取 怪物模型(Mesh) 本身的 CollisionEnabled 和 碰撞 Shape 的 CollisionEnabled 的最小值),继而会取 碰撞 Shape 的 CollisionEnabled 为 ECollisionEnabled::QueryOnly,即碰撞 Shape 不参与物理模拟,于是就出现了前面所说的怪物坠落问题 ...
上述流程可能说的有些繁复,但是流程本身并不是重点,不太理解的朋友不必过多关注,我们进一步要问的是,出现这个 Bug 的根本原因在哪里 ? 有没有可能避免 ?
个人认为出现这个 Bug 的根本原因还是在于细节不明 ,一般而言,在 UE 的碰撞处理上,游戏逻辑其实是不太需要去主动调用 SetShapeCollisionEnabled 的,保持默认设置即可,但是项目在怪物初始化阶段,因为细节不明 ,主动调用了 SetShapeCollisionEnabled,导致了问题隐患;同时项目在怪物死亡时,因为细节不明,又仅仅设置了 怪物模型(Mesh) 的 CollisionEnabled,而没有去处理碰撞 Shape 的 CollisionEnabled,于是触发了问题.
至于能否避免这类 Bug,个人觉得还是比较困难,除了要求个人对所编写代码尽量了解清晰明确之外(很难保证也很难执行 ...),似乎没有什么好的方法,尤其现在 AI 编码盛行,相关问题可能还会被进一步放大 ...
至此我不禁想起了多年前另一个让我印象深刻的 Bug,这个 Bug 的表现是玩家会在某个多人副本游玩过程中出现'卡死'情况,这里说的'卡死'并不是指玩家死机了,而是指玩家的游戏界面状态会和实际游戏状态不一致,继而导致玩家无法继续游戏,并且这个'卡死'是概率性的,本地测试时极难重现,线上环境也是偶尔重现.
玩家的游戏界面状态不正确,首先想到的自然是客户端是不是处理同步信息不当,但实际上客户端相关代码并不是特别复杂,看上去也没有什么问题;然后考虑是不是服务器没有正确下发同步信息,但实际看下来相关代码也比较正常,再加上 Bug 的偶现性,这个问题就被搁置下来了 ...
问题一搁就搁了好久,直到后来反馈这个问题的玩家越来越多,我们才不得不尝试在相关代码中埋点,外加测试同学不断帮忙复现,最后终于是定位到了问题:
原来在游戏逻辑层面,客户端和服务器的代码其实都没有问题,问题出在服务器给客户端同步数据的接口上,这个接口的内部实现中做了最大同步数量的优化(即一次只能给一定数量的客户端同步),超过最大数量的同步请求会被直接丢弃(并且没有任何日志 ...),于是当游戏中一次同步包含大量玩家的时候,部分玩家就不会收到同步信息,于是便出现了问题(而一般的游戏流程中,基本不会触发上述的接口问题,而出问题的多人副本玩法本身比较特殊,所以容易触发这个问题).
细细想,这个 Bug 其实也可算是 细节不明 造成的 ...
有没有解决之道呢 ? 想想还是如之前所言,没有什么好的方法.可能还是应了那句老话吧 : 软件行业没有银弹,即便在这个 AI 洪流的年代,对于有些问题,我们还是 不可不慎, 不可不察 啊 ~