- A+
前面已经说过了,Unity那边就一个Main的启动类驱动着GameManager.lua的方法运行着,所以有关场景的运行都要衔接上GameManager来驱动场景的运行,此时场景管理器是非常重要的。讲讲想法,原本在Unity中,场景的加载后,挂载在游戏物体上的MonoBehaviour脚本文件就会驱动起来,调用Awake、Start、Update等等方法推动游戏的进程。现在想法是在其他场景中不挂载任何的MonoBehaviour脚本,因为需要保证在场景lua脚本中可以获取到场景的GameObject,这样就只能在保证场景资源完全加载完成后,驱动一个相应的场景lua脚本的初始化方法,获取相应的游戏物体、添加事件、在GameManager中注册帧运行方法等。
因为场景管理器的帧运行方法是不能被取消的,所以在上一篇中可以看到GameManager的Update方法是单独驱动场景管理器的Update方法的,
1 2 3 4 5 6 7 8 9 10 |
function GameManager:Update(dt) MySceneManager:GetInstance():Update(dt) for k,update in ipairs(self.updates) do update.ticker = update.ticker + dt if update.ticker >= update.timer then update.handler(update.ticker) update.ticker = 0 end end end |
好了,下面直接看看场景管理器MySceneManager,lua的全貌
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
--场景管理 加载 local MySceneManager = class("Manager.MySceneManager") local PathManager = require("Manager.PathManager") local UtilityManager = require("Manager.UtilityManager") local SceneManager = UnityEngine.SceneManagement.SceneManager local LoadSceneMode = UnityEngine.SceneManagement.LoadSceneMode local Image = UnityEngine.UI.Image local Text = UnityEngine.UI.Text local AssetBundle = UnityEngine.AssetBundle function MySceneManager:GetInstance() if self.instance == nil then self.instance = MySceneManager:new() end return self.instance end function MySceneManager:Init(go) self.goGameObject = go self.loadingCanvas = self.goGameObject.transform:GetChild(0) self.loading = self.loadingCanvas.transform:GetChild(1):GetChild(0):GetComponent(typeof(Image)) self.text = self.loadingCanvas.transform:GetChild(1):GetChild(1):GetComponent(typeof(Text)) self.loadingCanvas.gameObject:SetActive(false) self.isLoading = false self.currentScene = nil self.currentSceneName = nil self.currentSceneScirpt = nil self.currentSceneAssetBundle = nil self.Ason = nil --进入默认场景 self:LoadScene(Const.DefaultSceneName) end function MySceneManager:LoadScene(sceneName) if UtilityManager:GetInstance():GetUseRemoteRes() then local filePath = PathManager:GetInstance():GetUpdateScenePath() .. "/".. sceneName .. ".scene" local f = io.open(filePath,"r") if f ~= nil then if self.currentSceneAssetBundle then --卸载 self.currentSceneAssetBundle:Unload(true) end self.currentSceneAssetBundle = AssetBundle.LoadFromFile(filePath) f:close() end end --这里搞个加载界面 self.Ason = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); if self.currentScene ~= nil then --卸载原来场景 self.currentSceneScirpt:Dispose() SceneManager.UnloadSceneAsync(self.currentScene) self.currentScene = nil end self.currentSceneName = sceneName self:StartLoadingScene() end function MySceneManager:Update(dt) if self.isLoading == false then return end self.text.text = (self.Ason.progress - self.Ason.progress % 0.01 ) * 100 .. "%" self.loading.fillAmount = self.Ason.progress if self.Ason.isDone then self:EndLoadingScene() end end function MySceneManager:StartLoadingScene() self.loadingCanvas.gameObject:SetActive(true) self.text.text = "0%" self.loading.fillAmount = 0 self.isLoading = true end function MySceneManager:EndLoadingScene() --获取当前场景 local scene = SceneManager.GetSceneByName(self.currentSceneName) SceneManager.SetActiveScene(scene) self.currentScene = scene --加载场景脚本 self.currentSceneScirpt = require("Scene." .. self.currentSceneName .. "Scene"):new() self.currentSceneScirpt:Init(self.currentScene) self.loadingCanvas.gameObject:SetActive(false) self.isLoading = false --self.Ason.allowSceneActivation = true end return MySceneManager |
先看到的是上面还引入了两个管理类,一个是PathManager负责路径的管理,统一规范路径的使用,目前就只写了更新文件的目录,这里就不展示出来了。一个是UtilityManager是一个工具类管理器,其实是一个调用Unity启动文件Main.cs启动注册进lua虚拟机的衔接,例如点击事件驱动、保存文件资源等
1 2 |
local PathManager = require("Manager.PathManager") local UtilityManager = require("Manager.UtilityManager") |
接下来看Init方法,这个方法是在GameManager的Init方法驱动调用的。因为传入了挂载了Main.cs的游戏物体,这里就通过这个游戏物体获取加载场景时应该显示的进度条和百分比显示的文本显示物体,在每次加载场景的时候就显示这个进度条界面。在后面定义了一些需要保存加载场景信息的变量。最后直接加载默认的场景,这个默认的场景当然就是更新场景了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function MySceneManager:Init(go) self.goGameObject = go self.loadingCanvas = self.goGameObject.transform:GetChild(0) self.loading = self.loadingCanvas.transform:GetChild(1):GetChild(0):GetComponent(typeof(Image)) self.text = self.loadingCanvas.transform:GetChild(1):GetChild(1):GetComponent(typeof(Text)) self.loadingCanvas.gameObject:SetActive(false) self.isLoading = false self.currentScene = nil self.currentSceneName = nil self.currentSceneScirpt = nil self.currentSceneAssetBundle = nil self.Ason = nil --进入默认场景 self:LoadScene(Const.DefaultSceneName) end |
接下来就是最重要的加载场景的方法了,先判断是否要使用远程的场景资源,如果需要就在更新目录中去查找这个场景文件,如果存在就加载到内存当中并保留好引用,方便在下一次加载其他场景的时候卸载资源。场景文件只要加载到内存当中,加载场景的时候就会优先使用该场景的,所以直接引用保存起来就不需要多做什么了。后面就直接异步叠加的方式的加载场景,保存异步操作指令用来获取进度和加载完成的状态。卸载先前加载的场景和原先驱动脚本的擦屁股操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
function MySceneManager:LoadScene(sceneName) if UtilityManager:GetInstance():GetUseRemoteRes() then local filePath = PathManager:GetInstance():GetUpdateScenePath() .. "/".. sceneName .. ".scene" local f = io.open(filePath,"r") if f ~= nil then if self.currentSceneAssetBundle then --卸载 self.currentSceneAssetBundle:Unload(true) end self.currentSceneAssetBundle = AssetBundle.LoadFromFile(filePath) f:close() end end --这里搞个加载界面 self.Ason = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); if self.currentScene ~= nil then --卸载原来场景 self.currentSceneScirpt:Dispose() SceneManager.UnloadSceneAsync(self.currentScene) self.currentScene = nil end self.currentSceneName = sceneName self:StartLoadingScene() end |
显示加载场景的进度条,初始化加载UI信息,打开加载开关,更新UI的显示内容,待获取到异步操作加载场景的完成,开始驱动场景的运行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function MySceneManager:StartLoadingScene() self.loadingCanvas.gameObject:SetActive(true) self.text.text = "0%" self.loading.fillAmount = 0 self.isLoading = true end function MySceneManager:Update(dt) if self.isLoading == false then return end self.text.text = (self.Ason.progress - self.Ason.progress % 0.01 ) * 100 .. "%" self.loading.fillAmount = self.Ason.progress if self.Ason.isDone then self:EndLoadingScene() end end |
因为场景加载已经时完成的了,就可以通过场景的名字获取到场景,设置这个场景为激活场景。获取到这个场景文件其实时为了获取这个场景中所有的根物体,从而才能获取到所有场景中的游戏物体,进项相应的操作。随后就是驱动相应的场景文件,存放在固定的目录下有着相应的命名规范,直接参考规范引入文件,并调用其的Init方法并传入当前场景资源以获取游戏物体。最后结束加载,隐藏进度条界面。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function MySceneManager:EndLoadingScene() --获取当前场景 local scene = SceneManager.GetSceneByName(self.currentSceneName) SceneManager.SetActiveScene(scene) self.currentScene = scene --加载场景脚本 self.currentSceneScirpt = require("Scene." .. self.currentSceneName .. "Scene"):new() self.currentSceneScirpt:Init(self.currentScene) self.loadingCanvas.gameObject:SetActive(false) self.isLoading = false --self.Ason.allowSceneActivation = true end |
在最后看看场景脚本文件的基类,是怎么获取场景中所有的根物体的。可以看到Init中传入了scene,通过自带的方法GetRootGameObjects获取到所有的根物体,我这里转换成了table,遍历它,使用根物体的名字为索引保存在一个table变量中。在以后使用的时候,可以通过物体的名字,直接查找到该物体。这个基类提供了查找物体的方法,方便使用,后面还可以写更多方便的方法。最后调用一个Main方法,用来开始你自己场景的逻辑,获取游戏物体、注册事件、注册Update等,这样场景就跑起来啦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
--返回的都是Transfrom local SceneBase = class("Base.SceneBase") function SceneBase:ctor() self.rootGameobjects = {} self.currentScene = nil end function SceneBase:Init(scene) self.currentScene = scene local rootObjs = scene:GetRootGameObjects():ToTable() self.rootGameobjects = {} for k,v in ipairs(rootObjs) do --初始化跟物体为名字索引 self.rootGameobjects[v.name] = v.transform end self:Main() end function SceneBase:Main() end function SceneBase:Dispose() end function SceneBase:GetRootGameObjectByName(name) return self.rootGameobjects[name] end --table 或 string function SceneBase:GetChildByName(name,parent) local go = nil local typeName = type(name) if typeName == 'table' then if parent then local searchName = name[1] for i=2,#name do searchName = searchName .. "/" .. name[i] end go = parent:Find(searchName) else local parentName = name[1] local searchName = name[2] for i=3,#name do searchName = searchName .. "/" .. name[i] end parent = self:GetRootGameObjectByName(parentName) if not searchName then go = parent else go = parent:Find(searchName) end end else if parent then go = parent:Find(name) else go = self:GetRootGameObjectByName(name) end end return go end return SceneBase |