--通用红点管理器 --任意模块通过 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