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


转自: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内置的场景加载
public class UnityBuiltSceneLoadAsync : SceneLoadAsync {
     private AsyncOperation sceneAsync = null ;
 
     public UnityBuiltSceneLoadAsync( string scene_name, bool allowSceneActivation, Action< float > progressCallBack, Action loadCompleted) {
         this .sceneName = scene_name;
         this .progressCallBack = progressCallBack;
         this .loadCompleted = loadCompleted;
         this .allowSceneActivation = allowSceneActivation;
 
         string assetPath = string .Format( "res/scenes/{0}.scene" , scene_name);
         Coroutine coroutine = Global.coroutine.Execute(LoadLevelBundle(scene_name, assetPath));
     }
 
     protected override IEnumerator LoadLevelBundle( string levelName, string assetPath) {
         Global.coroutine.Execute( base .LoadLevelBundle(levelName, assetPath));
         sceneAsync = Application.LoadLevelAsync(levelName);
         sceneAsync.allowSceneActivation = allowSceneActivation;
         float progress = 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 ;
                 yield break ;
             }
             yield return null ;
         }
     }
 
     public override void Unload( bool unloadAllLoadedObjects) {
         base .Unload(unloadAllLoadedObjects);
 
         sceneAsync = null ;
     }
 
     public override bool allowSceneActivation {
         get {
             return base .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);
                 }
             }
         }
     }
 
     protected override void OnLoadSceneProgress( string assetPath, float progress) {
         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资源的场景加载
public class AssetBundleSceneLoadAsync : SceneLoadAsync {
     public class SceneAssetData {
         private UnityEngine.Object mainAsset_ = null ;
         private AssetBundle assetBundle_ = null ;
 
         public string sceneName { get ; set ; }
         public string assetName { get ; set ; }
         public Coroutine assetCoroutine { get ; set ; }
         public AssetBundle assetBundle {
             get { return assetBundle_; }
             set {
                 if (value != null ) {
                     assetBundle_ = value;
 
                     mainAsset_ = assetBundle.Load(sceneName, typeof (GameObject));
                     if (mainAsset_ != null ) {
                         if ((mainAsset_ as GameObject) == null ) {
                             LOG.LogError( string .Format( "场景加载出错,加载的AB资源{0}中资源{1}[{2}]的类型错误,请美术检查相应AB资源"
                                 , assetName, sceneName, mainAsset_.GetType().Name));
                         }
                     }
                     else if (!assetName.EndsWith( ".scene" ))
                         LOG.LogError( string .Format( "场景加载出错,加载的AB资源{0}中无{1}资源,请美术检查相应AB资源"
                             , assetName, sceneName));
                 }
             }
         }
         public UnityEngine.Object mainAsset {
             get {
                 return mainAsset_;
             }
         }
 
         public SceneAssetData( string sceneName, string assetName, Coroutine assetCoroutine, AssetBundle assetBundle) {
             this .sceneName = sceneName;
             this .assetName = assetName;
             this .assetCoroutine = assetCoroutine;
             this .assetBundle = assetBundle;
         }
 
         public void Unload( bool unloadAllLoadedObjects) {
             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 is GameObject && !assetName.EndsWith( ".scene" )) {
                 GameObject obj = UnityEngine.Object.Instantiate(mainAsset) as GameObject;
                 CombineAssetStatic(obj.transform);
             }
         }
 
         private void CombineAssetStatic(Transform root) {
             for ( int i = 0; i < root.childCount; i++) {
                 if (root.GetChild(i).name == "static" ) {
                     StaticBatchingUtility.Combine(root.GetChild(i).gameObject);
                 }
             }
         }
     }
     private Map< string , SceneAssetData> sceneAssets = new Map< string , SceneAssetData>();
     private Map< string , float > assetsProgressMap = new Map< string , float >();
 
     public AssetBundleSceneLoadAsync( int scene_id, bool allowSceneActivation, 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;
 
         string assetPath = string .Format( "res/scenes/{0}.scene" , sceneName);
         Coroutine coroutine = Global.coroutine.Execute(LoadLevelBundle(sceneName, assetPath));
         sceneAssets.Add(assetPath, new SceneAssetData(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, new SceneAssetData(sceneName, assetPath, coroutine, null ));
         }
     }
 
     protected override IEnumerator LoadLevelBundle( string levelName, string assetPath) {
         Global.coroutine.Execute( base .LoadLevelBundle(levelName, assetPath));
         WWW loadSceneWWW = new WWW(CDirectory.MakeFullPath(assetPath));
         while (!loadSceneWWW.isDone) {
             assetProgresCallBacks[assetPath](assetPath, loadSceneWWW.progress);
             yield return null ;
         }
         assetProgresCallBacks[assetPath](assetPath, 1.0f);
         yield return loadSceneWWW;
         if (loadSceneWWW.error != null ) {
             LOG.LogError( "WWW download:" + loadSceneWWW.error + "  path :  " + loadSceneWWW.url);
             yield break ;
         }
         if (loadSceneWWW.isDone) {
             sceneAssets[assetPath].assetBundle = loadSceneWWW.assetBundle;
             loadSceneWWW.Dispose();
             if (isSceneLoadCompleted()) {
                 if (loadCompleted != null )
                     loadCompleted();
                 isDone = true ;
                 allowSceneActivation = sceneActive;
             }
         }
     }
 
     private bool isSceneLoadCompleted() {
         int complateCount = 0;
         for (sceneAssets.Begin(); sceneAssets.Next(); ) {
             if (sceneAssets.Value.assetBundle != null )
                 complateCount++;
         }
 
         return complateCount == sceneAssets.Count && complateCount != 0;
     }
 
     public override void Unload( bool unloadAllLoadedObjects) {
         base .Unload(unloadAllLoadedObjects);
 
         for (sceneAssets.Begin(); sceneAssets.Next(); ) {
             sceneAssets.Value.Unload(unloadAllLoadedObjects);
         }
         sceneAssets.Clear();
         assetsProgressMap.Clear();
     }
 
     public override bool allowSceneActivation {
         get {
             return base .allowSceneActivation;
         }
         set {
             base .allowSceneActivation = value;
 
             if (value && isSceneLoadCompleted()) {
                 if (onSceneActiveBegin != null )
                     onSceneActiveBegin(sceneName);
                 Application.LoadLevel(sceneName);
                 LOG.Log( "[ActiveScene] " + sceneName);
             }
         }
     }
 
     protected override void OnLoadSceneProgress( string assetPath, float progress) {
         if (assetsProgressMap.ContainsKey(assetPath))
             assetsProgressMap[assetPath] = progress;
         else
             assetsProgressMap.Add(assetPath, progress);
 
         float tempProgress = 0f;
         for (assetsProgressMap.Begin(); assetsProgressMap.Next(); ) {
             tempProgress += assetsProgressMap.Value;
         }
 
         if ( this .progressCallBack != null ) {
             this .progressCallBack(tempProgress / assetsProgressMap.Count);
         }
     }
}

你可能感兴趣的:(Unity)