一百行代码实现一个简易的红点系统

原理

红点系统其实说简单也简单,说复杂也复杂,其他描述起来就是一颗树状结构,描述起来就是一个建议的图

image-20220610122751485

描述起来就是上面这一个图。看懂上面这张图基本上就能想到要怎么做了,就是某个系统红点更新了同步更新自己的父节点,直到更新到根节点。像节点之间的关系都可以走配置,正常来所有系统的位置应该是固定的了,如果有变化的节点就需要动态修改配置。

实现

配置

这里我使用的是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
-- 描  述:    红点配置
gtRedDotId = {
nRB_Root = 1,
nRB_Root_S1 = 2,
nRB_Root_S1_T1 = 3,
nRB_Root_S1_T2 = 4,
nRB_Root_S2 = 5,
nRB_Root_S2_T1 = 6,
nRB_Root_S2_T2 = 7,
}
--root
--s1
--s1_t1
--s1_t2
--s2
--s2_t1
--s2_T2


gtRedDotConfig = {
[gtRedDotId.nRB_Root_S2_T2] = {
parentT = gtRedDotId.nRB_Root_S2, --父节点
cond = function() return true end, --条件
trigger = {}, --更新的事件合集
},
[gtRedDotId.nRB_Root_S2_T1] = {
parentT = gtRedDotId.nRB_Root_S2, --父节点
cond = function() return true end, --条件
trigger = {}, --更新的事件合集
},
[gtRedDotId.nRB_Root_S2] = {
parentT = gtRedDotId.nRB_Root, --父节点
cond = function() return true end, --条件
trigger = {}, --更新的事件合集
},
[gtRedDotId.nRB_Root_S1_T2] = {
parentT = gtRedDotId.nRB_Root_S1, --父节点
cond = function() return true end, --条件
trigger = {}, --更新的事件合集
},
[gtRedDotId.nRB_Root_S1_T1] = {
parentT = gtRedDotId.nRB_Root_S1, --父节点
cond = function() return true end, --条件
trigger = {}, --更新的事件合集
},
[gtRedDotId.nRB_Root_S1] = {
parentT = gtRedDotId.nRB_Root, --父节点
cond = function() return true end, --条件
trigger = {}, --更新的事件合集
},
[gtRedDotId.nRB_Root] = {
parentT = nil, --父节点
cond = function() return true end, --条件
trigger = {}, --更新的事件合集
},
}

--这一次遍历是为了求子节点和父节点的并集给到父节点
for i, v in pairs(gtRedDotConfig) do
if v.parentT then
local node = gtRedDotConfig[v.parentT]
if not node.childNode then
node.childNode = {}
end
node.childNode[i] = v
end
v.nMyId = i
end

image-20220610143601403

直接把红点全部定义好,把所有的红点信息的关系先配置上去,和上面这个思维导图一致,生成图片就是这样的结构,最后的计算就是给每个父节点添加子节点,直接程序操作。

  • parentT :父节点对应的id
  • cond : 红点条件,比如钱够不够
  • trigger:触发更新红点的事件合集,比如钱变化了。

核心逻辑

  • BindMethodRD :初始化红点,关联红点Id和红点View
  • TraverseNode :递归遍历子节点的红点信息
  • RelieveMethodRD :解除红点Id和红点View,对应View被销毁触发
  • UpdateMethodRD :更新红点的显隐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---BindMethodRD 绑定一个红点信息
---@param nMethodId number gtRedDotId
---@param uObj UnityEngine.GameObject 红点GameObject
function BindMethodRD(nMethodId, uObj)
local node = tRedDot[nMethodId]
if not node then
return
end
local bShow = node.cond()
--如果自己没红点,查询子节点是否有红点
if not bShow and node.childNode then
bShow = TraverseNode(node.childNode, true)
end
node.uRDObj = uObj
--实现你的红点显隐方式
uObj:SetActive(bShow)
end

初始化红点,把红点Id和GameObject的绑定起来,我这里用的是Unity如果你是其他的引擎,同理其他隐藏只要实现你的红点显隐操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---TraverseNode 遍历子节点,是否有红点显示
---@param childNode table 子节点列表
function TraverseNode(childNode)
for _, v in pairs(childNode) do
if v.cond() then
return true
elseif v.childNode then
if TraverseNode(v.childNode) then
return true
end
end
end
return false
end

这就是遍历子节点获取子节点的状态,只要有红点显示,直接更新到父节点。

1
2
3
4
5
6
7
8
9
10
11
12
---RelieveMethodRD 解除关联的红点信息
---@param nRDId number gtRedDotId
function RelieveMethodRD(nRDId)
if not nRDId then
return
end
local t = gtRedDotConfig[nRDId]
if not t then
return
end
t.uRDObj = nil
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
29
30
31
32
33
34
35
36
37
38
---UpdateMethodRD 更新一个红点信息
---@param nRDId number gtRedDotId
function UpdateMethodRD(nRDId)
local tPlayConfig = gtRedDotConfig[nRDId]
if not tPlayConfig then
return
end
local bShow = false
local bInvoke = false
if tPlayConfig.parentT then
bShow = tPlayConfig.cond()
if not bShow and tPlayConfig.childNode then
bShow = TraverseNode(tPlayConfig.childNode, false)
end
bInvoke = true
--如果是true,直接更新到root节点
if bShow then
local node = tPlayConfig
while node.parentT do
node = gtRedDotConfig[node.parentT]
node.uRDObj:SetActive(bShow)
end
else
UpdateMethodRD(tPlayConfig.parentT)
end
end
--更新自己
if not tPlayConfig.uRDObj then
return
end
if not bInvoke then
bShow = tPlayConfig.cond()
end
if not bShow and tPlayConfig.childNode then
bShow = TraverseNode(tPlayConfig.childNode, false)
end
tPlayConfig.uRDObj:SetActive(bShow)
end

更新红点,这个相对复杂,其实就是某个条件完成后显示或隐藏红点,需要一直更新到根节点,核心逻辑就是判断有没有父节点,如果自身的条件改变了就更新到父节点,这里我使用的事件来驱动更新,或者可以直接更新。

1
2
3
4
5
6
7
8
9
--这里注册每个系统红点更新事件
for nId, tNode in pairs(gtPlayMethodConfig.tRedDot) do
tNode.funcRDCallBack = function()
UpdateMethodRD(nId)
end
for _, v in pairs(tNode.trigger) do
--todo 实现你的红点触发事件,把他绑定到funcRDCallBack
end
end

更新事件和红点id绑定,这里直接是用的事件来更新,操作,或者可以直接执行UpdateMethodRD()

优化

每个节点保存之前的状态,只有变化才去执行更新逻辑。

某些更新频繁的时间做成通用的就不需要放到事件合集,比如钱币变化,等级提升,功能开放。

局限性

这个对动态变化的节点不适用,比如某个系统在a时间段是在b入口,在c时间段又是在d入口

Git链接

完整代码