TS版本热更新有Demo吗?

搜索过论坛了,没有找到相关问题。
最近刚开始写TS脚本,TS版本的热更新应该怎么写?用论坛现有的JS版本试着翻译了下,但是里面有很多代码在TS环境报错,找不到相应的函数。很多东西也不知道怎么翻译,不知道有没有TS版本的热更新实例可以学习一下?

1赞

你这手伸的有点长,Js 改成ts,你看十几分钟官方文档中的ts写法教程就会了

真心没改出来,一堆报错,还有类型不知道该怎么改。现在ts的class可以直接用吗?还是必须得用装饰器的ccclass

你要挂载到节点上就必须用类装饰器。不挂载,那就随便用

HotUpdateMamager.ts.zip (2.6 KB)

1赞

我在10.3上写过

仅供参考,不是完整工程,当时测试是通过的。

const {ccclass, property} = cc._decorator;

@ccclass
export default class HotUpdate extends cc.Component {
    @property(cc.ProgressBar)
    private updateProgressBar: cc.ProgressBar = null;

    @property(cc.Label)
    private tipUpLabel: cc.Label = null;

    @property(cc.Label)
    private progressLabel: cc.Label = null;

    @property(cc.Label)
    private tipDownLabel: cc.Label = null;

    @property({type: cc.Asset})
    private manifestUrl: cc.Asset = null;

    private _checkCallBack: (err: any, result?: any) => void = null;

    private _storagePath: string = "";

    private _assetsManager: jsb.AssetsManager = null;

    private _updating: boolean = false;
    private _canRetry: boolean = false;

    private _checkListener: jsb.EventListenerAssetsManager = null;
    private _updateListener: jsb.EventListenerAssetsManager = null;

    public async checkUpdate(callback: (err: any, result?: any) => void) {
        this._checkCallBack = callback;
        if (this._updating) {
            return;
        }
        this.tipUpLabel.string = "check update";
        if (this._assetsManager.getState() === jsb.AssetsManager.State.UNINITED) {
            let url = this.manifestUrl.nativeUrl;
            console.info("check manifestUrl is: ", url);
            const loader = cc.loader as any;
            if (loader.md5Pipe) {
                url = loader.md5Pipe.transformURL(url);
            }
            this._assetsManager.loadLocalManifest(url);
        }
        if (!this._assetsManager.getLocalManifest() || !this._assetsManager.getLocalManifest().isLoaded()) {
            // TODO: 错误处理
            console.error("Failed to load local manifest ...");
            return;
        }
        this._checkListener = new jsb.EventListenerAssetsManager(this._assetsManager, this.innerCheckCallBack.bind(this));
        cc.eventManager.addListener(this._checkListener, 1);

        this._assetsManager.checkUpdate();
        this._updating = true;
    }

    protected onLoad() {
        if (!cc.sys.isNative) {
            return;
        }
        this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : "/") + "bingo_remote_assets");
        console.info("Storage path for remote assets : " + this._storagePath);
        this._assetsManager = new jsb.AssetsManager("", this._storagePath, this.versionCompare.bind(this));
        this._assetsManager.setVerifyCallback((path: string, asset: any) => {
            // When asset is compressed, we don"t need to check its md5, because zip file have been deleted.
            const compressed = asset.compressed;
            // Retrieve the correct md5 value.
            const expectedMD5 = asset.md5;
            // asset.path is relative path and path is absolute.
            const relativePath = asset.path;
            // The size of asset file, but this value could be absent.
            // const size = asset.size;
            if (compressed) {
                console.info("Verification passed : " + relativePath);
                return true;
            } else {
                return true;
            }
        });
        if (cc.sys.os === cc.sys.OS_ANDROID) {
            // Some Android device may slow down the download process when concurrent tasks is too much.
            // The value may not be accurate, please do more test and find what"s most suitable for your game.
            this._assetsManager.setMaxConcurrentTask(2);
        }
    }

    protected start() {
        //
    }

    protected onDestroy() {
        if (this._updateListener) {
            cc.eventManager.removeListener(this._updateListener);
            this._updateListener = null;
        }
        if (this._checkListener) {
            cc.eventManager.removeListener(this._checkListener);
            this._checkListener = null;
        }
    }

    private retryDownLoadFailedAssets() {
        if (!this._updating && this._canRetry) {
            // TODO: 弹框提示重试, 回调执行重试
            console.info("hot update retry");
            this._canRetry = false;
            this._assetsManager.downloadFailedAssets();
        }
    }

    /**
     * 启动热更
     *
     * @private
     * @memberof HotUpdate
     */
    private hotUpdate() {
        if (this._assetsManager && !this._updating) {
            this._updateListener = new jsb.EventListenerAssetsManager(this._assetsManager, this.updateCallBack.bind(this));
            cc.eventManager.addListener(this._updateListener, 1);
            if (this._assetsManager.getState() === jsb.AssetsManager.State.UNINITED) {
                let url = this.manifestUrl.nativeUrl;
                console.info("update manifestUrl is: ", url);
                const loader = cc.loader as any;
                if (loader.md5Pipe) {
                    url = loader.md5Pipe.transformURL(url);
                }
                this._assetsManager.loadLocalManifest(url);
            }
            this._assetsManager.update();
            this._updating = true;
            this.tipUpLabel.string = "updating...";
        }
    }

    /**
     * 版本比较
     *
     * @private
     * @param {string} localVersion
     * @param {string} remoteVersion
     * @returns
     * @memberof HotUpdate
     */
    private versionCompare(localVersion: string, remoteVersion: string): number {
        console.info("localVersion is :", localVersion, "remoteVersion is :", remoteVersion);
        const localVer: string[] = localVersion.split(".");
        const remoteVer: string[] = remoteVersion.split(".");
        for (let i = 0; i < localVer.length; i++) {
            const lv = parseInt(localVer[i], 10);
            const rv = parseInt(remoteVer[i] || "0", 10);
            if (lv === rv) {
                continue;
            } else {
                return lv - rv;
            }
        }
        if (localVer.length > remoteVer.length) {
            return -1;
        } else {
            return 0;
        }
    }

    /**
     * 检查热更新回调
     *
     * @private
     * @param {cc.Event.EventTouch} event
     * @returns
     * @memberof HotUpdate
     */
    private innerCheckCallBack(event: any): void {
        let haveNewVersion = false;
        console.info("check code: " + event.getEventCode());
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                console.info("No local manifest file found, hot update skipped.");
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                console.info("Fail to download manifest file, hot update skipped.");
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                console.info("Already up to date with the latest remote version.");
                break;
            case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                haveNewVersion = true;
                console.info("New version found, begin to update.");
                break;
            default:
                return;
        }
        cc.eventManager.removeListener(this._checkListener);
        this._checkListener = null;
        this._updating = false;
        if (!haveNewVersion) {
            if (this._checkCallBack) {
                this._checkCallBack(null);
            }
        } else {
            this.hotUpdate();
            this.updateProgressBar.progress = 0;
        }
    }

    /**
     * 更新回调
     *
     * @private
     * @param {*} event
     * @memberof HotUpdate
     */
    private updateCallBack(event: any): void {
        let needRestart = false;
        let failed = false;
        console.info("update code: " + event.getEventCode());
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                console.info("No local manifest file found, hot update skipped.");
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                this.updateProgressBar.progress = event.getPercent(); // byteProgress
                const downloadedMegabyte = event.getDownloadedBytes() / (1024 * 1024);
                const totalMegabyte = event.getTotalBytes() / (1024 * 1024);
                // 以 MB 为单位
                this.progressLabel.string = ` ${downloadedMegabyte.toFixed(2)}MB / ${totalMegabyte.toFixed(2)}MB`;
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                console.info("Fail to download manifest file, hot update skipped.");
                failed = true;
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                console.info("Already up to date with the latest remote version.");
                failed = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                console.info("Update finished. " + event.getMessage());
                needRestart = true;
                break;
            case jsb.EventAssetsManager.UPDATE_FAILED:
                console.info("Update failed. " + event.getMessage());
                this._updating = false;
                this._canRetry = true;
                this.retryDownLoadFailedAssets();
                break;
            case jsb.EventAssetsManager.ERROR_UPDATING:
                console.info("Asset update error: " + event.getAssetId() + ", " + event.getMessage());
                break;
            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                console.info(event.getMessage());
                break;
            default:
                break;
        }

        if (failed) {
            cc.eventManager.removeListener(this._updateListener);
            this._updateListener = null;
            this._updating = false;
        }

        if (needRestart) {
            cc.eventManager.removeListener(this._updateListener);
            this._updateListener = null;
            // Prepend the manifest"s search path
            const searchPaths = jsb.fileUtils.getSearchPaths();
            console.info("befare restart searchPaths:", JSON.stringify(searchPaths));
            const newPaths = this._assetsManager.getLocalManifest().getSearchPaths();
            console.info("befare restart newPaths:", JSON.stringify(newPaths));
            Array.prototype.unshift.apply(searchPaths, newPaths);
            // This value will be retrieved and appended to the default search path during game startup,
            // please refer to samples/js-tests/main.js for detailed usage.
            // !!! 搜索路径记得 main.js 里也要写,否则不好用
            cc.sys.localStorage.setItem("HOT_UPDATE_SEARCH_PATHS", JSON.stringify(searchPaths));
            jsb.fileUtils.setSearchPaths(searchPaths);

            cc.audioEngine.stopAll();
            cc.game.restart();
        }
    }
    // TODO: 二进制包更新后,清理热更新目录
    // https://github.com/pandamicro/creator-docs/blob/v1.4/source/zh/advanced-topics/assets-manager.md
    // jsb.fileUtils.removeDirectory(storagePath);
}

2赞

const customManifestStr = JSON.stringify({
“version”: “0.0.0.0”,
“packageUrl”: “http://192.168.2.164:12547”,
“remoteManifestUrl”: “http://192.168.2.164:12547/project.manifest”,
“remoteVersionUrl”: “http://192.168.2.164:12547/version.manifest”,
“assets”: {},
“searchPaths”: []
});

const VERSION_MANIFEST = ‘version.manifest’;
const PROJECT_MANIFEST = ‘project.manifest’;

export class HotUpdateMgr {

protected _storagePath: string;
protected _assetMgr : jsb.AssetsManager;
public manifestUrl: cc.Asset;
protected _localVersion: GVersion = null;

/** state/check/handler */
protected _checkListener;
protected _updateListener;
protected _updating: boolean = false;
protected _canRetry: boolean = false;
protected _failCount: number = 0;
protected _isNewApp: boolean = false;

protected _handler: HotUpdateInterface = null;

public prepare(manifesurl: cc.Asset, handler: HotUpdateInterface) {
    this.manifestUrl = manifesurl;
    this._handler = handler;
    if(!cc.sys.isNative)
        return false;
    this._storagePath = (jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + 'tcg-remote-asset';
    cc.log('Storage path for remote asset : ' + this._storagePath);

    // Init asset manager;
    let url = this.manifestUrl.nativeUrl;
    if(cc.loader.md5Pipe) {
        url = cc.loader.md5Pipe.transformURL(url);
    }

    this._assetMgr = new jsb.AssetsManager(url, this._storagePath, this.versionCompareCb.bind(this));
    
    // Setup the verification callback, but we don't have md5 check function yet, so only print some message
    // Return true if the verification passed, otherwise return false
    this._assetMgr.setVerifyCallback(this.verifyCb.bind(this));

    if (cc.sys.os === cc.sys.OS_ANDROID) {
        // Some Android device may slow down the download process when concurrent tasks is too much.
        // The value may not be accurate, please do more test and find what's most suitable for your game.
        this._assetMgr.setMaxConcurrentTask(2);
    }
}

/**
 * 
 * @param versionA 旧版本
 * @param versionB 新版本
 */
protected versionCompareCb(versionA: string, versionB: string) {
    cc.log("JS Custom Version Compare: version A is " + versionA + ', version B is ' + versionB);
    var vA = versionA.split('.');
    var vB = versionB.split('.');
    for (var i = 0; i < vA.length; ++i) {
        var a = parseInt(vA[i]);
        var b = parseInt(vB[i] || '0');
        if (a === b) {
            continue;
        }
        else {
            if(i == 0) { //'a.b.c.d', if diff a, app verison change, to get a new version of the app.
                this._isNewApp = true;
                return 0;
            } 
            return a - b;
        }
    }
    if (vB.length > vA.length) {
        return -1;
    }
    else {
        return 0;
    }
}

/**
 * 资源比对,是否下载成功
 * @param path 资源路径
 * @param asset 资源数据
 */
protected verifyCb(path: string, asset: jsb.ManifestAsset) {
    console.log('path', path, 'setVerifyCallback' + JSON.stringify(asset));
    var compressed = asset.compressed;
    /**
     * 计算md5
     * @param {*} filePath 
     */
    let calMD5OfFile = function (filePath) {
        return md5(jsb.fileUtils.getDataFromFile(filePath));
    };

    if (compressed) {
        return true;
    } else {
        var resMD5 = calMD5OfFile(path);
        console.log('resMD%=', resMD5, 'asset md5=', asset.md5);
        if (asset.md5 == resMD5) {
            return true;
        }
        jsb.fileUtils.removeFile(path);
        return false;
    }
}

/**
 * 更新检测回调
 * @param event 
 */
protected checkCb(event: jsb.EventAssetsManager) {
    let eventCode = event.getEventCode();
    
    switch (eventCode)
    {
        case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: {
            this._handler.up_check_err(eventCode, 'ERROR_NO_LOCAL_MANIFEST', "No local manifest file found, hot update skipped.");
            break;
        }
        case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:{
            this._handler.up_check_err(eventCode, 'ERROR_DOWNLOAD_MANIFEST', "Fail to download manifest file, hot update skipped.");
            break;
        }
        case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:{
            this._handler.up_check_err(eventCode, 'ERROR_PARSE_MANIFEST', "Fail to parse manifest file, hot update skipped.");
            break;
        }
        case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: {
            this._isNewApp ? this._handler.new_app_version_handler(this.getRemoteStrVersion()) : this._handler.up_to_newest_version(this.getLocalVersion());
            break;
        }
        case jsb.EventAssetsManager.NEW_VERSION_FOUND: {
            this._handler.new_res_version_handler(this.getRemoteStrVersion());
            break;
        }
        default: {
            cc.log('Update check event code: ' + eventCode);
            return;
        }    
    }
    this._assetMgr.setEventCallback(null);
    this._checkListener = null;
    this._updating = false;
};


protected updateCb(event: jsb.EventAssetsManager) {
    var needRestart = false;
    var failed = false;
    let eventCode = event.getEventCode();
    switch (eventCode)
    {
        case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: {
            this._handler.up_check_err(eventCode, 'ERROR_NO_LOCAL_MANIFEST', "No local manifest file found, hot update skipped.");
            failed = true;
            break;
        } 
        case jsb.EventAssetsManager.UPDATE_PROGRESSION: {
            let progressData : ProgressionData = {
                msg : event.getMessage(),
                byteProgress : event.getPercent(),
                fileProgress : event.getPercentByFile(),
                sucFiles : event.getDownloadedFiles(),
                totalFiles : event.getTotalFiles(),
                sucBytes : event.getDownloadedBytes(),
                totalBytes : event.getTotalBytes(),
            }
            this._handler.up_progression(progressData);
            break;

        }
        case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:{
            this._handler.up_check_err(eventCode, 'ERROR_DOWNLOAD_MANIFEST', "Fail to download manifest file, hot update skipped.");
            failed = true;
            break;
        }    
        case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:{
            this._handler.up_check_err(eventCode, 'ERROR_PARSE_MANIFEST', "Fail to parse manifest file, hot update skipped.");
            failed = true;
            break;
        }
        case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: {
            this._handler.up_check_err(eventCode, 'ALREADY_UP_TO_DATE', "the res version is newest! you need checking the logic.");
            failed = true;
            break;
        }
        case jsb.EventAssetsManager.UPDATE_FINISHED: {
            this._handler.up_finished();
            needRestart = true;
            break;
        }
        case jsb.EventAssetsManager.UPDATE_FAILED: {
            // it need retry to download failed files!
            this._canRetry = true;
            this._updating = false;
            this._handler.up_failed_files(event.getMessage());
            break;
        }
        case jsb.EventAssetsManager.ERROR_UPDATING: {
            this._handler.up_file_error(event.getAssetId(), event.getMessage());
            break;
        }
        case jsb.EventAssetsManager.ERROR_DECOMPRESS: {
            this._handler.unable_decompress_file(event.getMessage());
            break;
        }
        default:
            break;
    }

    if (failed) {
        this._assetMgr.setEventCallback(null);
        this._updateListener = null;
        this._updating = false;
    }

    if (needRestart) {
        this._assetMgr.setEventCallback(null);
        this._updateListener = null;
        // Prepend the manifest's search path
        var searchPaths = jsb.fileUtils.getSearchPaths();
        var newPaths = this._assetMgr.getLocalManifest().getSearchPaths();
        console.log(JSON.stringify(newPaths));
        Array.prototype.unshift.apply(searchPaths, newPaths);
        // This value will be retrieved and appended to the default search path during game startup,
        // please refer to samples/js-tests/main.js for detailed usage.
        // !!! Re-add the search paths in main.js is very important, otherwise, new scripts won't take effect.
        cc.sys.localStorage.setItem('HotUpdateSearchPaths', JSON.stringify(searchPaths));
        jsb.fileUtils.setSearchPaths(searchPaths);

        cc.audioEngine.stopAll();
        cc.game.restart();
    }
}

protected loadCustomManifest () {
    if (this._assetMgr.getState() === jsb.AssetsManager.State.UNINITED) {
        var manifest = new jsb.Manifest(customManifestStr, this._storagePath);
        this._assetMgr.loadLocalManifest(manifest, this._storagePath);
        // this.panel.info.string = 'Using custom manifest';
    }
}

public retry() {
    if (!this._updating && this._canRetry) {
        // this.panel.retryBtn.active = false;
        this._canRetry = false;
        
        // this.panel.info.string = 'Retry failed Assets...';
        this._assetMgr.downloadFailedAssets();
    }
}

public checkUpdate() {
    if (this._updating) {
        // this.panel.info.string = 'Checking or updating ...';
        return;
    }
    if (this._assetMgr.getState() === jsb.AssetsManager.State.UNINITED) {
        // Resolve md5 url
        var url = this.manifestUrl.nativeUrl;
        if (cc.loader.md5Pipe) {
            url = cc.loader.md5Pipe.transformURL(url);
        }
        this._assetMgr.loadLocalManifest(url);
    }
    if (!this._assetMgr.getLocalManifest() || !this._assetMgr.getLocalManifest().isLoaded()) {
        // this.panel.info.string = 'Failed to load local manifest ...';
        return;
    }
    this._assetMgr.setEventCallback(this.checkCb.bind(this));

    this._assetMgr.checkUpdate();
    this._updating = true;
}

public hotUpdate() {
    if (this._assetMgr && !this._updating) {
        this._assetMgr.setEventCallback(this.updateCb.bind(this));

        if (this._assetMgr.getState() === jsb.AssetsManager.State.UNINITED) {
            // Resolve md5 url
            var url = this.manifestUrl.nativeUrl;
            if (cc.loader.md5Pipe) {
                url = cc.loader.md5Pipe.transformURL(url);
            }
            this._assetMgr.loadLocalManifest(url);
        }

        this._failCount = 0;
        this._assetMgr.update();
        // this.panel.updateBtn.active = false;
        this._updating = true;
    }
}

protected show() {
    // if (this.updateUI.active === false) {
    //     this.updateUI.active = true;
    // }
}

/** 获取本地版本号 */
public getLocalVersion() : GVersion {
    if(this._assetMgr.getState() == jsb.AssetsManager.State.UNINITED) return null;
    if(this._localVersion) {
        return this._localVersion;
    }
    let version = this._assetMgr.getLocalManifest().getVersion();
    let splites = version.split('.');
    if(splites.length != 4) return null;
    this._localVersion = this.genVersion(version);
    return this._localVersion;
}

/** 获取远端版本号 */
public getRemoteStrVersion(): string {
    let remote = this._assetMgr.getRemoteManifest();
    if(!remote) return;
    return remote.getVersion();
    
}

/** 将字符串版本号转为 GVersion */
public genVersion(version: string) : GVersion {
    let splites = version.split('.');
    if(splites.length != 4) return null;
    return {
        ver: version,
        app: parseInt(splites[0]),
        res: `${splites[1]}.${splites[2]}.${splites[3]}`
    }
}

public destroy() {
    if (this._updateListener) {
        !!this._assetMgr && this._assetMgr.setEventCallback(null);
        this._updateListener = null;
    }
}

}文字缩进4格
interface GVersion {
ver: string;
app: number;
res: string;
}

interface ProgressionData {
msg : string;
byteProgress : number;
fileProgress : number;
sucFiles : number;
totalFiles : number;
sucBytes : number;
totalBytes : number;
}

interface HotUpdateInterface {

/** discovery new res verison */
new_res_version_handler: {(version: string) : void};
/** disconvery new app verison */
new_app_version_handler: {(version: string) : void};
/** update to newest app and res verison */
up_to_newest_version: {(version: GVersion) : void};

/** update the res progression call back */
up_progression: {(proData: ProgressionData): void};
/** update the res finished call back! */
up_finished: {(): void};
/** update failed call back */
up_failed_files: {(errMsg: string): void};
/** update file error! */
up_file_error: { (assetId: string, errMsg: string): void};
/** unable to decompress file */
unable_decompress_file: {(errMsg: string): void };

up_check_err: {(errCode: number, errMsg: string, detail: string)};

}

文字缩进4格

1赞

感谢楼上的各位大神,根据你们给的例子我应该可以把JS版本翻译过来了

HotUpdate.zip (186.2 KB)

兄弟 分享下呗~

2.4上面不成功,检查更新直接说ALREADY_UP_TO_DATE

你2.4的 jsb.EventAssetsManager和jsb.AssetsManager不会报错吗

那肯定需要你根据新的文档相关的api调整一下了