unity遊戲框架學習-場景管理

概述地址:http://www.javashuo.com/article/p-nggymcxb-bw.htmlhtml

unity SceneManager API:https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.html,咱們用到的接口主要有如下三個緩存

SceneManager.GetActiveScene 獲取當前活動場景app

SceneManager.LoadScene(int sceneBuildIndex, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single); 同步加載場景,同步加載會有延遲一幀,大場景還會致使遊戲卡頓,建議使用異步加載。官方說明以下:異步

When using SceneManager.LoadScene, the loading does not happen immediately, it completes in the next frame. This semi-asynchronous behavior can cause frame stuttering and can be confusing because load does not complete immediately.async

SceneManager.LoadSceneAsync(int sceneBuildIndex, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single); 異步加載場景,異步加載可以得到加載過程的進度和是否加載完成,經過這種方式你能夠在切換中增減進度條或者其餘表現ui

參數mode說明:lua

LoadSceneMode.Single :Closes all current loaded Scenes and loads a Scene.在加載完成後以後將會馬上銷燬原先場景中的物體
LoadSceneMode.Additive :Adds the Scene to the current loaded Scenes.加載後將會保留原先的場景中的物體spa

 

在概述裏咱們說到,場景模塊的功能主要如下幾個3d

1.場景的加載、卸載、回到上一個場景,這邊不涉及ab包的加、卸載,ab包的維護在資源管理模塊,這邊在ab加載完成的回調裏調用Unity的API就好了code

2.加載新的場景時須要卸載舊場景的的資源,清除GC

3.支持場景資源的預加載,部分場景可能會很大,例如戰鬥場景,能夠預先加載部分模型,後面使用會比較流暢

 

那麼加載一個新的場景大概是如下流程:(使用AssetBundle,不使用ab包可忽略1.4步驟)

1.卸載上一個場景的ab資源(可選)

2.打開場景過渡界面或過渡場景

3.通知ui退出當前場景的界面,關閉場景ui,回收資源(緩存的gameobject,正在加載的資源)

4.加載當前場景的ab包(可選)

5.加載場景(調用unity的SceneManager.LoadScene或SceneManager.LoadSceneAsync接口)

6.預加載資源(可選)

7.清除gc,清除無用的資源

LuaModule.Instance.LuaGCCollect();
Resources.UnloadUnusedAssets();
System.GC.Collect();

8.通知ui進入新的場景,打開場景ui

9.關閉場景過渡界面或過渡場景

好了。場景模塊的代碼分兩塊,一塊是場景的基類,這個類的週期隨着場景的加載開始,場景的銷燬結束,每一個場景都應該有本身的場景類,並在這個類裏實現本身的邏輯(如打開場景ui,加載場景對象),他的結構是這樣子的:

local _PATH_HEAD = "game.modules.scene."

local SceneBase = class("SceneBase", ObjectBase)

--場景加載前會new一個SceneBase,通知業務作一些初始化的東西(這個時候場景是還沒加載,對象是找不到的)
function SceneBase:ctor(sceneConfig)
    SceneBase.super.ctor(self)

    self._sceneType = sceneConfig.stype
    self._sceneID  = sceneConfig.id
    self._sceneName = sceneConfig.scene
    self._sceneFolder = sceneConfig.sceneFolder
    self._loadState = LoadState.NONE
    self._sceneMusic = sceneConfig.music

    self.SceneRoot = nil            -- 根節點 Transform類型
    self._isEnter = false

    self._businessCollect = {}
    self._sceneParam = nil         -- 切換場景 外部傳進來的參數
end

-- 加載場景,先加載ab包,ab包加載完成後會調用unity的SceneManager.LoadSceneAsync接口,異步加載完成後回調DoSceneLoaded
function SceneBase:Load(param, onComplete, isback)
    self._sceneParam = param
    self._onLoadComplete = onComplete
    self._loadState = LoadState.LOADING
    self._isback = isback


    me.modules.load:LoadScene(self._sceneName, self._sceneFolder, handler(self, self.DoSceneLoaded))
end

function SceneBase:DoSceneLoaded(data)
    self._loadState = LoadState.LOADED

    me.modules.ui:SceneEnter(self._sceneID, self._isback)
    if self._sceneMusic then
        SoundUtil.PlayMusic(self._sceneMusic)
    end

    self:OnLoaded(self._sceneParam)

    self:Enter()
    if self._onLoadComplete then
        self._onLoadComplete(self)
    end
end

-- 場景加載完成,通知業務能夠實例化對象了
function SceneBase:OnLoaded()
    local root = GameObject.Find("SceneRoot")
    if root then
        HierarchyUtil.ExportToTarget(root, self)
        self.SceneRoot = root.transform

        me.MainCamera = self.MainCamera
        me.SceneUIRoot = self.SceneUIRoot
        Config.Instance.MainCamera = self.MainCamera
        if self.SceneUIRoot then
            me.SceneCanvas = self.SceneUIRoot.gameObject:GetComponent("Canvas")
        end
    end
end

function SceneBase:Enter()
    if not self._isEnter then
        self._isEnter = true
        self:OnStart()
    end
end

--通知業務開始監聽事件,打開界面等等
function SceneBase:OnStart()
end

function SceneBase:Update(dt)

end

--通知業務取消監聽事件,關閉界面等等
function SceneBase:Exit()
    if self._isEnter then
        self._isEnter = false
        self:OnEnd()
        self:OnExit()
    end
end

function SceneBase:OnEnd()

end

-- 退出場景
function SceneBase:OnExit()
end

--場景銷燬前調用,通知業務移除事件,刪除對象
function SceneBase:Dispose()

    self._loadState = LoadState.NONE

    if self.SceneRoot then
        HierarchyUtil.RemoveFromTarget(self)
    end

    for i = 1, #self._businessCollect do
        self._businessCollect[i]:Dispose()
    end
    self._businessCollect = {}

    SceneBase.super.Dispose(self)
end

-----------------------------------

function SceneBase:IsEnter()
    return self._isEnter
end

function SceneBase:OnBeforeRelogin()
    self:Exit()
end

--斷線重連
function SceneBase:OnRelogin()
    self:Enter()
end

function SceneBase:IsLoading()
    return self._loadState==LoadState.LOADING
end

return SceneBase

他的生命週期是這樣子的:ctor-Load-DoSceneLoaded-OnLoaded-Enter-OnStart-Update-Exit-OnEnd-Dispose,Enter(Exit)和OnStart(OnEnd)的區別是,前者是基類的私有方法,用於維護基類的self._isEnter屬性,後者是由子類繼承重現的方法。OnLoaded方法用於子類監聽按鈕事件,實例化對象,OnStart主要是給業務處理邏輯的。

場景模塊的另外一塊是SceneBase的管理類,用於維護場景類的生命週期。

local CURRENT_MODULE_NAME = ...

local SceneModule = class("SceneModule", ModuleBase)
function SceneModule:ctor()
    SceneModule.super.ctor(self)

    self._currScene            = nil
    self._sceneBackStack = {}
    GameMsg.AddMessage("GAME_RELOGIN_FINISH", self, self.OnRelogin)
end

-- 正在加載場景
function SceneModule:IsLoading()
    if not self._currScene then
        return false
    end
    return self._currScene:IsLoading()
end

-- 獲取場景類型
function SceneModule:GetSceneID()
    if not self._currScene then
        return -1
    end

    return self._currScene:GetSceneID()
end

function SceneModule:Update(dt)
    if self._currScene and self._currScene:IsEnter() then
        self._currScene:Update(dt)
    end
end


function SceneModule:OnRelogin()
    self._currScene:OnRelogin()
end

function SceneModule:Back(param, onloaded)
    if self._lastSceenID then
        self:ChangeScene(self._lastSceenID, param, onloaded, false, true)
    end
end

--返回到上次記錄的場景,若是上次記錄爲空,則返回上個場景
function SceneModule:PopSceneStack(param, onloaded)
    local count = #self._sceneBackStack
    if count > 0 then
        local sceneId = self._sceneBackStack[count]
        self._sceneBackStack[count] = nil
        self:ChangeScene(sceneId, param, onloaded, false, true)
    else
        self:Back(param,onloaded)
    end
end

--清空場景記錄
function SceneModule:ClearSceneStack()
    self._sceneBackStack = {}
end

-- 切換場景
function SceneModule:ChangeScene(sceneID, param, onloaded, sceneUIPush, isback, addSceneBackStack)
    local currSceneID = self:GetSceneID()
    if currSceneID==sceneID then
        printWarning("ChangeScene current scene is the target... sceneID:", sceneID)
        return
    end

    if self:IsLoading() then
        printWarning("ChangeScene current scene is loading....:", currSceneID, sceneID)
        return
    end

    if currSceneID ~= -1 then
        printWFF("====StopMusic ", currSceneID)
        SoundUtil.StopMusic()
    end
    self:LoadScene(sceneID, param, onloaded, sceneUIPush, isback, addSceneBackStack)
end

function SceneModule:LoadAdditiveScene(sceneID, param)

    local newScene = self:CreateScene(sceneID)
    if not newScene then
        return
    end

    newScene:Load(param)
    self._bgScene = newScene
end

function SceneModule:FocusBgScene()
    self:ExitCurrent()
    self._currScene = self._bgScene
    me.MainScene = self._currScene
end

--根據場景id加載新的場景
function SceneModule:LoadScene(sceneID, param, onloaded, sceneUIPush, isback, sceneBackStackPush)
    local newScene = self:CreateScene(sceneID)
    if not newScene then
        return
    end

    local lastSceneId = self:GetSceneID()
    if sceneBackStackPush then
        self._sceneBackStack[#self._sceneBackStack + 1] = lastSceneId
    end

    -- 卸載舊的場景
    self._lastSceenID = lastSceneId
    local lastScene = self._currScene
    if lastScene~= nil then
        local lastSceneName = lastScene:GetSceneName()
        local lastSceneType = lastScene:GetSceneType()

        self:ExitCurrent(sceneUIPush)

        if lastSceneType~=newScene:GetSceneType() then
            LuaHelper.UnloadSceneAB(lastSceneName, false)
        end
        me.modules.resource:ClearLoad()
        -- 清除資源
        me.modules.resource:ClearPool()
        me.MainScene = nil
        me.MainCamera = nil
        me.SceneUIRoot = nil
        -- 除了登陸場景 其餘場景切換都有場景過渡
        if lastSceneType ~= SceneDefine.SceneType.LOGIN then
            -- 若是參數裏標記了使用CUTSCENE過渡,那麼這邊不要打開這個普經過渡界面
            local useNormalTransition = true
            if param then
                if param.UseCutSceneTransition then
                    useNormalTransition = false
                elseif param.useCivSceneTransition then
                    useNormalTransition = false
                    me.modules.ui:OpenView(ViewID.CIV_PRE_SCENE,param)
                end
            end

            if useNormalTransition then
                me.modules.ui:OpenView(ViewID.TRANSITION)
            end
        end
    end

    self._currScene = newScene

    --加載新場景
    me.MainScene = newScene
    newScene:Load(param, onloaded, isback)

    -- 發送場景切換事件
    GameMsg.SendMessage("SCENE_CHANGED")
end

--生成一個SceneBase
function SceneModule:CreateScene(sceneID)
    local sceneConfig = SceneDefine.SceneConfig[sceneID]
    if not sceneConfig then
        printError("Can't find scene config... sceneID:",sceneID)
        return
    end

    local sceneClass = import(sceneConfig.path, CURRENT_MODULE_NAME)
    if not sceneClass then
        printError("Import new scene fail:",sceneID)
        return
    end

    -- 新場景加載前的準備
    local newScene = sceneClass.new(sceneConfig)
    return newScene
end

function SceneModule:ExitCurrent(sceneUIPush)
    if not self._currScene then
        return
    end
    local sceneID = self._currScene:GetSceneID()
    me.modules.ui:SceneExit(sceneID, sceneUIPush)
    self._currScene:Exit()
    self._currScene:Dispose()
    self._currScene = nil
end

-- 中止當前邏輯
function SceneModule:OnBeforeRelogin()
    if not self._currScene then
        return
    end
    self._currScene:OnBeforeRelogin()
end

return SceneModule

SceneModule最主要的四個方法,

1.ChangeScene:業務調用該接口,用於切換到指定名字的場景

2.Back:咱們的UI界面都有返回鍵,當沒有可返回的界面時,會返回到上一場景,也就是這個Back方法

3.PopSceneStack:有些遊戲須要記錄玩家上一次進入的場景,舉個例子。玩家從場景A的a界面進入了場景B,當玩家退出場景B時,須要還原到場景A並打開a界面(a多是通過c-d-f界面纔打開的,這時候還須要還原到上一次的界面棧,這個功能會在後面的UIModule實現)

4.LoadScene:這個是私有方法(lua裏面沒有這個概念,能夠理解成只有SceneModule能夠調用這個方法),這是切換代碼的核心功能,他完成的內容按順序以下:

(1.新建下一個場景的SceneBase newScene

(2.退出當前場景並通知ui關閉當前場景ui

(3.清理當前場景緩存的對象、終止正在加載的隊列

(4.打開場景過渡界面

(5.通知newScene開始加載場景

 

場景模塊到這邊就結束了~

相關文章
相關標籤/搜索