SceneManager
类提供了一个强大且灵活的场景管理框架,可以更容易地处理复杂的场景切换和资源管理任务
谢谢大家关注一下我的微信
框架上 设计一个 基类 SceneLoad:BaseSceneLoad
设计多个 场景类:NormalSceneLoad 例如:
BaseSceneLoad
:引入基础场景加载模式。NormalSceneLoad
:引入普通场景加载模式。PrefabSceneLoad
:引入预制体场景加载模式。
lua 游戏架构 之 SceneLoad场景加载(二)-CSDN博客https://blog.csdn.net/heyuchang666/article/details/140560741?spm=1001.2014.3001.5502设计一个 场景基类 SceneBase ,提供了一个框架,用于处理场景的加载、初始化、激活、释放等生命周期管理
这次 设计一个 SceneManager 管理 多个 SceneBase。
设计变量 :
- 1. `self._curScene = nil`:当前场景,初始化为`nil`,表示当前没有场景被加载。
- 2. `self._sceneSwitchSt = kSceneSwitchSt.unknown`:将场景切换状态(`_sceneSwitchSt`)初始化为`unknown`,表示当前场景切换状态未知。
- 3. `self._preScene = nil`:上一个场景
- 4. `self._stackSceneLoad = nil`:将栈中的场景加载器
- 5. `self._loadingStartTime = 0`:将加载开始时间(
- 6. `self._switchClock = 0`:将切换时钟(`_switchClock`)初始化为0,表示场景切换尚未开始计时。
- 7. `self._stackSceneName = Stack:new()`:创建一个新的栈对象(`_stackSceneName`),用于存储上一场景的名称。
- 8. `self._sceneLoadMap = {}`:创建一个空的表(`_sceneLoadMap`),用于存储场景加载器的映射关系。
SceneManager
类的设计思路和主要函数体现了一个典型的游戏场景管理系统,其核心目标是控制场景的加载、切换、激活和清理。以下是其主要设计思路和关键函数的总结:
主要设计思路:
单例模式:
SceneManager
设计为单例,确保全局只有一个实例,便于管理。状态机管理:使用状态机来控制场景切换的不同阶段,确保流程的清晰和可控。
资源管理:管理场景资源的加载和释放,优化内存使用和加载时间。
资源管理:管理场景资源的加载和释放,优化内存使用和加载时间。
场景栈:使用栈来管理场景的进入和退出,支持复杂的场景切换逻辑。
异步加载:支持异步加载场景资源,提高加载效率和用户体验。
进度反馈:提供加载进度的反馈机制,允许UI显示加载进度。
错误处理:在场景切换过程中加入错误处理,确保系统的健壮性。
性能监控:监控场景加载的性能,如加载时间和帧率。
主要函数:
initialize / onInit:初始化
SceneManager
实例,设置初始状态和资源。dispose / release:清理
SceneManager
使用的资源,确保没有内存泄漏。clearStackSceneLoad:清空场景栈,释放所有场景加载资源。
releaseStackSceneLoad:释放指定名称的场景加载资源。
pushSceneLoad:将新场景加载资源压入栈中,管理场景的加载顺序。
stopSceneLoading:停止当前的场景加载流程,重置状态。
switchScene:启动新场景的加载和切换流程,是场景切换的入口函数。
update:主更新函数,根据当前状态执行相应的场景加载逻辑。
destroyPreScene:销毁前一个场景,释放相关资源。
onLoadingCompleted:场景加载完成后的回调函数,用于执行加载完成后的操作。
onFinish:完成场景切换的回调函数,用于执行切换完成后的操作。
isSwitchingScene:检查是否正在切换场景。
resumeScene:恢复场景状态,通常用于应用从后台恢复时。
closeSceneAutoPanle:关闭场景自动管理的UI面板。
getCurSceneName:获取当前场景的名称。
printSceneLoading:打印当前场景加载的状态信息,用于调试和日志记录。
---@class SceneManager : MiddleClass
---@field static SceneManager_static
---@field _stackSceneName Stack<string>
local SceneManager = SimpleClassUtil:class()
local kSceneSwitchSt = {
unknown = -999,
waitLoadingPanel = -1,
startPreSceneExit = 0,
onCurSceneInit = 1, -- 有些场景开始就需要进行分帧处理
onCurSceneIniting = 2,
onCurSceneDoPreSceneExit = 3,
onCurSceneDoPreSceneExiting = 4,
startLoadingScene = 5,
loadLoadingScene = 6,
releasePreScene = 7,
preloadNewScene = 8,
loadNewScene = 9,
loadingNewData = 11,
finalStage = 12
}
SceneManager.stateEnum = kSceneSwitchSt
function SceneManager:initialize()
self:onInit()
end
function SceneManager:onInit()
self._curScene = nil
self._sceneSwitchSt = kSceneSwitchSt.unknown
self._preScene = nil
-- 栈中的scene
--BaseSceneLoad
self._stackSceneLoad = nil
self._loadingStartTime = 0
self._switchClock = 0
-- 存放上一场景的加载器,方便像大地图这类的不想在切场景丢失了其数据
self._stackSceneName = Stack:new()
self._sceneLoadMap = {}
HelperFunc.dump(self._sceneLoadMap, "scene step, SceneManager_onInit :"..debug.traceback())
end
---@class SceneManager_static
---@field getInstance fun():SceneManager
---@field release fun()
function SceneManager:release()
self:dispose()
end
function SceneManager:dispose()
HelperFunc.dump(self._sceneLoadMap, "scene step, SceneManager_dispose")
for _, sceneLoad in pairs(self._sceneLoadMap) do
sceneLoad:destoryPreDispose()
end
if self._preScene then
self._preScene:onSceneExit()
self._preScene:dispose()
self._preScene = nil
end
if self._curScene then
self._curScene:onSceneExit()
self._curScene:dispose()
self._curScene = nil
end
local count = self._stackSceneName:size()
for i=1, count do
self:releaseStackSceneLoad(self._stackSceneName:pop())
end
self._stackSceneLoad = nil
self._sceneLoadMap = nil
self._stackSceneName = nil
self._sceneSwitchSt = nil
if self._updateTimer then
TimerScheduler:removeSchedule(self._updateTimer)
self._updateTimer = nil
end
end
function SceneManager:clearStackSceneLoad()
---@type BaseSceneLoad
local sceneLoad
for i=1, self._stackSceneName:size() do
local name = self._stackSceneName:pop()
sceneLoad = self._sceneLoadMap[name]
sceneLoad:destoryPreDispose()
self:destroyStackSceneLoad(sceneLoad)
end
self._stackSceneName = nil
self._stackSceneName = Stack:new()
self._sceneLoadMap = {}
self._stackSceneLoad = nil
HelperFunc.dump(self._sceneLoadMap, "scene step, SceneManager_clearStackSceneLoad")
end
function SceneManager:releaseStackSceneLoad(name)
self:destroyStackSceneLoad(self._sceneLoadMap[name])
end
---@param stackSceneLoad BaseSceneLoad
---@param sceneLoad BaseSceneLoad
function SceneManager:pushSceneLoad(name, stackSceneLoad, sceneLoad)
if stackSceneLoad then
local temp
local count = self._stackSceneName:size()
for i=1, count do
temp = self._stackSceneName:pop()
---@type BaseSceneLoad
local tempLoad = self._sceneLoadMap[temp]
if temp == name then
self._stackSceneName:push(name)
break
else
tempLoad:destoryPreDispose()
self:destroyStackSceneLoad(tempLoad)
self._sceneLoadMap[temp] = nil
end
end
else
self._stackSceneName:push(name)
self._sceneLoadMap[name] = sceneLoad
end
HelperFunc.dump(self._sceneLoadMap, "scene step, SceneManager_pushSceneLoad")
end
function SceneManager:stopSceneLoading()
self._sceneSwitchSt = kSceneSwitchSt.unknown -- reset state,停止加载流程
if self._preScene then
self._preScene:onSceneExit()
self._preScene:dispose()
self._preScene = nil
end
end
---@param newScene SceneBase @ 目标场景
function SceneManager:switchScene(newScene)
self._switchClock = os.clock()
print("switchScene", newScene, newScene:getSceneResPath())
if self._sceneSwitchSt ~= kSceneSwitchSt.unknown then
Logger.error("当前有未完成的场景切换")
return
end
if self._curScene and newScene:getName() == self._curScene:getName() then
Logger.warning("在当前场景,不允许再次切入")
-- newScene:onSceneResLoaded(self._curScene:getSceneObj())
-- self._sceneSwitchSt = kSceneSwitchSt.loadingNewData
return
end
self._preScene = self._curScene
self._curScene = newScene
if newScene:isInitScene() then
-- 清空栈
self:clearStackSceneLoad()
self._sceneLoad = newScene:createSceneLoad(false)
HelperFunc.dump(self._sceneLoadMap, "scene step, SceneManager_switchScene initScene")
else
self._stackSceneLoad = self._sceneLoadMap[newScene:getName()]
self._sceneLoad = newScene:createSceneLoad(self._stackSceneLoad ~= nil and true or false)
self:pushSceneLoad(newScene:getName(), self._stackSceneLoad, newScene:getSceneLoad())
HelperFunc.dump(self._sceneLoadMap, "scene step, SceneManager_switchScene stackScene")
end
g.uiManager:mask("switchScene" .. newScene:getName())
self._sceneSwitchSt = kSceneSwitchSt.waitLoadingPanel
self._loadingStartTime = CS.UnityEngine.Time.realtimeSinceStartup
if self._updateTimer then
TimerScheduler:resetSchedule(self._updateTimer)
else
self._updateTimer = TimerScheduler:schedulePerFrame(self.update, self)
end
g.eventManager:Dispatch(EventType.SCENE_SWITCH_START)
end
function SceneManager:getPreScene()
return self._preScene
end
function SceneManager:getCurScene()
return self._curScene
end
function SceneManager:update()
if self._sceneSwitchSt == kSceneSwitchSt.unknown then
if self._updateTimer then
TimerScheduler:disableSchedule(self._updateTimer)
end
return
elseif self._sceneSwitchSt == kSceneSwitchSt.waitLoadingPanel then
local activeScene = CSharpImport.SceneManager.GetActiveScene()
local activeName = activeScene.name
if activeName ~= "GameStart" then
self._curScene:onProgress(0)
if self._curScene._loadingView then
if self._curScene._loadingView:getState() == UIManager.State.Normal then
self:printSceneLoading(self._sceneSwitchSt)
self._sceneSwitchSt = kSceneSwitchSt.startPreSceneExit
end
else
self:printSceneLoading(self._sceneSwitchSt)
self._sceneSwitchSt = kSceneSwitchSt.startPreSceneExit
end
else
self._curScene:onProgress(1)
self:printSceneLoading(self._sceneSwitchSt)
self._sceneSwitchSt = kSceneSwitchSt.startPreSceneExit
end
elseif self._sceneSwitchSt == kSceneSwitchSt.startPreSceneExit then
xpcall(function()
if self._preScene and self._preScene:isActive() then
self:closeSceneAutoPanle(self._preScene)
self._preScene:onSceneExit()
end
g.uiManager:disposeLoadingUI({})
-- 会再这里清除big_world_panel
-- 放在这里尝试解决ui清理和堆栈恢复的时序问题
g.uiManager:clearUIPool()
CS.Stein.Performance.PerformanceSmoother.Instance:SetMaxWorkTime(20000, "Resource")
self._curScene:onSceneSwitchStart()
end,error)-- 将上述包起来,避免报错引起走多遍,使战斗数据加载异常
self:printSceneLoading(self._sceneSwitchSt)
self._sceneSwitchSt = kSceneSwitchSt.onCurSceneInit
elseif self._sceneSwitchSt == kSceneSwitchSt.onCurSceneInit then
self._sceneSwitchSt = kSceneSwitchSt.onCurSceneIniting
print(
"Scene Loading",
2,
CS.UnityEngine.Time.realtimeSinceStartup - self._loadingStartTime,
CS.UnityEngine.Time.frameCount
)
local preSceneName = self._preScene and self._preScene._name or nil
self._curScene:initData(preSceneName)
g.PlatControl:GPMMarkLevelLoad(self._curScene:getName())
elseif self._sceneSwitchSt == kSceneSwitchSt.onCurSceneIniting then
self._curScene:setProgressValue(kSceneSwitchSt.onCurSceneIniting, self._curScene:getInitDataProgress())
if self._curScene:isInitDataComplete() then
self._sceneSwitchSt = kSceneSwitchSt.onCurSceneDoPreSceneExit
self._curScene:setProgressValue(kSceneSwitchSt.onCurSceneIniting, 1)
end
elseif self._sceneSwitchSt == kSceneSwitchSt.onCurSceneDoPreSceneExit then
print(
"Scene Loading",
3,
CS.UnityEngine.Time.realtimeSinceStartup - self._loadingStartTime,
CS.UnityEngine.Time.frameCount
)
self._sceneSwitchSt = kSceneSwitchSt.onCurSceneDoPreSceneExiting
self._curScene:onPreSceneExit(
function()
print(
"Scene Loading",
4,
CS.UnityEngine.Time.realtimeSinceStartup - self._loadingStartTime,
CS.UnityEngine.Time.frameCount
)
self._sceneSwitchSt = kSceneSwitchSt.startLoadingScene
end
)
elseif self._sceneSwitchSt == kSceneSwitchSt.onCurSceneDoPreSceneExiting then
-- do nothing
elseif self._sceneSwitchSt == kSceneSwitchSt.startLoadingScene then
if self._preScene then
self._preScene:onNewSceneStartLoaded()
end
self._sceneLoad:startLoadingScene(self._curScene)
g.qualityManager:setLoadPriorityHigh()
self:printSceneLoading(kSceneSwitchSt.startLoadingScene)
self._sceneSwitchSt = kSceneSwitchSt.loadLoadingScene
elseif self._sceneSwitchSt == kSceneSwitchSt.loadLoadingScene then
self._curScene:setProgressValue(kSceneSwitchSt.loadLoadingScene, self._sceneLoad:getLoadingSceneProgress())
if self._sceneLoad:isLoadingSceneReady() then
self:printSceneLoading(self._sceneSwitchSt)
self._sceneSwitchSt = kSceneSwitchSt.releasePreScene
end
elseif self._sceneSwitchSt == kSceneSwitchSt.releasePreScene then
self:destroyPreScene()
g.uiManager:maskFalse("switchScene" .. self._curScene:getName())
Global.uiManager:maskClear()
self._sceneSwitchSt = kSceneSwitchSt.preloadNewScene
self:printSceneLoading(7)
elseif self._sceneSwitchSt == kSceneSwitchSt.preloadNewScene then
self._curScene:onSceneResStartLoad()
self._sceneLoad:startPreLoadNewScene(self._curScene)
if self._stackSceneLoad then
self._stackSceneLoad:onSceneEnterForeground()
end
self._sceneSwitchSt = kSceneSwitchSt.loadNewScene
self:printSceneLoading(8)
elseif self._sceneSwitchSt == kSceneSwitchSt.loadNewScene then
self._curScene:setProgressValue(kSceneSwitchSt.loadNewScene, self._sceneLoad:getNewSceneLoadProgress())
if self._sceneLoad:isNewSceneLoadReady() then
self._curScene:setActive(true)
self._curScene:onSceneResLoaded()
self._sceneSwitchSt = kSceneSwitchSt.loadingNewData
if self._preScene then
self._preScene:onNewSceneLoaded()
end
self._sceneLoad:disposeLoadingScene()
print(
"Scene Loading",
9,
CS.UnityEngine.Time.realtimeSinceStartup - self._loadingStartTime,
CS.UnityEngine.Time.frameCount
)
end
elseif self._sceneSwitchSt == kSceneSwitchSt.loadingNewData then
-- 更新进度
-- self:printSceneLoading(10)
local progress = self._curScene:onExtraDataProgress()
-- self:printSceneLoading(11)
self._curScene:setProgressValue(kSceneSwitchSt.loadingNewData, progress)
if progress >= 0.99999999 then
self._sceneSwitchSt = kSceneSwitchSt.finalStage
self:printSceneLoading("Complete 1")
local function finish_callback()
self:printSceneLoading("Complete 2")
if self._sceneLoad then
self._sceneLoad:disposeLoadingScene()
self._sceneLoad = nil
end
self._curScene:onExtraDataCompleted(
function()
self:printSceneLoading("Complete 3")
self:onLoadingCompleted()
self._curScene:onProgress(1)
self:onFinish(function()
self._sceneSwitchSt = kSceneSwitchSt.unknown
end)
self:printSceneLoading("Complete 4")
end
)
end
if self._preScene then
self._preScene:onNewSceneFinish(finish_callback)
self._preScene = nil
else
finish_callback()
end
g.PlatControl:GPMMarkLevelLoadCompleted()
else
-- do nothing
end
end
end
function SceneManager:destroyPreScene()
if self._preScene then
print("self._preScene:dispose()")
self._preScene:onDestroyScene()
collectgarbage("collect")
CSharpImport.GC.Collect()
print("CSharpImport.Resources.UnloadUnusedAssets()")
CSharpImport.Resources.UnloadUnusedAssets()
CSharpImport.GC.Collect()
collectgarbage("collect")
end
end
---@param sceneLoad BaseSceneLoad
function SceneManager:destroyStackSceneLoad(sceneLoad)
if sceneLoad then
-- sceneLoad:destoryPreDispose()
sceneLoad:destoryDispose()
sceneLoad = nil
end
end
-- 是否正在切换场景中
function SceneManager:isSwitchingScene()
return self._sceneSwitchSt ~= kSceneSwitchSt.unknown
end
function SceneManager:onLoadingCompleted()
self._curScene:onLoadingCompleted()
end
function SceneManager:onFinish(callBack)
local switchTime = os.clock() - self._switchClock
print("SceneManager:onFinish", self._curScene._name, switchTime, "secs")
self._curScene:onFinish(function()
g.eventManager:Dispatch(EventType.SCENE_SWITCH_TIME, {switchTime=switchTime, sceneName=self._curScene:getName()})
CS.Stein.Performance.PerformanceSmoother.Instance:SetMaxWorkTime(2000, "Resource")
g.qualityManager:setLoadPriorityNormal()
callBack()
g.eventManager:Dispatch(EventType.SCENE_SWITCH_END, self._curScene:getName())
end)
end
function SceneManager:resumeScene(data)
self:closeSceneAutoPanle(self._curScene)
self._curScene:resumeScene(data)
end
---@param scene SceneBase
function SceneManager:closeSceneAutoPanle(scene)
local panleNames = scene:getAutoClosePanleList()
for i = 1, #panleNames do
g.uiManager:hidePanel(panleNames[i])
end
scene:clearAutoClosePanle()
end
function SceneManager:getCurSceneName()
if self._curScene then
return self._curScene:getName()
end
return ""
end
function SceneManager:printSceneLoading(step)
print(
"Scene Loading",
step,
CS.UnityEngine.Time.realtimeSinceStartup - self._loadingStartTime,
CS.UnityEngine.Time.frameCount
)
end
return SceneManager