我是微信小游戏《猎头专家》的开发者之一。我们项目在小游戏平台已经上线两个多月,项目也通过日志错误收集一直在不修正完善中。但期间有一个问题一直没能找到根结原因:问题表现在,某个通过cc.Prefab生成的节点,在其生命周期内,通过它某个组件上引用的另一个cc.Prefab类型的property生成(cc.instantiate)另一个节点,但得到的节点却为空。通过错误日志收集,最后定位到该property为空。这个错误出现不算频繁,但一定会导致逻辑错误。
起初我怀疑是应用层逻辑错误导致偶现的在节点生命周期外使用其属性(在onDestroy时,组件属性会被置空),但近期我们自己在测试时,在某台安卓手机上偶然的重现了这个问题,根据显示的日志排查后发现,这个问题应该是由于cc.Prefab引用的另一个cc.Prefab(依赖项)加载失败导致的。
由于微信小游戏调试控制台大大的clear按钮太容易误点,具体错误堆栈未能及时截图或保存,但在clear之前有对日志做一定的分析,所以印象较为深刻,错误信息大体如下:
首先是两个 4916 报错,两个报错后各附有一个类似于fileid的字符串,但全局搜索后,并未发现哪里引用这两个字符串;
然后是五个错误对象,内容相同,大概是:
Object:{status:0,errorMessage:Load xxxxxx.json failed!}
其中,xxxxxx.json是我们项目中存在的某个AniamtionClip预制件对应的json文件,浏览器中输入相应的路径,可以加载到文件。
综合一下上述的错误信息:
-
Creator 版本:
1.9.0, 1.9.3 -
目标平台:
已知重现平台 WechatGame (Android) -
详细报错信息,未包含调用堆栈:
Object:{status:0,errorMessage:Load xxxxxx.json failed!} -
重现方式:
未发现完全的重线路径
经过查看引擎源码,我发现在加载prefab等资源时,如果加载依赖项失败,并未将错误传入回调函数中,从而导致应用层认为资源加载完成,并进行接下来的逻辑,而过个时候,引用依赖项的属性将为空。详见源码(为工具1.9.3版中的egine,未与github上获取的分支作对比)中 “uuid-loader.js” 第147行,依赖项加载错误仅使用了cc._throw抛出错误,并且cc._throw方法仅仅是打印了错误,并不能被应用层获取。
找到问题后我有针对该问题进行模拟重现,即在build后修改某个依赖项的文件名,使它不能被正确加载。模拟实验证时在依赖项加载未成功时,回调函数会被正常调用(调用接口加载的文件能正确加载)。《猎头专家》的资源加载被我封装后,在资源载失败时,会尝试重新加载,部分关键资源会无限次重试加载。但依赖项加载失败并不会进入重新加载的逻辑,而且加载的cc.Prefab能够正常通过cc.instantiate成生节点。但如果未加载成功的是一张图片,则该图片不能正确显示;如果未加载成功的是一个引用的cc.Prefab,则在使用该引用的时候会发现其为空。
定位问题后,我在cc._throw后增加了 return callback(item.error, []) 将加载错误通过回调返回,这样在进行上述模拟时,游戏进程因不断重试加载资源而卡住,直到我将名字改回,资源加载成功,游戏进程继续。
我很好奇源码中为何没有对加载依赖项失败做相应的处理,由于本人对http特性不是很熟,对Creator引擎了解也不够深入,所以也不能确认依赖项加载失败是否有着更深入的原因,期待引擎组可以就相关问题做出处理。
另外,喜欢我们《猎头专家》的朋友可以关注我们老大关于我们游戏项目的介绍帖:
微信小游戏从立项到上线!谈谈《猎头专家》的开发历程