概述地址: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開始加載場景
場景模塊到這邊就結束了~