unity scene 无缝切换(缓存)

场景切换的时候经常会造成卡顿,导致用户体验并不好。
如何能做到不卡呢,有一种方式是做成prefab,然后通过启用禁用来模拟切场景,但是现在遇到的情况是希望烘焙出的lightmap也能够进行切换。而在unity5中动态的去切lightmap有点麻烦,没有去深入研究,于是采用了下面缓存场景的做法。

假设场景切换顺序:Main->A->B->C->Main
从Main场景切到A,A加载完后希望在A运行的同时能够缓存B场景。这样待会从B切到C时B因为已经缓存过了,加载耗时会比较短,B加载同时又缓存C场景,缓存C的过程不会造成B的卡顿,之后会有解释。

异步切换场景的核心代码如下

LoadAsyncApi(ref _curResult,_curSceneName);
yield return StartCoroutine(LoadInProgress(_curResult));
yield return StartCoroutine(LoadAfterProgress(_curResult, _curSceneName));

    private void LoadAsyncApi(ref AsyncOperation result,string sceneName)
    {
        result = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
    }
    //0-0.9
    private IEnumerator LoadInProgress(AsyncOperation result)
    {
        result.allowSceneActivation = false;
        while (result.progress < 0.9f)
        {
            yield return null;
        }
    }
    //0.9-1(start awake ,not include coroutine)
    private IEnumerator LoadAfterProgress(AsyncOperation result,string sceneName )
    {
        result.allowSceneActivation = true;
        while (!SceneManager.GetSceneByName(sceneName).isLoaded)
        {
            yield return null;
        }
    }

调用LoadSceneAsyc异步加载,然后将之后的加载分为两步。
allowSceneActivation很多帖子写的都很懵逼,今天我来讲清楚。。。
1. 0-0.9(阶段1)
allowSceneActivation设为false的时候,阻止Scene的完全激活,这时进度会一直卡在0.9。这个阶段会做一些磁盘相关的IO操作,比如载入资源等,这些操作不会阻塞当前主线程。当你的场景中引用了比较多的material啊,texture啊,那很多时间会花费在这里。
2. 0.9-1(阶段2)
将allowSceneActivation设为true,这个过程做的事情包括所有的awake,start函数的执行以及setactive的时间,应该也包括所有组件如collider,animator内部初始化相关的操作。通过实验发现,直接在场景中加入成千上万个立方体,最后场景加载的时间基本都花在这个阶段。你手动启用这些物体也是会造成卡顿的。这一步会造成主进程阻塞,会使得unity难以避免的卡顿,这一步暂无办法去优化。

了解了这两步之后,实现这个需求就很清楚了。
Main切A加载B:
执行A的阶段1,阶段2,unload掉Main,再执行B的阶段1,让B卡在progress为0.9的位置。真正加载B的时候再执行B的阶段2。如果说场景中没有大量物体,而引用的资源又大又多,是可以做到秒切场景的。
提供了两个函数,EnterScene需要提供切换到的场景和需要缓存的场景。当不需要走这套机制,直接从场景中跳出到别的场景时,直接将precachescene设为null或者直接调用EnterOutScene退出即可。

完整代码:
DialogSceneMgr

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine.SceneManagement;

public class DialogSceneMgr : Singleton<DialogSceneMgr>
{
    private string _curSceneName;
    private string _precacheSceneName;
    private bool _fromOuterScene = false;

    private AsyncOperation _curResult;
    private AsyncOperation _precacheResult;

    void OnGUI()
    {
        if (GUILayout.Button("BaseLight2 cache BaseLight", GUILayout.Width(200)))
        {
            EnterScene("BaseLight2", "BaseLight");

            //DialogSceneMgr.Instance.InitScenes(new List<string>() { "BaseLight", "BaseLight2" });
        }

        if (GUILayout.Button("BaseLight cache Context", GUILayout.Width(200)))
        {
            EnterScene("BaseLight","Context");
        }

        if (GUILayout.Button("SwitchOut", GUILayout.Width(200)))
        {
            EnterOuterScene("Context");
        }

//        if (GUILayout.Button("BaseLight2", GUILayout.Width(200)))
//        {
//            EnterScene("BaseLight2", null);
//        }
//        if (GUILayout.Button("BaseLight", GUILayout.Width(200)))
//        {
//            EnterScene("BaseLight", null);
//        }

        if (_curResult!=null)
            GUILayout.TextArea("CurProgress:" + _curResult.progress);
        if (_precacheResult != null)
            GUILayout.TextArea("PrecacheProgress:" + _precacheResult.progress);
    }

    public void EnterScene(string sceneName,string precacheSceneName )
    {
        _curSceneName = sceneName;
        //没有precache的scene说明是第一次进入
        _fromOuterScene = string.IsNullOrEmpty(_precacheSceneName);

        //缓存的场景和即将进入的场景不一致直接Load场景
        if (_curSceneName != _precacheSceneName && !_fromOuterScene)
        {
            Clear();
            SceneManager.LoadScene(_curSceneName);
            return;
        }
        _precacheSceneName = precacheSceneName;
        StartCoroutine(LoadSceneCor());
    }

    private void Clear()
    {
        _precacheSceneName = null;
    }

    public void EnterOuterScene(string sceneName)
    {
        EnterScene(sceneName, null);
    }

    private IEnumerator LoadSceneCor()
    {
        //from prev to cur
        if (_fromOuterScene)
        {
            LoadAsyncApi(ref _curResult,_curSceneName);
            yield return StartCoroutine(LoadInProgress(_curResult));
            yield return StartCoroutine(LoadAfterProgress(_curResult, _curSceneName));
        }
        else
        {
            _curResult = _precacheResult;
            yield return StartCoroutine(LoadAfterProgress(_curResult, _curSceneName));

        }

        UnLoadPrevScene();
        SetActiveScene(_curSceneName);

        //precache next
        Debug.Log("precache next");
        //没有缓存场景了,不需要缓存
        if (string.IsNullOrEmpty(_precacheSceneName))
        {
            yield break;
        }

        LoadAsyncApi(ref _precacheResult,_precacheSceneName);
        yield return StartCoroutine(LoadInProgress(_precacheResult));
    }

    private void UnLoadPrevScene()
    {
        Scene prevScene = SceneManager.GetActiveScene();
        SceneManager.UnloadScene(prevScene.name);
    }

    private void SetActiveScene(string sceneName)
    {
        SceneManager.SetActiveScene(SceneManager.GetSceneByName(sceneName));
    }

    //0-0.9
    private IEnumerator LoadInProgress(AsyncOperation result)
    {
        result.allowSceneActivation = false;
        while (result.progress < 0.9f)
        {
            yield return null;
        }
    }

    private void LoadAsyncApi(ref AsyncOperation result,string sceneName)
    {
        result = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
    }

    //0.9-1(start awake ,not include coroutine)
    private IEnumerator LoadAfterProgress(AsyncOperation result,string sceneName )
    {
        result.allowSceneActivation = true;
        while (!SceneManager.GetSceneByName(sceneName).isLoaded)
        {
            yield return null;
        }
    }
}

附singleton

using System;
using UnityEngine;
using System.Collections;

public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
    private static T mInstance;
    private static bool isQuiting;

    public static T Instance
    {
        get
        {
            if (mInstance == null && !isQuiting)
            {
                mInstance = new GameObject("(Singleton) " + typeof (T).Name).AddComponent<T>();
            }

            return mInstance;
        }
    }

    private void Awake()
    {
        T instance = this as T;

        if (mInstance != null && mInstance != instance)
        {
            T cacheInstance = mInstance;//因为下一步destroy的时候更改了static的mInstance
            DestroyImmediate(this.gameObject);
            mInstance = cacheInstance;
            return;
        }

        // 切换场景不要销毁GameObject
        DontDestroyOnLoad(gameObject);
        mInstance = instance;
        OnInit();
    }

    private void OnDestroy()
    {
        OnRelease();
        mInstance = null;
    }

    private void OnApplicationQuit()
    {
        isQuiting = true;
    }

    /// <summary>
    /// 初始化
    /// </summary>
    protected virtual void OnInit()
    {

    }

    /// <summary>
    /// 释放
    /// </summary>
    protected virtual void OnRelease()
    {

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值