zoukankan      html  css  js  c++  java
  • unity游戏框架学习-场景管理

    概述地址:https://www.cnblogs.com/wang-jin-fu/p/10975660.html

    unity SceneManager API:https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.html,我们用到的接口主要有以下三个

    SceneManager.GetActiveScene 获取当前活动场景

    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.

    SceneManager.LoadSceneAsync(int sceneBuildIndex, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single); 异步加载场景,异步加载能够获得加载过程的进度和是否加载完成,通过这种方式你可以在切换中增减进度条或者其他表现

    参数mode说明:

    LoadSceneMode.Single :Closes all current loaded Scenes and loads a Scene.在加载完成后之后将会立刻销毁原先场景中的物体
    LoadSceneMode.Additive :Adds the Scene to the current loaded Scenes.加载后将会保留原先的场景中的物体

    在概述里我们说到,场景模块的功能主要以下几个

    1.场景的加载、卸载、回到上一个场景,这边不涉及ab包的加、卸载,ab包的维护在资源管理模块,这边在ab加载完成的回调里调用Unity的API就行了

    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开始加载场景

    场景模块到这边就结束了~

  • 相关阅读:
    UI进阶--UIPikcerView实现餐点搭配示例
    UI基础--UITableView实现仿QQ好友列表页面
    通知和代理
    UI基础--UITableView实现仿QQ聊天页面
    UI基础--UITableView实现仿微博页面
    UI基础--UITableView,UITableViewDataSource,UITableViewDelegate的一些属性和方法
    UI基础--使用UIScrollView、UIPageControl、NSTimer实现图片循环播放
    UI基础--UIScrollView和UIScrollViewDelegate
    NSURLSession 简介
    Core Animation
  • 原文地址:https://www.cnblogs.com/wang-jin-fu/p/11128974.html
Copyright © 2011-2022 走看看