lua 游戏架构 之 SceneLoad场景加载之 SceneManager (四)

SceneManager 类提供了一个强大且灵活的场景管理框架,可以更容易地处理复杂的场景切换和资源管理任务

谢谢大家关注一下我的微信 

框架上 设计一个 基类 SceneLoad:BaseSceneLoad

lua 游戏架构 之 SceneLoad场景加载(一)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/heyuchang666/article/details/140560014?spm=1001.2014.3001.5501

设计多个 场景类:NormalSceneLoad 例如:

  • BaseSceneLoad:引入基础场景加载模式。
  • NormalSceneLoad:引入普通场景加载模式。
  • PrefabSceneLoad:引入预制体场景加载模式。

lua 游戏架构 之 SceneLoad场景加载(二)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/heyuchang666/article/details/140560741?spm=1001.2014.3001.5502设计一个 场景基类 SceneBase ,提供了一个框架,用于处理场景的加载、初始化、激活、释放等生命周期管理

lua 游戏架构 之 SceneLoad场景加载之 SceneBase (三)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/heyuchang666/article/details/140577929 

这次 设计一个 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 类的设计思路和主要函数体现了一个典型的游戏场景管理系统,其核心目标是控制场景的加载、切换、激活和清理。以下是其主要设计思路和关键函数的总结:

主要设计思路:
  1. 单例模式SceneManager 设计为单例,确保全局只有一个实例,便于管理。
  2. 状态机管理:使用状态机来控制场景切换的不同阶段,确保流程的清晰和可控。
  3. 资源管理:管理场景资源的加载和释放,优化内存使用和加载时间。
  4. 资源管理:管理场景资源的加载和释放,优化内存使用和加载时间。

  5. 场景栈:使用栈来管理场景的进入和退出,支持复杂的场景切换逻辑。

  6. 异步加载:支持异步加载场景资源,提高加载效率和用户体验。

  7. 进度反馈:提供加载进度的反馈机制,允许UI显示加载进度。

  8. 错误处理:在场景切换过程中加入错误处理,确保系统的健壮性。

  9. 性能监控:监控场景加载的性能,如加载时间和帧率。

主要函数:

  1. initialize / onInit:初始化 SceneManager 实例,设置初始状态和资源。

  2. dispose / release:清理 SceneManager 使用的资源,确保没有内存泄漏。

  3. clearStackSceneLoad:清空场景栈,释放所有场景加载资源。

  4. releaseStackSceneLoad:释放指定名称的场景加载资源。

  5. pushSceneLoad:将新场景加载资源压入栈中,管理场景的加载顺序。

  6. stopSceneLoading:停止当前的场景加载流程,重置状态。

  7. switchScene:启动新场景的加载和切换流程,是场景切换的入口函数。

  8. update:主更新函数,根据当前状态执行相应的场景加载逻辑。

  9. destroyPreScene:销毁前一个场景,释放相关资源。

  10. onLoadingCompleted:场景加载完成后的回调函数,用于执行加载完成后的操作。

  11. onFinish:完成场景切换的回调函数,用于执行切换完成后的操作。

  12. isSwitchingScene:检查是否正在切换场景。

  13. resumeScene:恢复场景状态,通常用于应用从后台恢复时。

  14. closeSceneAutoPanle:关闭场景自动管理的UI面板。

  15. getCurSceneName:获取当前场景的名称。

  16. 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

相关推荐

  1. lua 游戏架构 SceneLoad场景(一)

    2024-07-22 02:14:02       38 阅读
  2. lua 游戏架构 资源 LoaderManager (一)

    2024-07-22 02:14:02       28 阅读
  3. 手写SpringBoot(bean动态

    2024-07-22 02:14:02       54 阅读
  4. QML学习gif

    2024-07-22 02:14:02       32 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-22 02:14:02       171 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-22 02:14:02       189 阅读
  3. 在Django里面运行非项目文件

    2024-07-22 02:14:02       157 阅读
  4. Python语言-面向对象

    2024-07-22 02:14:02       170 阅读

热门阅读

  1. C++ Primer:4.4 赋值运算符

    2024-07-22 02:14:02       36 阅读
  2. ubuntu 上安装软件

    2024-07-22 02:14:02       38 阅读
  3. ubuntu系统下安装配置 8.0.37的MySQL

    2024-07-22 02:14:02       29 阅读
  4. Keras和Pytorch输入图像的张量维度

    2024-07-22 02:14:02       34 阅读
  5. 中介子方程六十五

    2024-07-22 02:14:02       36 阅读
  6. linux命令

    2024-07-22 02:14:02       28 阅读
  7. 1186. 删除一次得到子数组最大和

    2024-07-22 02:14:02       35 阅读
  8. GPT-LLM

    GPT-LLM

    2024-07-22 02:14:02      36 阅读
  9. 【开源库学习】libodb库学习(二)

    2024-07-22 02:14:02       29 阅读