CCTMXTiledMap 与热更机制的冲突
最近一个月在项目中新增加了类大富翁玩法,使用了 CCTMXTiledMap 来实现地图背景。由于热更资源的时候只提交了修改过的 tmx 地图数据文件,导致 CCTMXLayer 根据「相对路径」无法加载到地图图集(Tilesets)。解决方案有两种:
- 热更的时候连 tilesets 相关图片一起提交;
- 修复引擎底层加载图集的机制。
对于方法一,是比较好的临时补救方案,不需要重新提交软件包。通过现有热更机制更新脚本和资源即可修复。但不是个长久之计。
所幸这个问题是在内部测试的时候发现的,方案二的影响很少。所以就对 CCTMXTiledMap 底层的加载机制进行了一次跟踪。
¶Hot Update
热更的过程是将线上变更的文件下载到本地可写目录中(例如 iOS 中是 .app/Documents
),然后将其优先级置于原始目录之上。最后重新启动游戏,即可加载到最新资源。没有更新到的文件会 fallback 到原始目录上,所以可以读到旧的资源。
¶FileUtils
在 Cocos2d-x 3.0 之后,文件的读取通过 FileUtils 进行。游戏中多使用相对路径索引资源(res/path/to/file
),然后统一由 FileUtils 根据预置的根目录列表转换成绝对路径,以保证资源位置的正确性。
¶Tiled Map Editor
由于 Tiled Map Editor 允许资源以相对路径的方式进行资源管理,所以在早期的 Cocos2d-x 版本中 CCTMXTiledMap 需要自己处理相对路径。
¶Conclusion
显然 Cocos2d-x 的一些子系统并没有完全使用 FileUtils 来进行资源定位,而是自己私下处理一些「相对路径」生成绝对路径。而「绝对路径」传入 FileUtils 后不会再次经过处理,就导致了文章最开始出现的问题。
经过追踪,定位到 CCTMXXMLParser.cpp
作了如下处理:
void TMXMapInfo::internalInit(const std::string& tmxFileName, const std::string& resourcePath)
{
if (tmxFileName.size() > 0)
{
_TMXFileName = FileUtils::getInstance()->fullPathForFilename(tmxFileName);
}
// ...
这里获取了 tmx 文件的「绝对路径」,其后使用这一绝对路径与其它资源的「相对路径」拼出新的绝对路径来加载资源:
// ...
else if (elementName == "image")
{
TMXTilesetInfo* tileset = tmxMapInfo->getTilesets().back();
// build full path
std::string imagename = attributeDict["source"].asString();
if (_TMXFileName.find_last_of("/") != string::npos)
{
string dir = _TMXFileName.substr(0, _TMXFileName.find_last_of("/") + 1);
tileset->_sourceImage = dir + imagename;
}
else
{
tileset->_sourceImage = _resources + (_resources.size() ? "/" : "") + imagename;
}
}
于是如果 tmx 文件在热更目录中,而又没有同时热更 tilesets 图集时,tileset->_sourceImage
指向的文件实际上就不存在,导致游戏闪退。
¶Solution
所以我想到的解决方法是,internalInit
方法中并不需要获取 tmx 的「绝对路径」,直接使用 tmx 最初的「相对路径」即可:
void TMXMapInfo::internalInit(const std::string& tmxFileName, const std::string& resourcePath)
{
if (tmxFileName.size() > 0)
{
_TMXFileName = tmxFileName.substr();
}
// ...
这样处理后的路径仍然是「相对路径」,使用这样的路径可以被 FileUtils 正确解析,问题解决。