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開始加載場景

 

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

相關文章
相關標籤/搜索