- MVC设计模式
脚本框架遵循MVC设计模式,每个模块的主体大致分为Model(BaseModel类)、View(BaseView类、BaseWindow类、BasePanel类)、Control层(BaseManager类)。
- Model类主要负责处理协议传递的数据,包括格式化、存储、增删查改等,还负责管理View的打开或关闭等操作;
- View类主要负责直接与玩家交互的UI层面的表现处理,BaseWindow与BasePanel均继承BaseView,根据需求将界面分为“窗口”或“面板”处理。玩家在UI层的操作传递到Model类进行数据处理,处理完毕后Model又将数据传递至View执行表现(刷新)。
- Controller(Manager)类主要负责协议的请求和接收数据。同时作为对外相关model的调用接口,即不直接调用xxxModel.Instance:xxx(),而是在Manager中封装了可能要用到的方法,调用xxxManager.Instance:xxx()
Window类和Panel类都继承View类。
view会率先执行__init(model)方法,该执行顺序逻辑由基类BaseClass定义。在这里会进行一些属性的初始化,例如设置数据模型对象、命名、窗口id、缓存类型、打开窗口传入参数、窗口打开和隐藏触发的事件、初始化完成触发的事件、是否全屏显示、是否进入控制队列、是否在切换场景时关闭等。
self.model为__init(model)中传入的model,self.name为在unity中该游戏物体的名称。self.windowId的设置则需要先在WindowConfig.WinID中注册一个专属id。
随后在WindowConfig.WindowList中为对应的id赋予class和model。
self.resList为该窗体所需要加载的预制件,包括本身的主体和一些大背景。path为该预制件在resources工程中的路径,需要在AssetConfig中进行注册;type为资源类型,预制件统一为AssetType.Prefab。BigPref大图预制也要在resList中定义。
self.OnHideEvent和self.OnOpenEvent分别为窗口隐藏和打开所触发的事件,皆为新创建的EventLib对象,可直接通过:Add(function() self:xxx end)的形式执行需要触发的方法。
self.append_to_ctl_queue 参数控制窗口是否进入控制队列,若进入控制队列则会接受WindowManager的统一管理,在打开该队列中的另一窗口时会自行隐藏。
self.changeSceneClose 控制窗口是否在跳转场景时自行关闭。
2.1.2 Open/Show加载资源设置完初始属性后,window类会执行基类BaseWindow:Open(arge)方法,panel类则执行基类BasePanel:Show(arge)方法。这两个方法都会先对self.loading(是否加载过该资源)进行判断,若加载过则直接return,否则继续往下执行。随后又对self.gameObject进行判断该窗口是否实例化过,若不为空,说明已经实例化过,只是被隐藏,因而将其归回原位或激活。若为空,说明还未实例化,随即对self.resList是否为空且表长度是否大于零进行判断,据此执行self:LoadAllAsset()进行资源加载或执行self:OnResLoadCompleted()跳过加载。
if self.resList ~= nil and #self.resList > 0 then self:LoadAllAsset() else self:OnResLoadCompleted() end
LoadAllAsset()是BaseView中定义的方法,该方法会新建一个AssetBatchLoader对象,该对象负责AssetBundle资源的加载,通过执行LoadAll(self.resList)将需要的资源全部加载出来,加载完毕后执行回调self:OnResLoadCompleted()
-- 资源加载完毕事件
function BaseView:OnResLoadCompleted() self:InitPanel() self:__OnInitCompleted() self:InitEventListener() end2.1.3 InitPanel获取组件
这一步主要是获取需要操作或者响应的游戏物体及其组件。首选是获取self.transform,无论是window还是panel都需要通过InitMainGameObject()获取,该方法会执行UtilsUI.AddUIChild(BaseCanvas, gameObject)将窗口设置为BaseCanvas的子物体,并返回transform组件。而其他组件的获取均使用BaseView下封装好的接口进行获取,不需要再使用transform:Find("xxx"):GetComponent()等API。另外,一些大背景预制件的添加也在这一步进行。同样通过基类封装好的GetGameObject(AssetConfig.xxx)获取,并用UtilsUI.AddBigbg()设置背景为该窗口的子物体。
2.1.4 __OnInitCompleted初始化完成此时基本属性和组件变量初始化完成,window类会额外执行WindowManager.Instance:OnOpenWindow(self)打开窗口,对前面__init()中定义的append_to_ctl_queue进行判断。若该窗口是要加入控制队列,且当前控制队列中还没有改窗口时,会将队列中其他窗口隐藏,并为该窗口分配win_id,并加入window_ctl_queue队列。
if win.append_to_ctl_queue and self.window_ctl_queue[win] == nil then for window, _ in pairs(self.window_ctl_queue) do window:Hide() end self.win_id = self.win_id + 1 self.window_ctl_queue[win] = self.win_id end2.1.5 InitEventListener注册事件监听
资源加载完毕的最后会进行监听事件的注册。此处可使用BaseView类下的AddGlobalEvent(eventName,callback)进行添加,若事件码还未定义,则需要在EventName类中定义。添加完毕后该事件会加入到_eventList事件表当中,窗口关闭时会自动解除监听,无需手动解除。
2.1.6 __delete释放内存在窗口加载、运行时不可避免地会产生一些新的对象,例如FSScrollView等。这些对象不同于私有变量随着对象的销毁而销毁,若在本对象销毁之后没有及时处理这些新建的对象就会造成内存泄漏,导致内存无法释放。因此在该阶段需要手动利用UtilsBase.FieldDeleteMe将这些对象销毁。此外,外部引用到本view类对象的地方也要手动清除,例如有TimerManger计时器添加过本对象声明的id和函数或Tween动画队列添加过本对象声明的组件和函数等情况都需要手动删除。
需要显式调用销毁方法的对象:
- 通过New()方法新建的对象
self.scrollView = FSScrollView.New(self:GetChild(self.transform, "Main/ScrollView"), BackpackGrid, 1)
在背包界面需要用到scrollview的复用,因而新建了FSScrollView的对象,此时就需要用
UtilsBase.FieldDeleteMe(self, "scrollView")
进行销毁。
- 存储新建对象的表
local slot = ItemSlot.New()
self.itemList[i] = slot
在有物品槽的界面往往需要创建ItemSlot对象,并用itemList存储起来,这时候就需要用到
UtilsBase.TableDeleteMe(self, "itemList")
将该表下的变量清空。实际TableDeleteMe即循环调用了FileldDeleteMe()。
- 实例化的游戏物体
self.effectPoint = GameObject("EffectFlyPoint")
在窗口中,将二维坐标转到三维坐标时会需要创建一个空物体替代转换后的transform,此时需要用
UtilsBase.DestroyGameObject(self,"effectPoint")
销毁该物体。
- Tween动画
窗口中一些特效或者动效需要用到Tween对象,而窗口销毁后若tween动画没有删除会导致transform组件丢失而报错。可以使用
UtilsBase.TweenIdListDelete
清除动画列表或用
UtilsBase.TweenDelete
清除单个动画。
- 计时器
窗口用到计时器时同样需要在窗口消失时将新增的计时回调从TimerManager中删除,同样也可以使用
UtilsBase.TimerDelete
清除单个计时或用
UtilsBase.TimerListDelete
清除计时列表。



