348 lines
11 KiB
Lua
348 lines
11 KiB
Lua
--通用红点管理器
|
||
--任意模块通过 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 --全局默认特效 ID;nil 则回退为图片红点
|
||
|
||
--注册红点
|
||
--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" 可选;业务模块 __cname,Up_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
|