沒想到我上篇博文還在寫作中阅仔,cocos就出了新版3.15,好像對(duì)音頻播放做了優(yōu)化弧械,挺好的八酒,但是熱更新也修改了點(diǎn),好吧刃唐,其實(shí)我之前修改了它的熱更新機(jī)制丘跌,實(shí)現(xiàn)序列化更新,就是1.0.0 更新到1.0.1然后再更新到1.0.2唁桩。好吧,這個(gè)方法的缺陷就是不能刪除以前不要的文件耸棒。但是cocos自己的新的更新機(jī)制也沒有去刪除不要的文件啊荒澡,-_-,看下面的代碼:
if (diff.type != Manifest::DiffType::DELETED)
{
std::string path = diff.asset.path;
DownloadUnit unit;
unit.customId = it->first;
unit.srcUrl = packageUrl + path;
unit.storagePath = _tempStoragePath + path;
unit.size = diff.asset.size;
_downloadUnits.emplace(unit.customId, unit);
_tempManifest->setAssetDownloadState(it->first,Manifest::DownloadState::UNSTARTED);
}
唯一用到Manifest::DiffType::DELETED
的地方。就是如果我們的asset資源如果不是刪除狀態(tài)与殃,那么我們記錄需要下載单山。
3.15下載文件如果需要解壓縮,那么直接解壓縮幅疼,3.14.1還是添加到一個(gè)待解壓縮列表米奸,于是我在3.14.1中做了個(gè)修改,讓他們安裝順序填入map中爽篷,然后依次解壓縮悴晰,后面解壓的覆蓋前面,這樣我們就實(shí)現(xiàn)了跨版本序列化更新≌∠可是3.15的改動(dòng)導(dǎo)致我得重新修改很多代碼漂辐。
cocos的本意是所有的src和res的每個(gè)文件都是單獨(dú)的assets資源,這樣可以改動(dòng)棕硫,或添加的文件就會(huì)下載更新髓涯,但是這樣的話,打包更新包麻煩哈扮,每個(gè)文件都需要單獨(dú)打包纬纪,就算寫腳本處理也覺得不大靠譜,更何況之前遇到的游戲更新滑肉,大家都是一個(gè)版本一個(gè)版本的更新上去的包各,并不是直接跳到某個(gè)版本,亦或是我覺得這樣更新不爽吧赦邻,我覺得還是一個(gè)版本一個(gè)版本更新舒服一點(diǎn)髓棋。哈哈。惶洲。按声。
好,那我們先開始修改源碼恬吕。
找到 AssetsManagerEx.cpp:
改動(dòng)1
添加代碼:
#define MAX_DOWNLOAD_TASK 1
改動(dòng)2
network::DownloaderHints hints =
{
static_cast<uint32_t>(_maxConcurrentTaskK),
DEFAULT_CONNECTION_TIMEOUT,
".tmp"
};
改成
network::DownloaderHints hints =
{
static_cast<uint32_t>(/*_maxConcurrentTask*/MAX_DOWNLOAD_TASK),
DEFAULT_CONNECTION_TIMEOUT,
".tmp"
};
改動(dòng)3
while (_currConcurrentTask < _maxConcurrentTask && _queue.size() > 0)
改成
while (_currConcurrentTask < /*_maxConcurrentTask*/MAX_DOWNLOAD_TASK && _queue.size() > 0)
意思很簡(jiǎn)單签则,我們下載任務(wù)只執(zhí)行一個(gè)!n砹稀渐裂!
c++代碼修改完成。
那我們先下載Tomcat钠惩,安裝完成后柒凉,打開開始菜單的
如果你能看到下面的圖片:
說(shuō)明你已經(jīng)安裝成功了。好先放一邊篓跛。膝捞。。
創(chuàng)建一個(gè)cocos js工程愧沟,
- 下載安裝cocos3.15蔬咬。
- 下載安裝python2.7。
-
打開cocos3.15環(huán)境沐寺,我的是:
cocos3.15安裝環(huán)境 - 執(zhí)行
setup.py
林艘,根據(jù)提示,部署環(huán)境混坞。 - 運(yùn)行cmd狐援,執(zhí)行腳本
cocos new -p com.hangzhou.test -d D:\xxx -l js
,最好事先創(chuàng)建目錄xxx
目錄,xxx
是你希望項(xiàng)目存放的目錄 - 下載安裝webstorm
- 用webstorm打開我們的xxx目錄。
修改我們的代碼
在src目錄新建兩個(gè)文件:updateList.js和assetsManagerScene.js文件
文件內(nèi)容是:
updateList.js 用于保存我們的更新js列表
var jsUpdateList = [
"src/app.js",
"src/resource.js"
];
assetsManagerScene.js 熱更新場(chǎng)景
/**
* 熱更新
*/
var failCount = 0;
var maxFailCount = 0; //最大錯(cuò)誤重試次數(shù)
/**
* 自動(dòng)更新js和資源
*/
var AssetsManagerLoaderScene = cc.Scene.extend({
_am: null,
_progress: null,
_percent: 0,
run: function () {
if (!cc.sys.isNative) {
this.loadGame();
return;
}
var layer = new cc.Layer();
this.addChild(layer);
this._progress = new cc.LabelTTF.create("update____0%", "Arial", 12);
this._progress.x = cc.winSize.width / 2;
this._progress.y = cc.winSize.height / 2 + 50;
layer.addChild(this._progress);
var storagePath = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : "./");
cc.log("storagePath is " + storagePath);
this._am = new jsb.AssetsManager("res/Manifests/project.manifest", storagePath);
this._am.retain();
if (!this._am.getLocalManifest().isLoaded()) {
cc.log("Fail to update assets, step skipped.");
this.loadGame();
} else {
var that = this;
//cc.EventListenerAssetsManager
var listener = new jsb.EventListenerAssetsManager(this._am, function (event) {
switch (event.getEventCode()) {
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
cc.log("assetsManagerScene : ERROR_NO_LOCAL_MANIFEST " + event.getMessage());
that.loadGame();
break;
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
cc.log("assetsManagerScene : ERROR_DOWNLOAD_MANIFEST " + event.getMessage());
that.loadGame();
break;
case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
cc.log("assetsManagerScene : ERROR_PARSE_MANIFEST " + event.getMessage());
that.loadGame();
break;
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
cc.log("assetsManagerScene : NEW_VERSION_FOUND " + event.getMessage());
//我們需要更新咕村。场钉。。
break;
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
cc.log("assetsManagerScene : ALREADY_UP_TO_DATE " + event.getMessage());
//我們不需要更新懈涛。逛万。。
that.loadGame();
break;
case jsb.EventAssetsManager.UPDATE_PROGRESSION://更新進(jìn)度條
cc.log("assetsManagerScene : UPDATE_PROGRESSION " + event.getPercent() + "," + event.getMessage());
that._percent = event.getPercent();
var msg = event.getMessage();
if (msg) {
cc.log(msg);
}
that.updateProgress(event.getPercent());
break;
case jsb.EventAssetsManager.ASSET_UPDATED://資源更新完畢批钠,可能還需要解壓縮
cc.log("assetsManagerScene : ASSET_UPDATED " + event.getAssetId() + "," + event.getMessage());
break;
case jsb.EventAssetsManager.ERROR_UPDATING://更新發(fā)生錯(cuò)誤
cc.log("assetsManagerScene : ERROR_UPDATING " + event.getAssetId() + ", " + event.getMessage());
that.loadGame();
break;
case jsb.EventAssetsManager.UPDATE_FINISHED://資源更新完畢
cc.log("assetsManagerScene : UPDATE_FINISHED " + event.getMessage());
that.loadGame();
break;
case jsb.EventAssetsManager.UPDATE_FAILED://更新失敗
cc.log("assetsManagerScene : UPDATE_FAILED " + event.getMessage());
failCount++;
if (failCount < maxFailCount) {
that._am.downloadFailedAssets();
}
else {
cc.log("Reach maximum fail count, exit update process");
failCount = 0;
that.loadGame();
}
break;
case jsb.EventAssetsManager.ERROR_DECOMPRESS://解壓錯(cuò)誤
cc.log("assetsManagerScene : ERROR_DECOMPRESS " + event.getMessage());
that.loadGame();
break;
default:
cc.log("assetsManagerScene : unknown Event" + event.getMessage());
that.loadGame();
break;
}
});
cc.eventManager.addListener(listener, 1);
this._am.update();
cc.director.runScene(this);
}
},
loadGame: function () {
//jsList是jsList.js的變量宇植,記錄全部js。
if (cc.sys.isNative) {
cc.loader.loadJs("", ["src/updateList.js"], function (error) {
if (error)
console.log("load js src/updateList.js error=" + error);
else
console.log("load js src/updateList.js success");
// cc.loader.loadImg() // 圖片放后邊再說(shuō)
cc.loader.loadJs("", jsUpdateList, function (error) {
if (error)
console.log("load js error=" + error);
else
console.log("load js success");
cc.director.runScene(new HelloWorldScene());
});
});
}
},
updateProgress: function (percent) {
this._progress.string = "update__" + percent + "__%";
},
onExit: function () {
cc.log("AssetsManager::onExit");
this._am.release();
this._super();
}
});
修改我們的main.js文件:
cc.LoaderScene.preload(g_resources, function () {
cc.director.runScene(new HelloWorldScene());
}, this);
改成:
// cc.LoaderScene.preload(g_resources, function () {
// cc.director.runScene(new HelloWorldScene());
//
// }, this);
var scene = new AssetsManagerLoaderScene();
scene.run();
修改project.json埋心,改成這樣
"jsList" : [
"src/assetsManagerScene.js"
]
在res/Manifests目錄下添加一個(gè)版本文件:project.manifest
{
"packageUrl": "http://192.168.1.148:8080/cocos/test/",
"remoteManifestUrl": "http://192.168.1.148:8080/cocos/test/project.manifest",
"remoteVersionUrl": "http://192.168.1.148:8080/cocos/test/version.manifest",
"engineVersion": "3.15",
"version": "1.0.0",
"assets": {
},
"searchPaths": [
"update"
]
}
最終webstorm的目錄結(jié)構(gòu)是:
打開vs項(xiàng)目
運(yùn)行項(xiàng)目指郁,我們可以看到我們的更新場(chǎng)景:
大約4秒后會(huì)呈現(xiàn)我們的游戲場(chǎng)景
由于我們還沒配置我們的熱更新服務(wù)器,所以有4秒的超時(shí)等待時(shí)間拷呆。
下面配置我們的熱更新服務(wù)器:
打開Apache安裝目錄闲坎,我的是在C:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps
,新建cocos目錄茬斧,再在里面創(chuàng)建test目錄腰懂。
得到這樣一個(gè)目錄:C:\Program Files\Apache Software Foundation\Tomcat 9.0\webapps\cocos\test
創(chuàng)建目錄update,和project.manifest文件和version.manifest文件
結(jié)果圖:
project.manifest就是我上面講的文件项秉,內(nèi)容一樣的绣溜。
version.manifest內(nèi)容如下:
{
"forceUpdate": true
"packageUrl": "http://192.168.1.148:8080/cocos/test/",
"remoteManifestUrl": "http://192.168.1.148:8080/cocos/test/project.manifest",
"remoteVersionUrl": "http://192.168.1.148:8080/cocos/test/version.manifest",
"engineVersion": "3.15",
"version": "1.0.0"
}
再運(yùn)行我們的程序,發(fā)現(xiàn)我們很快就從更新場(chǎng)景到游戲場(chǎng)景了BΠ2烙鳌!
下面進(jìn)行我們的服務(wù)器配置熱更新
拷貝一份app.js到test岁诉,修改一行代碼:
var helloLabel = new cc.LabelTTF("Hello World", "Arial", 38);
改成
var helloLabel = new cc.LabelTTF("Hello World 1", "Arial", 38);
新建src目錄拷貝app.js到里面锚沸,然后添加到壓縮包。起名app_js_1.0.1.zip:
修改我們?cè)趖est目錄的project.manifest和version.manifest:
再次運(yùn)行我們的程序涕癣,OK哗蜈,見到我們的Hello World 1了!J艋!
下面候生,我們重復(fù)操作同眯,把上面那段顯示文字改成Hello World 2
,Hello World 3
,每個(gè)都創(chuàng)建一個(gè)壓縮包:
壓縮包都一樣都是包含一個(gè)src文件夾,里面存一個(gè)app.js文件唯鸭。
繼續(xù)修改我們的project.manifest和version.manifest文件:
重新運(yùn)行我們的程序须蜗!如愿以償看到我們的Hello World 3
最后要說(shuō)的話
這里cocos有一個(gè)坑,
"assets": {
"update3": {
"path": "update/app.js_1.0.3.zip",
"md5": "9521D6B2176D667E22DC4F8345AE96E5",
"compressed": true
},
"update2": {
"path": "update/app.js_1.0.2.zip",
"md5": "940002A15EA29516E3AC45AE073BE66C",
"compressed": true
},
"update1": {
"path": "update/app.js_1.0.1.zip",
"md5": "88AA0BC4F6D9FE04A8532859674DE321",
"compressed": true
}
}
我們所有的assets更新文件必須是倒序的,因?yàn)閏ocos執(zhí)行下載的時(shí)候是倒著取的明肮,
while (_currConcurrentTask < /*_maxConcurrentTask*/MAX_DOWNLOAD_TASK && _queue.size() > 0)
{
std::string key = _queue.back();//這里從back取下載的key菱农,也就是我們的update1,update2柿估,update3
_queue.pop_back();
_currConcurrentTask++;
DownloadUnit& unit = _downloadUnits[key];
_fileUtils->createDirectory(basename(unit.storagePath));
_downloader->createDownloadFileTask(unit.srcUrl, unit.storagePath, unit.customId);
_tempManifest->setAssetDownloadState(key, Manifest::DownloadState::DOWNLOADING);
}
限制單個(gè)下載也是這個(gè)原因循未,我們?nèi)绻鄠€(gè)任務(wù)同時(shí)下載,可能會(huì)導(dǎo)致覆蓋文件出現(xiàn)問(wèn)題秫舌,可能導(dǎo)致我們的Hello World 3
不能顯示的妖,可能最后只顯示Hello World 1
硫戈。
后續(xù)問(wèn)題
后面發(fā)現(xiàn)文件多了泥彤,更新的下來(lái)的zip包也是多線程解壓縮的,责静,然后可能會(huì)存在舊的壓縮包解壓出來(lái)的東西替換最新的壓縮包解壓出來(lái)的墨缘,星虹,尷尬。镊讼。宽涌。可能還是只能支持整包更新吧狠毯。护糖。〗浪桑看了官方github上提供的manifest生成工具嫡良,它的思路就是把你所有的文件都記錄在案,沒有的献酗,自己去下載寝受。可能這樣是目前最好的一種方法了罕偎。附上官方的manifest生成工具,使用之前需要安裝nodejs