布景
前期进行了对cocos原生游戏调研,关于加载原生游戏有一个限制,也便是原生游戏的资源包存放的途径需求固定(也便是依照cocos的默许途径,iOS需求存放在main bundle下,android需求存放在Asset下),这也就带来了一个问题:原生游戏无法进行下载更新,只能每次经过将游戏资源包更新发版的办法进行游戏更新,这无疑是事务方不行接受的。基于这种状况,因而对cocos原生游戏的热更调研显得尤为重要。而且事务方关于游戏的加载耗时也是尤为关注的,也顺带把原生游戏预加载同时调研了。
热更
Cocos加载流程
思考
首要基于上次调研的成果:游戏离线包资源放在默许途径下,运用原生加载游戏的话,需求调用cocos引擎加载两个文件(/jsb-adapter/jsb-builtin.js和/main.js),在iOS调用办法如下:
se::ScriptEngine* se = se::ScriptEngine::getInstance();
se->start();
se::AutoHandleScope hs;
std::string g = std::string([gameId UTF8String]);
jsb_run_script(g+"/jsb-adapter/jsb-builtin.js");
jsb_run_script(g+"/main.js");
能够看到咱们传的是一个相对途径,那么cocos引擎内部,绝对是有对这个相对途径进行拼接处理的。因而,咱们能够根据这个jsb_run_script(const std::string& filePath, se::Value* rval = nullptr);办法的完结一步一步找到拼接途径的地方。
文件修正途径
- jsb_run_script(const std::string& filePath, se::Value* rval = nullptr)
bool jsb_run_script(const std::string& filePath, se::Value* rval/* = nullptr */)
{
se::AutoHandleScope hs;
return se::ScriptEngine::getInstance()->runScript(filePath, rval);
}
- 找到se::ScriptEngine::getInstance()->runScript(filePath, rval)的完结
bool ScriptEngine::runScript(const std::string& path, Value* ret/* = nullptr */)
{
assert(!path.empty());
assert(_fileOperationDelegate.isValid());
std::string scriptBuffer = _fileOperationDelegate.onGetStringFromFile(path);
if (!scriptBuffer.empty())
{
return evalString(scriptBuffer.c_str(), scriptBuffer.length(), ret, path.c_str());
}
SE_LOGE("ScriptEngine::runScript script %s, buffer is empty!n", path.c_str());
return false;
}
- 途径拼接是在_fileOperationDelegate.onGetStringFromFile(path);的完结的,首要咱们需求找到_fileOperationDelegate的赋值所在(搜索setFileOperationDelegate()调用)
void jsb_init_file_operation_delegate()
{
static se::ScriptEngine::FileOperationDelegate delegate;
if (!delegate.isValid())
{
...
delegate.onGetStringFromFile = [](const std::string& path) -> std::string{
assert(!path.empty());
std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT;
if (FileUtils::getInstance()->isFileExist(byteCodePath)) {
Data fileData = FileUtils::getInstance()->getDataFromFile(byteCodePath);
uint32_t dataLen;
uint8_t* data = xxtea_decrypt((uint8_t*)fileData.getBytes(), (uint32_t)fileData.getSize(), (uint8_t*)xxteaKey.c_str(), (uint32_t)xxteaKey.size(), &dataLen);
if (data == nullptr) {
SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str());
return "";
}
if (ZipUtils::isGZipBuffer(data,dataLen)) {
uint8_t* unpackedData;
ssize_t unpackedLen = ZipUtils::inflateMemory(data, dataLen,&unpackedData);
if (unpackedData == nullptr) {
SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str());
return "";
}
std::string ret(reinterpret_cast<const char*>(unpackedData), unpackedLen);
free(unpackedData);
free(data);
return ret;
}
else {
std::string ret(reinterpret_cast<const char*>(data), dataLen);
free(data);
return ret;
}
}
if (FileUtils::getInstance()->isFileExist(path)) {
return FileUtils::getInstance()->getStringFromFile(path);
}
else {
SE_LOGE("ScriptEngine::onGetStringFromFile %s not found, possible missing file.n", path.c_str());
}
return "";
};
delegate.onGetFullPath = [](const std::string& path) -> std::string{
assert(!path.empty());
std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT;
if (FileUtils::getInstance()->isFileExist(byteCodePath)) {
return FileUtils::getInstance()->fullPathForFilename(byteCodePath);
}
return FileUtils::getInstance()->fullPathForFilename(path);
};
delegate.onCheckFileExist = [](const std::string& path) -> bool{
assert(!path.empty());
return FileUtils::getInstance()->isFileExist(path);
};
assert(delegate.isValid());
}
se::ScriptEngine::getInstance()->setFileOperationDelegate(delegate);
}
- 找到FileUtils::getInstance()->isFileExist(path);,也便是FileUtils管理文件途径
- 找到FileUtils的isFileExist完结
std::string FileUtils::getStringFromFile(const std::string& filename)
{
std::string s;
getContents(filename, &s);
return s;
}
- 找到FileUtils的getContents完结
FileUtils::Status FileUtils::getContents(const std::string& filename, ResizableBuffer* buffer)
{
if (filename.empty())
return Status::NotExists;
auto fs = FileUtils::getInstance();
std::string fullPath = fs->fullPathForFilename(filename);
if (fullPath.empty())
return Status::NotExists;
FILE *fp = fopen(fs->getSuitableFOpen(fullPath).c_str(), "rb");
if (!fp)
return Status::OpenFailed;
#if defined(_MSC_VER)
auto descriptor = _fileno(fp);
#else
auto descriptor = fileno(fp);
#endif
struct stat statBuf;
if (fstat(descriptor, &statBuf) == -1) {
fclose(fp);
return Status::ReadFailed;
}
size_t size = statBuf.st_size;
buffer->resize(size);
size_t readsize = fread(buffer->buffer(), 1, size, fp);
fclose(fp);
if (readsize < size) {
buffer->resize(readsize);
return Status::ReadFailed;
}
return Status::OK;
}
- 找到FileUtils的fullPathForFilename完结
std::string FileUtils::fullPathForFilename(const std::string &filename) const
{
if (filename.empty())
{
return "";
}
if (isAbsolutePath(filename))
{
return normalizePath(filename);
}
// Already Cached ?
auto cacheIter = _fullPathCache.find(filename);
if(cacheIter != _fullPathCache.end())
{
return cacheIter->second;
}
// Get the new file name.
const std::string newFilename( getNewFilename(filename) );
std::string fullpath;
for (const auto& searchIt : _searchPathArray)
{
for (const auto& resolutionIt : _searchResolutionsOrderArray)
{
fullpath = this->getPathForFilename(newFilename, resolutionIt, searchIt);
if (!fullpath.empty())
{
// Using the filename passed in as key.
_fullPathCache.insert(std::make_pair(filename, fullpath));
return fullpath;
}
}
}
if(isPopupNotify()){
CCLOG("fullPathForFilename: No file found at %s. Possible missing file.", filename.c_str());
}
// The file wasn't found, return empty string.
return "";
}
- 发现途径拼接其实是拿_searchPathArray的内容进行拼接的。
- 找到对_searchPathArray入栈的地方(全局搜索_searchPathArray),最终找到void FileUtils::setSearchPaths(const std::vectorstd::string& searchPaths)
void FileUtils::setSearchPaths(const std::vector<std::string>& searchPaths)
{
bool existDefaultRootPath = false;
_originalSearchPaths = searchPaths;
_fullPathCache.clear();
_searchPathArray.clear();
for (const auto& path : _originalSearchPaths)
{
std::string prefix;
std::string fullPath;
if (!isAbsolutePath(path))
{ // Not an absolute path
prefix = _defaultResRootPath;
}
fullPath = prefix + path;
if (!path.empty() && path[path.length()-1] != '/')
{
fullPath += "/";
}
if (!existDefaultRootPath && path == _defaultResRootPath)
{
existDefaultRootPath = true;
}
_searchPathArray.push_back(fullPath);
}
if (!existDefaultRootPath)
{
//CCLOG("Default root path doesn't exist, adding it.");
_searchPathArray.push_back(_defaultResRootPath);
}
}
- 同时,setSearchPaths的办法声明也验证了咱们的猜想
/**
* Sets the array of search paths.
*
* You can use this array to modify the search path of the resources.
* If you want to use "themes" or search resources in the "cache", you can do it easily by adding new entries in this array.
*
* @note This method could access relative path and absolute path.
* If the relative path was passed to the vector, FileUtils will add the default resource directory before the relative path.
* For instance:
* On Android, the default resource root path is "@assets/".
* If "/mnt/sdcard/" and "resources-large" were set to the search paths vector,
* "resources-large" will be converted to "@assets/resources-large" since it was a relative path.
*
* @param searchPaths The array contains search paths.
* @see fullPathForFilename(const char* ) * @since v2.1 * In js:var setSearchPaths(var jsval); * @lua NA */
virtual void setSearchPaths(const std::vector<std::string>& searchPaths);
- 在Demo里边验证,把原生游戏资源文件存放在沙盒里边,然后经过setSearchPaths设置文件目录,看游戏是否能够加载成功。
- (void)initCocosEngine {
float scale = [[UIScreen mainScreen] scale];
CGRect bounds = [[UIScreen mainScreen] bounds];
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *fileFolderPath = [docDir stringByAppendingFormat:@"/hhhh"];
std::string g = std::string([fileFolderPath UTF8String]);
std::vector<std::string> paths;//创建一个string型的容器
// paths.push_back("hhhh");//往容器中增加图片目录所在的途径
paths.push_back(g);//往容器中增加图片目录所在的途径
cocos2d::FileUtils::getInstance()->setSearchPaths(paths);
app = new CocosAppDelegate(bounds.size.width * scale, bounds.size.height * scale);
app->setMultitouch(true);
//run the cocos2d-x game scene
app->start();
}
- 成功加载游戏。原生游戏热更完结。
游戏资源引证办法
之前因为调研以为游戏资源只能放在main bundle下,所以运用了pod组件导入游戏资源的办法。现在游戏资源能够放在恣意途径,因而原生游戏资源的引证办法能够参照之前webview加载的办法。经过资源包让事务导入到工程中即可。
以下为iOS游戏资源层级
sealSource.bundle //bundle资源包
└── web //webview烘托资源包
└── 5206662980335600255.zip
└── 5237049012831387775.zip
└── native //原生烘托游戏资源包
└── 5206662980335600255.zip
└── 5237049012831387775.zip
└── config.txt //版别配置文件
以下为config.txt的内容:
{
"web": {
"5237049012831387775": {
"version": 10203,
"versionStr": "1.2.3"
},
"5206662980335600255": {
"version": 10101,
"versionStr": "1.1.1"
}
},
"native": {
"5237049012831387775": {
"version": 10203,
"versionStr": "1.2.3"
},
"5206662980335600255": {
"version": 10101,
"versionStr": "1.1.1"
}
}
}
游戏资源沙盒存放
gameSource //游戏资源
└── web //webview烘托资源包
└── 5206662980335600255
└── 10101
└── 资源文件...
└── 5237049012831387775
└── 10203
└── 资源文件...
└── native //原生烘托游戏资源包
└── 5206662980335600255
└── 10101
└── 资源文件...
└── 5237049012831387775
└── 10203
└── 资源文件...
预加载
以下仅为iOS的方案
过程
能够复用之前webview预加载的办法去完结原生预加载
- 在preLoadJYGame办法内部去load对应的原生游戏(在这里需求注意,原生的cocosview需求增加到view上,而且CocosAppManager.shareInstance().loadGame(“ludo”);需求异步履行)
let preView = UIView(frame: UIScreen.main.bounds)
UIApplication.shared.windows.last?.addSubview(preView)
preView.isHidden = true
let gv: UIView = CocosAppManager.shareInstance().getCocosView()
gv.frame = UIScreen.main.bounds
preView.addSubview(gv)
DispatchQueue.main.async {
CocosAppManager.shareInstance().loadGame("ludo");
}
- 需求和游戏洽谈,经过jsb协议办法奉告游戏方,当前加载为预加载(之前webview加载是直接经过url途径拼接参数,原生加载需求经过办法奉告)