1. 程式人生 > >unity scene 無縫切換(快取)

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()
    {

    }
}