Unity3D游戏开发之“预加载场景,瞬间过图”

来源:互联网 发布:微信朋友圈封面知乎 编辑:程序博客网 时间:2024/06/05 03:56


转自:http://www.manew.com/forum.php?mod=viewthread&tid=98520&page=1#pid1275560



为什么需要预加载场景?

一般我们的过图的时候很少考虑到预加载场景,所以我们在过图的时候经常会显示加载进度条等待场景加载完成,那么加载一些很大的场景的话就会让玩家等待很久的时间。但是在某一些项目有瞬间过图的需求的时候,那么我们就需要预加载场景了。

场景过图包括2种方式。1、unity内置过图场景  2、AB资源场景过图
1、unity内置场景预加载
像我们做mmo的时候内置场景是很少的,只有初始化场景、登录场景等一些很简单的场景,其他的场景全部是AB资源场景方式。
在内置场景做预加载的时候我们必须使用Application.LoadLevelAsync(levelName)异步加载场景的方式,在把返回的AsyncOperation对象存起来并把allowSceneActivation设置为false

当你需要切换场景的时候再把allowSceneActivation设置成true就成功进行了场景切换。


示例代码

//unity内置的场景加载
publicclass UnityBuiltSceneLoadAsync : SceneLoadAsync {
    privateAsyncOperation sceneAsync = null;
 
    publicUnityBuiltSceneLoadAsync(stringscene_name, boolallowSceneActivation, Action<float> progressCallBack, Action loadCompleted) {
        this.sceneName = scene_name;
        this.progressCallBack = progressCallBack;
        this.loadCompleted = loadCompleted;
        this.allowSceneActivation = allowSceneActivation;
 
        stringassetPath = string.Format("res/scenes/{0}.scene", scene_name);
        Coroutine coroutine = Global.coroutine.Execute(LoadLevelBundle(scene_name, assetPath));
    }
 
    protectedoverride IEnumerator LoadLevelBundle(stringlevelName, stringassetPath) {
        Global.coroutine.Execute(base.LoadLevelBundle(levelName, assetPath));
        sceneAsync = Application.LoadLevelAsync(levelName);
        sceneAsync.allowSceneActivation = allowSceneActivation;
        floatprogress = 0;
        while(!sceneAsync.isDone) {
            progress = sceneAsync.progress;
            if(progress >= 0.9f) {
                progress = 1;
                assetProgresCallBacks[assetPath](assetPath, progress);
 
                if(this.loadCompleted != null)
                    this.loadCompleted();
                this.isDone = true;
                LOG.Log("[UnityBuiltSceneLoadAsync-LoadLevelBundle] sceneAsync load isDone");
                allowSceneActivation = true;
                yieldbreak;
            }
            yieldreturn null;
        }
    }
 
    publicoverride void Unload(boolunloadAllLoadedObjects) {
        base.Unload(unloadAllLoadedObjects);
 
        sceneAsync = null;
    }
 
    publicoverride bool allowSceneActivation {
        get{
            returnbase.allowSceneActivation;
        }
        set{
            base.allowSceneActivation = value;
            LOG.Log("[UnityBuiltSceneLoadAsync-allowSceneActivation] allowSceneActivation=" + value);
            if(sceneAsync != null) {
                sceneAsync.allowSceneActivation = value;
                if(value) {
                    if(onSceneActiveBegin != null)
                        onSceneActiveBegin(sceneName);
                    LOG.Log("[ActiveScene] " + sceneName);
                }
            }
        }
    }
 
    protectedoverride void OnLoadSceneProgress(stringassetPath,floatprogress) {
        if(this.progressCallBack != null)
            this.progressCallBack(progress);
    }
}



2、AB资源场景预加载
AB资源场景顾名思义就是把场景打包成AB资源然后通过加载AB资源来达到加载场景的目的。
首先需要把场景打包成AB资源,然后在切换场景的时候先加载场景AB资源再通过Application.LoadLevel(sceneName)接口来切换我们的场景。
在做预加载的时候我们需要缓存加载好的场景AB资源,在切换场景的时候再调用Application.LoadLevel(sceneName),这样就会瞬间切换。
特别注意:这里有一个大坑就是,在这种方式的时候我们只能保存场景AB资源,切换的时候再调用Application.LoadLevel(sceneName)接口。不能加载AB资源后保存调用Application.LoadLevelAsync(levelName)返回的异步接口并设置allowSceneActivation为false,等切换的时候在设置allowSceneActivation为true。在unity4.7这种方式会导致我们的工程的所有协程停止运行,不知道是unity的bug还是什么,unity5.X的没有试过,不知道有没有这种情况。
为了优化更多的内存占用,我们需要在加载完AB场景后需要把AB资源的内存镜像给删除掉调用assetBundle.Unload(false);

示例代码:

//ab资源的场景加载
publicclass AssetBundleSceneLoadAsync : SceneLoadAsync {
    publicclass SceneAssetData {
        privateUnityEngine.Object mainAsset_ = null;
        privateAssetBundle assetBundle_ = null;
 
        publicstring sceneName { get;set; }
        publicstring assetName { get;set; }
        publicCoroutine assetCoroutine { get;set; }
        publicAssetBundle assetBundle {
            get{ returnassetBundle_; }
            set{
                if(value != null) {
                    assetBundle_ = value;
 
                    mainAsset_ = assetBundle.Load(sceneName,typeof(GameObject));
                    if(mainAsset_ != null) {
                        if((mainAsset_ asGameObject) == null) {
                            LOG.LogError(string.Format("场景加载出错,加载的AB资源{0}中资源{1}[{2}]的类型错误,请美术检查相应AB资源"
                                , assetName, sceneName, mainAsset_.GetType().Name));
                        }
                    }
                    elseif (!assetName.EndsWith(".scene"))
                        LOG.LogError(string.Format("场景加载出错,加载的AB资源{0}中无{1}资源,请美术检查相应AB资源"
                            , assetName, sceneName));
                }
            }
        }
        publicUnityEngine.Object mainAsset {
            get{
                returnmainAsset_;
            }
        }
 
        publicSceneAssetData(stringsceneName, stringassetName, Coroutine assetCoroutine, AssetBundle assetBundle) {
            this.sceneName = sceneName;
            this.assetName = assetName;
            this.assetCoroutine = assetCoroutine;
            this.assetBundle = assetBundle;
        }
 
        publicvoid Unload(boolunloadAllLoadedObjects) {
            if(assetCoroutine != null) {
                Global.coroutine.Stop(assetCoroutine);
                assetCoroutine = null;
            }
            if(assetBundle != null)
                assetBundle.Unload(unloadAllLoadedObjects);
 
            //unloadAllLoadedObjects=false表示的是过完场景后对场景AB资源进行卸载
            //unloadAllLoadedObjects=true表示对整个场景的预加载的卸载
            if(!unloadAllLoadedObjects && mainAsset != null
                && mainAsset isGameObject && !assetName.EndsWith(".scene")) {
                GameObject obj = UnityEngine.Object.Instantiate(mainAsset) asGameObject;
                CombineAssetStatic(obj.transform);
            }
        }
 
        privatevoid CombineAssetStatic(Transform root) {
            for(inti = 0; i < root.childCount; i++) {
                if(root.GetChild(i).name == "static") {
                    StaticBatchingUtility.Combine(root.GetChild(i).gameObject);
                }
            }
        }
    }
    privateMap<string, SceneAssetData> sceneAssets = newMap<string, SceneAssetData>();
    privateMap<string,float> assetsProgressMap = newMap<string,float>();
 
    publicAssetBundleSceneLoadAsync(intscene_id,boolallowSceneActivation, Action<float> progressCallBack, Action loadCompleted) {
        MapReference mapref = Global.map_mgr.GetReference(scene_id);
        this.sceneName = mapref.FileName;
        this.progressCallBack = progressCallBack;
        this.loadCompleted = loadCompleted;
        this.allowSceneActivation = allowSceneActivation;
 
        stringassetPath = string.Format("res/scenes/{0}.scene", sceneName);
        Coroutine coroutine = Global.coroutine.Execute(LoadLevelBundle(sceneName, assetPath));
        sceneAssets.Add(assetPath,newSceneAssetData(sceneName,assetPath, coroutine, null));
 
        if(!string.IsNullOrEmpty(mapref.ResPath)) {
            assetPath = string.Format("res/scenes/{0}.go", mapref.ResPath);
            coroutine = Global.coroutine.Execute(LoadLevelBundle(sceneName, assetPath));
            sceneAssets.Add(assetPath,newSceneAssetData(sceneName, assetPath, coroutine, null));
        }
    }
 
    protectedoverride IEnumerator LoadLevelBundle(stringlevelName, stringassetPath) {
        Global.coroutine.Execute(base.LoadLevelBundle(levelName, assetPath));
        WWW loadSceneWWW = newWWW(CDirectory.MakeFullPath(assetPath));
        while(!loadSceneWWW.isDone) {
            assetProgresCallBacks[assetPath](assetPath, loadSceneWWW.progress);
            yieldreturn null;
        }
        assetProgresCallBacks[assetPath](assetPath, 1.0f);
        yieldreturn loadSceneWWW;
        if(loadSceneWWW.error != null) {
            LOG.LogError("WWW download:" + loadSceneWWW.error + "  path :  " + loadSceneWWW.url);
            yieldbreak;
        }
        if(loadSceneWWW.isDone) {
            sceneAssets[assetPath].assetBundle = loadSceneWWW.assetBundle;
            loadSceneWWW.Dispose();
            if(isSceneLoadCompleted()) {
                if(loadCompleted != null)
                    loadCompleted();
                isDone = true;
                allowSceneActivation = sceneActive;
            }
        }
    }
 
    privatebool isSceneLoadCompleted() {
        intcomplateCount = 0;
        for(sceneAssets.Begin(); sceneAssets.Next(); ) {
            if(sceneAssets.Value.assetBundle != null)
                complateCount++;
        }
 
        returncomplateCount == sceneAssets.Count && complateCount != 0;
    }
 
    publicoverride void Unload(boolunloadAllLoadedObjects) {
        base.Unload(unloadAllLoadedObjects);
 
        for(sceneAssets.Begin(); sceneAssets.Next(); ) {
            sceneAssets.Value.Unload(unloadAllLoadedObjects);
        }
        sceneAssets.Clear();
        assetsProgressMap.Clear();
    }
 
    publicoverride bool allowSceneActivation {
        get{
            returnbase.allowSceneActivation;
        }
        set{
            base.allowSceneActivation = value;
 
            if(value && isSceneLoadCompleted()) {
                if(onSceneActiveBegin != null)
                    onSceneActiveBegin(sceneName);
                Application.LoadLevel(sceneName);
                LOG.Log("[ActiveScene] " + sceneName);
            }
        }
    }
 
    protectedoverride void OnLoadSceneProgress(stringassetPath,floatprogress) {
        if(assetsProgressMap.ContainsKey(assetPath))
            assetsProgressMap[assetPath] = progress;
        else
            assetsProgressMap.Add(assetPath, progress);
 
        floattempProgress = 0f;
        for(assetsProgressMap.Begin(); assetsProgressMap.Next(); ) {
            tempProgress += assetsProgressMap.Value;
        }
 
        if(this.progressCallBack != null) {
            this.progressCallBack(tempProgress / assetsProgressMap.Count);
        }
    }
}

0 0