小眼游戏架构:UI篇:三层架构(UI层)

上一篇我们分析了UI的架构原则以及为什么要这么架构的一些原因,这篇会具体实现架构的方方面面,东西会有点多。我辈求道,岂能求快!

为了避免框架的干扰我们先来配置一下开发环境。
LuaFrameWork的启动是在:
在这里插入图片描述
加载的相关可以看这里(加载
上面主要做的就是去除和我们现在没有联系的模块,避免一些问题。


现在开始分析具体流程。
经过前一篇的分析我们可以总结出:
UI管理层UIManager):加载、打开、关闭、卸载。管理UI集合的自动打开和关闭,以及对外的接口。
UI集合层UIBaseCollect):加载、打开、关闭、卸载。管理多个UI
UI层UIBaseView):加载、打开、关闭、卸载。管理UI本身的生命周期。
其实流程都是类似的,一层层的管理下去。

流程总览
在这里插入图片描述
上面的图是我们的三层架构流程总览图。
按列划分:
第一列:UI管理层UIManager)相关操作;
第二列:UI集合层UIBaseColelct)相关操作;
第三列:UI层UIBaseView)相关操作;
实现代表同步,虚线代表异步(不在同一帧执行)。

我们采取自下而上的讲解方式。那么先开始UI层吧。

UI层

一般的UI流程肯定是有以下流程:加载、打开、关闭、卸载
在分析详细四个流程前,我们先确认下Prefab的制作规范,这样对于讲解后面的流程是有帮助的。
Prefab的制作要求:在这里插入图片描述
上图有两个部分:编辑器模式下的Prefab显示,和运行时的Prefab显示
这样一对比会发现这两个图有点对不上,是的,运行时和编辑器模式下的显示不是一样的。

  1. 先看编辑器模式
    绿色是UIRoot,黄色是UI相机,红色是Prefab的主题结构。
  2. 再看运行时部分
    红色是代表UIRoot,绿色代表Prefab的主题就够。
    因为整个UI使用一个UIRoot和一个UI相机,但是现在每个UI都有一个UIRoot和一个相机,所以我们将这两个部分抽离出来形成"ui/prefab/uibase"结构,这个节点将会是所有UI的父节点。后续UI在加载后会将Prefab下Core节点改名成"ui/prefab/uipanel_messagebox"放到UIRoot(“ui/prefab/uipanel_base”)下面去;
    好了Prefab的制作规范就完成了。
    下图是代码解释:加载完成Prefab之后实例化过程。
public static GameObject InitGameObject(int type, GameObject obj, string assetName)
{
    ......
    // UI
    else if(type == 2)
    {
        GameObject root = GameObject.Find("ui/prefab/uipanel_base");
        parent = root.transform;
        son = obj.transform.FindChild("Core");
        GameObject.Destroy(obj);
    }
	......
}

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
现在来说UI的四个流程

UI加载

先看图
在这里插入图片描述

  1. 打开参数:有时候我们打开一个UI需要特定的一些数据去做显示,比如:UI上有多个页签,我现在想打开UI前就决定显示到哪个页签。我们经常会自己写一个管理器去管理UI的行为,这并没有问题,但是有时候我们会将数据放到管理器中,然后UI调用管理器中的数据,这样的设计并不好,与UI相关的数据最好通过参数的形式传进去,这就是我们打开参数设置的目的。
  2. 加载prefab是异步的:所以需要注意异步的问题,卸载的时候,在请求加载中需要取消加载。
  3. 绑定控件:这时一种省时省力的做法,后面会有单独的一个章节来讲解,这里就不说了。
  4. 注册UI事件 :UI的监听事件不会动态的改变,所以放在加载里面注册就可以了。我们处理事件的方式是:只有显示的UI才能收到监听事件的回调。你可能会问不显示的UI就收不到?是的,因为UI在显示时会重新刷新页面,所以就不需要在隐藏的时候更新了。
  5. 加载完成:加载完成之后会通知子类,这样子类就能知道加载已经完成,可以开始自己的操作了。
事件设计

既然UI上用到了事件注册,那我们就来说说事件的设计。

--[[
	注册消息,发送消息
--]]
local Message = {};

-- 消息体
function BeginMessage(msgName)
	local msg = {};
	msg.name = msgName;
	return msg;
end

-- 发送消息
function SendMessage(msg)
	DispatchMessage(msg);
end

-- 注册消息
function RegisterMessage(msgName,tCall,t)
	if not Message[msgName] then 
		Message[msgName] = {};
	end
	local inIndex = table.ContainValue(Message[msgName],tCall,"tCall");
	if inIndex ~= 0 then
		return;
	end
	local msg = {};
	msg.t = t;
	msg.tCall = tCall;
	table.insert(Message[msgName],msg);
end

-- 删除消息
function RemoveMessage(msgName,call)
	if not Message[msgName] then 
		return;
	end
	local inIndex = table.ContainValue(Message[msgName],call,"tCall");
	if inIndex ~= 0 then
		table.remove(Message[msgName],inIndex);
	end
end

-- 触发注册消息
function DispatchMessage(msg)
	local registers = Message[msg.name];
	if not registers then 
		return;
	end
	for i,v in ipairs(registers) do
		v.tCall(v.t,msg);
	end
end

事件的流程比较简单,lua就中这点好,包罗万象,所以事件的实现方式简单。

UI打开

先看图
在这里插入图片描述

  1. 计算UI层级:想让UI呈现出前后的关系,我们需要改变UIPaneldepth值,我们在制作Prefab的过程中也会把UIPanel当成一个页面的容器。
  2. 最后显示的Prafab层级是最高的。
-- 设置UI层级
function _M:SetUILayer()
	local obj = GetGameObjectById(self.uiInstanceId);
	if not obj then 
		print("SetUILayer is error " , self.name);
		return;
	end
	-- 获取一个层级,这个层级是目前最大的
	local layer = UILayer:CalculateLayer(self);
	self:AddUILayerHelper(layer);
end
-- 删除UI层级
function _M:DelUILayer()
	local obj = GetGameObjectById(self.uiInstanceId);
	if not obj then 
		print("SetUILayer is error " , self.name);
		return;
	end
	local layer = UILayer:DelLayer(self);
	self:AddUILayerHelper(-layer);
end
function _M:AddUILayerHelper(layer)
	local obj = GetGameObjectById(self.uiInstanceId);
	if not obj then 
		print("AddUILayerHelper is error " .. self.name);
		return;
	end
	local addLayer = layer * 100;
	local panels = CommonUtil.GetUIPanels(obj);
	-- C#中的用法 在lua中显得很另类
	for i = 0, panels.Length - 1 do
		panels[i].depth = panels[i].depth + addLayer;
	end
end

实际上就是寻找整个PreafabUIPanel然后全部加上一个值,这个值是计算出来的,用来保证当前显示的PrefabUIPaneldepth是最大的,计算的方法是写在UILayer里面:保存一个当前UI对应的列表,每次打开UI的时候就去找到最后打开的一个然后+1。
UILayer设计如下:
在这里插入图片描述
3. atals的加载:这里的atlas包括图集图片Prefab包括:自身的序列化信息和引用的信息。Prefab本身只是序列化数据,不大,但是每次加载解析却浪费了时间,尤其是Prefab的结构越复杂,需要的时间就越多。既然Prefab本身不大那我们保证Prefab在不切换场景前不卸载,只卸载绑定在Prefab上的图集图片,这样内存也没有增加多少,再次打开这个界面的时候会很快。
4. 显示完成Prefab显示之后会通知子类,Prefab已经显示完成,可以开始自己的操作了。自己的操作:根据数据刷新页面显示。

UI关闭

先看图
在这里插入图片描述
关闭UI的流程和打开UI的流程刚还是相反的,所以就不说了。

UI卸载

在这里插入图片描述
卸载UI流程和加载UI流程也是相反的,所以就不说了。

Lua类的设计:

既然我们采取类的模式,那么lua得提供类的功能,lua实现类的逻辑很巧妙,luatable中有一个重要特性,可以给当前的table设置一个元表(也是一个table),这样在当前table中找不到的属性方法可以去元表table中去寻找,这个机制不就和类的方式是类似的么?这样就能轻松实现类了。
在这里插入图片描述

基类UIBaseVIew设计:

在这里插入图片描述
创建一个UIBaseView基类(所有UI的父类),ctor是构造函数。

使用者的子类设计:

在这里插入图片描述
UIPanel_MessageBox继承自UIBaseView,这样UIPanel_MessageBox也就拥有和UIBaseView一样的生命周期。

这样我们UI层UIBaseView)就设计完成了。

项目地址:https://github.com/xiaoyanxiansheng/SmallEyeGame

下一篇:UI集合层

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值