lua-游戏红点提示系统抽象设计

发布于:2025-02-25 ⋅ 阅读:(11) ⋅ 点赞:(0)


前言

在游戏开发中,红点提示系统可以通过树形结构和策略模式进行抽象,实现高扩展性。以下是基于Lua的实现方案:


一、定义红点节点类型

节点分为两种类型:

  • 叶子节点:直接绑定条件函数(如检查新道具)
  • 组合节点:自动聚合子节点状态(任一子节点激活则激活)
-- ========================
-- 红点系统核心管理器
-- ========================
local RedPointManager = {
    -- 所有注册的红点节点,以节点ID为键
    nodes = {},       
    
    -- 没有父节点的根节点集合(例如HUD入口)
    rootNodes = {},   
    
    -- 事件监听器(扩展功能:事件触发自动更新)
    eventListeners = {} 
}

-- ========================
-- 节点评估策略模块
-- ========================
-- 叶子节点评估方法:直接执行条件函数
local function evaluateLeaf(node)
    -- 当且仅当条件函数返回true时激活
    return node.condition and node.condition()
end

-- 组合节点评估方法:检查任意子节点是否激活
local function evaluateComposite(node)
    for _, child in ipairs(node.children) do
        if child.isActive then
            return true -- 任一子节点激活则组合节点激活
        end
    end
    return false
end

二、节点注册与管理

-- ========================
-- 节点注册与管理模块
-- ========================
--- 注册一个红点节点
--- @param nodeId string 唯一节点标识(例如"WeaponTab")
--- @param parentNodeId string|nil 父节点ID(nil表示根节点)
--- @param nodeType "leaf"|"composite" 节点类型
--- @param condition function|nil 仅叶子节点需要的条件函数
function RedPointManager:RegisterNode(nodeId, parentNodeId, nodeType, condition)
    -- 创建节点数据结构
    local node = {
        id = nodeId,             -- 节点唯一标识
        parent = parentNodeId and self.nodes[parentNodeId], -- 父节点引用
        children = {},            -- 子节点列表(仅组合节点使用)
        isActive = false,         -- 当前激活状态
        evaluation = nodeType == "leaf" and evaluateLeaf or evaluateComposite, -- 评估策略
        condition = condition,    -- 条件函数(仅叶子节点)
        uiCallback = nil          -- UI刷新回调
    }
    
    -- 构建父子节点关系
    if node.parent then
        -- 将当前节点添加到父节点的子节点列表
        table.insert(node.parent.children, node)
    else
        -- 没有父节点时加入根节点列表
        table.insert(self.rootNodes, node)
    end
    
    -- 将节点注册到全局管理器
    self.nodes[nodeId] = node
    return node
end

--- 为指定节点注册UI刷新回调
--- @param nodeId string 目标节点ID
--- @param callback function 回调函数(参数:isActive)
function RedPointManager:RegisterUICallback(nodeId, callback)
    local node = self.nodes[nodeId]
    if node then
        node.uiCallback = callback -- 绑定UI更新逻辑
    end
end

三、状态更新与冒泡机制

-- ========================
-- 状态更新模块
-- ========================
--- 标记某个节点需要重新评估状态
--- @param nodeId string 需要更新的节点ID
function RedPointManager:MarkDirty(nodeId)
    local node = self.nodes[nodeId]
    if node then
        self:_EvaluateNode(node) -- 立即触发评估
    end
end

-- 内部方法:递归评估节点状态
function RedPointManager:_EvaluateNode(node)
    -- 执行节点对应的评估策略(叶子节点/组合节点)
    local newActive = node.evaluation(node)
    
    -- 仅当状态变化时触发后续操作
    if newActive ~= node.isActive then
        node.isActive = newActive -- 更新节点状态
        
        -- 执行UI刷新回调(通知前端更新红点显示)
        if node.uiCallback then
            node.uiCallback(node.isActive)
        end
        
        -- 冒泡机制:状态变化时向上更新父节点
        if node.parent then
            self:_EvaluateNode(node.parent) -- 递归评估父节点
        end
    end
end

四、示例配置与使用

-- ========================
-- 示例用法模块
-- ========================
-- 初始化红点树结构
RedPointManager:RegisterNode("HUDEntry", nil, "composite") -- 根节点
RedPointManager:RegisterNode("PropPanel", "HUDEntry", "composite") -- 二级节点
RedPointManager:RegisterNode("WeaponTab", "PropPanel", "leaf", function()
    -- 叶子节点的具体条件判断:玩家是否有新武器
    return Player.HasNewWeapons()
end)

-- 绑定UI元素的红点显示逻辑
RedPointManager:RegisterUICallback("HUDEntry", function(isActive)
    -- 当HUD入口红点状态变化时,调用HUD系统接口
    HudView.SetRedDot("MainEntry", isActive)
end)

RedPointManager:RegisterUICallback("WeaponTab", function(isActive)
    -- 武器标签红点状态变化时,更新道具界面
    PropView.SetWeaponTabRedDot(isActive)
end)

-- ========================
-- 业务逻辑触发示例
-- ========================
-- 当玩家获得新武器时
function Player.OnNewWeaponAdded()
    -- 直接标记叶子节点需要重新评估
    RedPointManager:MarkDirty("WeaponTab")
end

-- ========================
-- 扩展功能:事件驱动更新
-- ========================
--- 注册事件监听(例如:道具变化、邮件到达等)
--- @param eventType string 事件类型
--- @param nodeId string 关联的节点ID
function RedPointManager:AddEventListener(eventType, nodeId)
    self.eventListeners[eventType] = self.eventListeners[eventType] or {}
    table.insert(self.eventListeners[eventType], nodeId)
end

-- 全局游戏事件处理器
function OnGameEvent(eventType, ...)
    local nodes = RedPointManager.eventListeners[eventType]
    if nodes then
        for _, nodeId in ipairs(nodes) do
            RedPointManager:MarkDirty(nodeId) -- 自动触发相关节点更新
        end
    end
end

-- 示例:将武器标签与新武器事件绑定
RedPointManager:AddEventListener("NEW_WEAPON", "WeaponTab")

五、结构示意图

HUDEntry(组合节点)
├─ PropPanel(组合节点)
│  ├─ WeaponTab(叶子节点,条件=有新武器)
│  └─ ArmorTab(叶子节点,条件=有新防具)
└─ MailPanel(组合节点)
   └─ SystemMail(叶子节点,条件=未读邮件)

该设计通过分层抽象,实现以下优势:

  1. 低耦合:界面层仅关注回调,逻辑层管理状态
  2. 易扩展:新增红点只需注册节点+条件
  3. 高效更新:事件驱动+冒泡机制减少无效计算
  4. 灵活策略:支持自定义条件与聚合逻辑

六、关键机制说明

  1. 树形结构管理
    • 根节点(如HUD入口)→组合节点(如道具面板)→叶子节点(如武器标签)
    • 父节点状态自动由子节点决定,无需手动维护
  2. 状态更新流程
graph TD
A[玩家获得新武器] --> B(标记WeaponTab为脏)
B --> C{评估WeaponTab状态}
C -->|状态变化| D[更新武器标签UI]
D --> E{存在父节点?}
E -->|是| F[递归评估PropPanel]
F --> G{状态变化?}
G -->|是| H[更新道具面板父节点]
H --> I[继续冒泡到HUDEntry]

总结

该设计通过清晰的层级划分和事件驱动机制,使红点系统具备:
✅ 新增功能只需添加节点+条件
✅ 状态变更自动传播
✅ 界面与逻辑完全解耦
✅ 支持复杂嵌套规则(如:主界面红点=任务红点 OR 邮件红点)