bayuMIR/client/dev/GUILayout/public/RedDotMgr.lua
2026-06-12 02:32:15 +08:00

348 lines
11 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--通用红点管理器
--任意模块通过 RedDotMgr:register(key, conf) 注册一个红点节点
--框架自动监听服务器变量 / 等级 / 转生变化,按需刷新对应节点
--支持父子聚合:子注册时声明 parent父节点状态 = 任一子节点亮
RedDotMgr = RedDotMgr or {}
RedDotMgr._nodes = RedDotMgr._nodes or {} --key -> conf
RedDotMgr._watchKey2Keys = RedDotMgr._watchKey2Keys or {} --服务器变量 key -> { redKey1, ... }
RedDotMgr._watchLevelKeys = RedDotMgr._watchLevelKeys or {} --监听等级变化的 key 集合
RedDotMgr._watchBagKeys = RedDotMgr._watchBagKeys or {} --监听背包变化的 key 集合
RedDotMgr._watchTitleKeys = RedDotMgr._watchTitleKeys or {} --监听称号变化的 key 集合
RedDotMgr._parent2Children = RedDotMgr._parent2Children or {} --parent -> { childKey1, ... }
RedDotMgr._owner2Keys = RedDotMgr._owner2Keys or {} --owner(业务模块 __cname) -> { redKey1, ... }
RedDotMgr._inited = RedDotMgr._inited or false
--默认偏移
RedDotMgr.DEFAULT_OFFSET = { x = 15, y = 15 }
RedDotMgr.DEFAULT_EFFECT_ID = 50378 --全局默认特效 IDnil 则回退为图片红点
--注册红点
--conf:
-- target : function() return node end 红点挂载节点懒求值UI 可能未创建)
-- check : function() return bool end 是否亮(叶子节点);省略时自动聚合所有子节点
-- watchKeys : { "{8}", "HUMAN(STR_xx)" } 监听的服务器变量
-- watchLevel : true|false 是否监听等级 / 转生
-- watchBag : true|false 是否监听背包物品变化
-- watchTitle : true|false 是否监听称号变化LUA_EVENT_TITLE_REFRESH
-- parent : "TopIcon_5" 父 key子亮则父跟着亮
-- offset : { x=5, y=5 } 红点偏移;省略时取 DEFAULT_OFFSET
-- effectId : number 可选;填了用 Effect_Create 贴特效代替默认图片红点
-- owner : "ShouChongOBJ" 可选;业务模块 __cnameUp_BaseClassOBJ:main 跑完后会自动 refreshByOwner
-- 让 check 依赖模块内存数据XxxOBJ.cfg的红点不需要在 updata 末尾手动兑底
function RedDotMgr:register(key, conf)
if not key or not conf then
return
end
--幂等:同 key 重复注册先注销旧的
if self._nodes[key] then
self:unregister(key)
end
self._nodes[key] = conf
--反查表
if conf.watchKeys then
for _, k in ipairs(conf.watchKeys) do
self._watchKey2Keys[k] = self._watchKey2Keys[k] or {}
table.insert(self._watchKey2Keys[k], key)
end
end
if conf.watchLevel then
self._watchLevelKeys[key] = true
end
if conf.watchBag then
self._watchBagKeys[key] = true
end
if conf.watchTitle then
self._watchTitleKeys[key] = true
end
if conf.parent then
self._parent2Children[conf.parent] = self._parent2Children[conf.parent] or {}
table.insert(self._parent2Children[conf.parent], key)
end
if conf.owner then
self._owner2Keys[conf.owner] = self._owner2Keys[conf.owner] or {}
table.insert(self._owner2Keys[conf.owner], key)
end
self:_initListener()
--立即应用一次target 可能尚未创建apply 内部会安全跳过)
self:refresh(key)
end
--注销
function RedDotMgr:unregister(key)
local conf = self._nodes[key]
if not conf then
return
end
--先关掉视觉
if conf._dot and GUI:Win_IsNotNull(conf._dot) then
GUI:removeFromParent(conf._dot)
end
conf._dot = nil
--从反查表中剔除
if conf.watchKeys then
for _, k in ipairs(conf.watchKeys) do
local list = self._watchKey2Keys[k]
if list then
for i = #list, 1, -1 do
if list[i] == key then
table.remove(list, i)
end
end
if #list == 0 then
self._watchKey2Keys[k] = nil
end
end
end
end
self._watchLevelKeys[key] = nil
self._watchBagKeys[key] = nil
self._watchTitleKeys[key] = nil
if conf.parent then
local list = self._parent2Children[conf.parent]
if list then
for i = #list, 1, -1 do
if list[i] == key then
table.remove(list, i)
end
end
if #list == 0 then
self._parent2Children[conf.parent] = nil
end
end
end
if conf.owner then
local list = self._owner2Keys[conf.owner]
if list then
for i = #list, 1, -1 do
if list[i] == key then
table.remove(list, i)
end
end
if #list == 0 then
self._owner2Keys[conf.owner] = nil
end
end
end
self._nodes[key] = nil
end
--单点刷新
function RedDotMgr:refresh(key)
local conf = self._nodes[key]
if not conf then
return
end
local on = self:isOn(key)
local prev = conf._on
conf._on = on
self:_apply(key, on)
--状态变化时向上传播给父节点
if prev ~= on and conf.parent then
self:refresh(conf.parent)
end
end
--全量刷新(登录、重连、首次进入用)
function RedDotMgr:refreshAll()
for key in pairs(self._nodes) do
self:refresh(key)
end
end
--刷新某父节点的所有子节点适合切页面、UI 重建后批量重挂)
function RedDotMgr:refreshChildren(parentKey)
local list = self._parent2Children[parentKey]
if list then
--拷贝快照避免遍历中被改动
local snapshot = {}
for i, v in ipairs(list) do
snapshot[i] = v
end
for _, k in ipairs(snapshot) do
self:refresh(k)
end
end
--父节点也刷一次保证状态同步
if self._nodes[parentKey] then
self:refresh(parentKey)
end
end
--按 owner(业务模块 __cname) 批量刷新:专为解决 check 依赖模块内存数据XxxOBJ.cfg的填充时机问题
--Up_BaseClassOBJ:main 跑完后会自动调用本方法,业务层不再需要在 updata 末尾手动 refresh
function RedDotMgr:refreshByOwner(owner)
if not owner then
return
end
local list = self._owner2Keys[owner]
if not list then
return
end
--拷贝快照避免遍历中被 unregister 改动
local snapshot = {}
for i, v in ipairs(list) do
snapshot[i] = v
end
for _, k in ipairs(snapshot) do
self:refresh(k)
end
end
--查询某 key 是否亮(外部偶尔需要)
function RedDotMgr:isOn(key)
local conf = self._nodes[key]
if not conf then
return false
end
if conf.check then
local ok, val = pcall(conf.check)
return ok and val and true or false
end
--无 check聚合所有子节点
local children = self._parent2Children[key]
if children then
for _, ck in ipairs(children) do
if self:isOn(ck) then
return true
end
end
end
return false
end
--应用红点节点的视觉
function RedDotMgr:_apply(key, on)
local conf = self._nodes[key]
if not conf then
return
end
local target = conf.target and conf.target() or nil
if not target or GUI:Win_IsNull(target) then
--target 当前不在场UI 未创建/已销毁),保留状态等下次 refresh
--旧 _dot 引用已随 target 销毁,置 nil 防悬挂
conf._dot = nil
return
end
if on then
--已存在且仍有效:不动
if conf._dot and GUI:Win_IsNotNull(conf._dot) then
return
end
local offset = conf.offset or self.DEFAULT_OFFSET
local effectId = conf.effectId or self.DEFAULT_EFFECT_ID
if effectId then
--特效模式GUI:Effect_Create(parent, name, x, y, 0, sfxID)
conf._dot = GUI:Effect_Create(target, "_RedDot_" .. tostring(key),
offset.x or 0, offset.y or 0, 0, effectId)
else
conf._dot = SL:CreateRedPoint(target, offset)
end
else
if conf._dot and GUI:Win_IsNotNull(conf._dot) then
GUI:removeFromParent(conf._dot)
end
conf._dot = nil
end
end
--初始化全局监听(只装一次)
function RedDotMgr:_initListener()
if self._inited then
return
end
self._inited = true
SL:RegisterLUAEvent(LUA_EVENT_SERVER_VALUE_CHANGE, "RedDotMgr", function(data)
if not data or not data.key then
return
end
local list = self._watchKey2Keys[data.key]
if not list then
return
end
--拷贝快照避免遍历中被 unregister 改动
local snapshot = {}
for i, v in ipairs(list) do
snapshot[i] = v
end
for _, k in ipairs(snapshot) do
self:refresh(k)
end
end)
local function refreshLevelKeys()
local snapshot = {}
for k in pairs(self._watchLevelKeys) do
table.insert(snapshot, k)
end
for _, k in ipairs(snapshot) do
self:refresh(k)
end
end
SL:RegisterLUAEvent(LUA_EVENT_LEVEL_CHANGE, "RedDotMgr", refreshLevelKeys)
SL:RegisterLUAEvent(LUA_EVENT_REINLEVEL_CHANGE, "RedDotMgr", refreshLevelKeys)
SL:RegisterLUAEvent(LUA_EVENT_BAG_ITEM_CHANGE, "RedDotMgr", function()
local snapshot = {}
for k in pairs(self._watchBagKeys) do
table.insert(snapshot, k)
end
for _, k in ipairs(snapshot) do
self:refresh(k)
end
end)
--称号变化(获得 / 失去称号):只刷明确声明了 watchTitle = true 的节点
--谁需要谁自己勾选,不做全量 refreshAll避免不相干红点被误触
SL:RegisterLUAEvent(LUA_EVENT_TITLE_REFRESH, "RedDotMgr", function()
local snapshot = {}
for k in pairs(self._watchTitleKeys) do
table.insert(snapshot, k)
end
for _, k in ipairs(snapshot) do
self:refresh(k)
end
end)
end
--快捷工具:给不走注册法的临时红点(如 cell 内按钮)贴一个红点,统一使用全局默认特效
--返回创建出的节点(随 target 销毁自动销毁,无需手动管理)
function RedDotMgr.attachDot(target, offset, effectId)
if not target or GUI:Win_IsNull(target) then
return nil
end
--幂等:先清掉同 target 上旧的临时红点,避免 updata 重复调用叠加/残留
RedDotMgr.detachDot(target)
offset = offset or RedDotMgr.DEFAULT_OFFSET
effectId = effectId or RedDotMgr.DEFAULT_EFFECT_ID
if effectId then
return GUI:Effect_Create(target, "_RedDot_tmp",
offset.x or 0, offset.y or 0, 0, effectId)
else
return SL:CreateRedPoint(target, offset)
end
end
--手动清除 attachDot 贴上去的红点(状态变化但 UI 不重建时用)
function RedDotMgr.detachDot(target)
if not target or GUI:Win_IsNull(target) then
return
end
GUI:removeChildByName(target, "_RedDot_tmp")
end
return RedDotMgr