红点系统设计到的内容有哪些
UI界面(例如主界面)、UI上的按钮红点(例如主界面上面有各种玩法的入口)、玩法数据(例如活动玩法的完成比例,它是驱动红点是否显示的关键要素)、红点管理器(修改红点状态,注册红点物体)、树形数据结构。
红点系统的特点
- 数据驱动
- 树形结构,且数据是从下向上修改的
提问:红点UI何时会亮起
答:当这个UI打开时,树形结构这个节点是亮起状态。
提问:树形结构这个节点如何判断亮起还是熄灭?
红点是显示还是隐藏是由数据驱动的,所以这个问题就是何时会修改数据
答:有3种情况,1、登录时拉取所有功能数据,2、服务器主动推送刷新功能数据(有部分功能在服务器不断Tick计算状态)3、用户操作后刷新功能数据(比如领取邮件奖励)
红点树型脑图
玩法数据是如何与红点显示状态关联的?
以邮件系统举例,
- 项目启动时,拉取服务器邮件数据,客户端的邮件管理器判断邮件列表1和2是可领取状态,向红点管理器设置邮件红点的状态为亮起(true)
- 进入主城并显示主城UI后,主城UI调用红点管理器的注册红点方法,此方法会拿到邮件对应的红点状态并设置红点UI是否显示。此时邮件按钮亮起
- 当用户点击领取邮件,前后端通信一次,服务器推送最新的邮件数据,客户端邮件管理器再次判断是否有可领取邮件,此时列表里还有一个未领取邮件,并再次设置邮件红点的状态为亮起(true)。
- 用户再次领取邮件,服务器推送最新的邮件数据,此时邮件已全部领取完毕,设置邮件红点的状态为熄灭(false)。
树形结构的难点在哪里
以脑图为例,夏季活动有两个分支(夏令营和运动),且都是红点亮起状态
当红色链条夏令营篝火晚会完成时,我们既要设置篝火晚会、夏令营的红点状态为熄灭,还不能影响蓝色链条的红点显示状态。因为这两个链条的数据是独立的
我是如何解决这两个链条互不影响的?
--[[
遍历tab并查找目标
找到目标后按深度加入列表
]]
local function travTab(tab, target,path)
local find = false
for key, subTable in pairs(tab) do
if type(subTable) == "table" then
if subTable == target then
table.insert(path,target)
return true
else
find = travTab(subTable,target,path)
if find == true then
table.insert(path,subTable)
return true
end
end
end
end
return false
end
核心就是这个深度递归获取路径的方法,
tab是活动的根、target是篝火晚会,通过这个递归可以返回一个(1篝火晚会、2夏令营、3夏季活动、4活动)的列表。
拿到列表后for循环每一个元素的父节点是否有子物体是亮起红点的。当循环到父节点是夏季活动这个节点时,检查到夏季活动的子节点运动是红点亮起状态,那么夏季活动这个节点就不会被夏令营节点的改变而影响,实现了红色链条和蓝色链条的互不影响。
--[[
检查父节点的所有子节点是否亮起
]]
local function CheckSubRedDot(parent)
for index, subTab in pairs(parent) do
if type(subTab) == "table" then
if subTab.value == true then
return true
end
end
end
return false
end
Lua版本红点系统实现
现在的UI业务大多数都是用Lua开发的,那我就写个Lua版本的红点系统
当我们require RedDotDefine.lua文件时,RedDot_Activity全局存在,可直接使用
制作好demo需要的UI,冬季活动的分支没做,夏季的足够演示
下面是RedDotManager的源码
require 'Framework/RedDot/RedDotDefine'
--UI音频管理类--
RedDotManager = {
IsRootLoaded = false
}
local this = RedDotManager
local RedDotDatas = {}
--[[
遍历tab并查找目标
找到目标后按深度加入列表
]]
local function travTab(tab, target,path)
local find = false
for key, subTable in pairs(tab) do
if type(subTable) == "table" then
if subTable == target then
table.insert(path,target)
return true
else
find = travTab(subTable,target,path)
if find == true then
table.insert(path,subTable)
return true
end
end
end
end
return false
end
--[[
注册红点UI
]]
function RedDotManager.RegisterRedDotUI(target,redDotUI)
target.RedDotUI = redDotUI
redDotUI:SetActive(target.value == true)
end
function RedDotManager.UnRegisterRedDot() end
--[[
检查父节点的所有子节点是否亮起
]]
local function CheckSubRedDot(parent)
for index, subTab in pairs(parent) do
if type(subTab) == "table" then
if subTab.value == true then
return true
end
end
end
return false
end
--[[
设置红点状态
]]
function RedDotManager.SetRedDotState(tab,target,value)
target.value = value
if target.RedDotUI ~= nil then
target.RedDotUI:SetActive(value)
end
--[[
获取目标到根的深度路径
tab是根,target是最深的节点
]]
local path = {}
travTab(tab,target,path)
table.insert(path,tab)
for index = 1, #path do--body
if index == #path then
return
end
local parent = path[index + 1]
parent.value = CheckSubRedDot(parent)
if parent.RedDotUI ~= nil then
parent.RedDotUI:SetActive(parent.value)
end
end
end
function RedDotManager.GetRedDotState(target)
return target.value == true
end
项目启动后,设置脑图中的篝火晚会和游泳节点设置为亮起
当主界面显示时,注册主界面的红点,其他所有界面启动后,注册红点都相同,就不挨个展示了
这个Demo中实现的玩法和上面的脑图保持一致,但只做了活动玩法的红点展示
红点系统其他实现方式
除了用Table来存储树形结构关系外,还有很多方式可以实现红点系统,例如直接用int值来存储,子节点都用根节点的值做加法,或者是乘法。所以说,只要你能找到一个方式能实现从下到上的节点索引,即可解决自下而上驱动树型结构数据的问题。