留个档,Unity 一个简易的红点树功能实现。(两大段,C#版本 和 xLua版本)

---root:                node_r1         node_r2
---                   ↗         ↖     ↗         ↖
---level1:     node_l1_1       node_l1_2       node_l1_3
---                           ↗         ↖
---level2:              node_l2_1       node_l2_2
---                   ↗           ↖
---level3:     node_l3_1         node_l3_2

图例命名规则:node_l(第几层)_序号(1开始)

只有叶子节点可以被设置 具体数值,向上的树全部进行计算

图中 node_l3_1, node_l3_2, node_l2_2, node_l1_1, node_l1_3 可以被设置数值

某个节点被设置数值时序:(例如 node_l3_1 被设置了数值)
    node_l3_1 通知回调
        node_l2_1 计算 node_l3_1 和 node_l3_2 累加
        node_l2_1 通知回调
                node_l1_2 计算 node_l2_1 和 node_l2_2 累加
                node_l1_2 通知回调
                                node_r1 计算 node_l1_1 和 node_l1_2 累加
                                node_r1 通知回调
                                        node_r2 计算 node_l1_3 和 node_l1_2 累加
                                        node_r2 通知回调

支持x型树。例如中心节点 node_l1_2。
    因为最外层展示可能会有多个模块需求,比如道具修改对应红点是在活动,任务,背包 多个Root节点

不支持菱形结构。例如在图中 node_l3_2 同时指向 node_l2_2。
    这个需求可能是合理的,但是实际应用场景非常少
    菱形结构在递归中,可能会由于人为原因导致死循环,排查bug困难 或者 导致逻辑复杂度高
    可以新增一个节点,同时增加计数来解决菱形结构的问题

构建关系树,应该是静态的,固定的。
    这是设计层面的事情,不应该出现动态修改关系树的行为。
    动态创建单独的无Link的节点,是被允许的。public接口(除RemoveListener)都是Create or Load


C#部分

/*
root:                node_r1         node_r2
                   ↗         ↖     ↗         ↖
level1:     node_l1_1       node_l1_2       node_l1_3
                           ↗         ↖
level2:              node_l2_1       node_l2_2
                   ↗           ↖
level3:     node_l3_1         node_l3_2

图例命名规则:node_l(第几层)_序号(1开始)

只有叶子节点可以被设置 具体数值,向上的树全部进行计算
图中 node_l3_1, node_l3_2, node_l2_2, node_l1_1, node_l1_3 可以被设置数值

某个节点被设置数值时序:(例如 node_l3_1 被设置了数值)
 node_l3_1 通知回调
     node_l2_1 计算 node_l3_1 和 node_l3_2 累加
     node_l2_1 通知回调
         node_l1_2 计算 node_l2_1 和 node_l2_2 累加
         node_l1_2 通知回调
             node_r1 计算 node_l1_1 和 node_l1_2 累加
             node_r1 通知回调
             node_r2 计算 node_l1_3 和 node_l1_2 累加
             node_r2 通知回调

支持x型树。例如中心节点 node_l1_2。
     因为最外层展示可能会有多个模块需求,比如道具修改对应红点 是在活动,任务,背包 多个Root节点
不支持菱形结构。例如在图中 node_l3_2 同时指向 node_l2_2。
     这个需求可能是合理的,但是实际应用场景非常少
     菱形结构在递归中,可能会由于人为原因导致死循环,排查bug困难 或者 导致逻辑复杂度高
     可以新增一个节点,同时增加计数来解决菱形结构的问题
构建关系树,应该是静态的,固定的。
     这是设计层面的事情,不应该出现动态修改关系树的行为。
     动态创建单独的无Link的节点,是被允许的。public接口(除RemoveListener)都是Create or Load
监听变化: AddListener 和 RemoveListener 需要按照生命周期 成对出现,有Add必有Remove
*/

using System.Collections.Generic;
using System;
using UnityEngine;

public class RedDotManager
{
    private static RedDotManager m_this = null;
    public static RedDotManager Instance
    {
        get
        {
            if (m_this == null)
            {
                m_this = new RedDotManager();
            }
            return m_this;
        }
    }

    /// 
    /// 单个节点的集合
    /// 
    private class RedDotNode
    {
        /// 
        /// 构造
        /// 
        /// 必须 唯一ID
        public RedDotNode(string strKey)
        {
            m_strKey = strKey;
        }

        /// 
        /// 唯一ID
        /// 
        public string m_strKey = null;
        /// 
        /// 计数变更的回调
        /// 
        public Action m_callBack = null;
        /// 
        /// 计数,每一层都需要记录,可能是相邻节点变化,本节点不需要重新计算自己的子节点
        /// 
        private int m_nRef = 0;
        /// 
        /// 父节点 可能会有多个
        /// 
        private List m_listParent = null; // 初始化为null,没必要初始化new浪费内存
        /// 
        /// 子节点 可能会有多个
        /// 
        private List m_listChild = null; // 初始化为null,没必要初始化new浪费内存


        #region 父子节点相关
        /// 
        /// 父节点数量
        /// 
        private int ParentCount
        {
            get
            {
                if (m_listParent == null)
                {
                    return 0;
                }
                return m_listParent.Count;
            }
        }

        /// 
        /// 子节点数量
        /// 
        public int ChildCount
        {
            get
            {
                if (m_listChild == null)
                {
                    return 0;
                }
                return m_listChild.Count;
            }
        }

        /// 
        /// 检查目标节点是不是已经存在于树中
        /// 
        /// 目标节点
        /// true=parent false=child
        /// true=存在
        public bool ContainsInTree(RedDotNode node, bool dir)
        {
            int nCount = dir ? ParentCount : ChildCount;
            if (nCount > 0)
            {
                List list = dir ? m_listParent : m_listChild;
                if (list.Contains(node))
                {
                    return true;
                }
                else
                {
                    for (int i = 0; i < nCount; i++)
                    {
                        if (list[i].ContainsInTree(node, dir))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        /// 
        /// 加入一个父节点
        /// 
        /// 单个节点
        public void AddParent(RedDotNode node)
        {
            if (m_listParent == null)
            {
                m_listParent = new List();
            }
            m_listParent.Add(node);
        }

        /// 
        /// 加入一个子节点
        /// 
        /// 单个节点
        public void AddChild(RedDotNode node)
        {
            if (m_listChild == null)
            {
                m_listChild = new List();
            }
            m_listChild.Add(node);
        }

        #endregion

        #region 计数相关

        /// 
        /// 计数
        /// 
        public int Ref
        {
            get
            {
                return m_nRef;
            }
            set
            {
                if (m_nRef != value)
                {
                    m_nRef = value;
                    m_callBack?.Invoke(m_strKey, m_nRef);
                    RefreshParentRef();
                }
            }
        }

        /// 
        /// 递归刷新所有父节点
        /// 
        private void RefreshParentRef()
        {
            int nCount = ParentCount;
            for (int i = 0; i < nCount; i++)
            {
                m_listParent[i].CollectChildRef();
            }
        }

        /// 
        /// 累加子节点计数
        /// 
        private void CollectChildRef()
        {
            int nResult = 0;
            int nCount = ChildCount;
            for (int i = 0; i < nCount; i++)
            {
                var one = m_listChild[i];
                nResult += one.m_nRef;
            }
            Ref = nResult;  /// 这里触发递归
        }
        #endregion
    }

    /// 
    /// 所有节点的集合, key=string唯一
    /// 
    private Dictionary m_dicNodes = new Dictionary();

    /// 
    /// 测试用,输出所有节点计数
    /// 
    public void LogRef()
    {
#if UNITY_EDITOR
        System.Text.StringBuilder strBuilder = new System.Text.StringBuilder();
        foreach (var item in m_dicNodes)
        {
            strBuilder.Append(item.Value.m_strKey + " : " + item.Value.Ref + " ;   ");
        }
        Debug.Log(strBuilder.ToString());
#endif
    }

    /// 
    /// 创建或者获取一个节点
    /// 
    /// 唯一ID
    /// 查询到 或者 新建的对象
    private RedDotNode CreateOrGetNode(string strKey)
    {
        RedDotNode node;
        if (!m_dicNodes.TryGetValue(strKey, out node))
        {
            node = new RedDotNode(strKey);
            m_dicNodes.Add(strKey, node);
        }
        return node;
    }

    #region 事件注册
    /// 
    /// 添加加入一个注册
    /// 
    /// 唯一ID
    /// 回调
    public void AddListener(string strKey, Action callBack)
    {
        RedDotNode node = CreateOrGetNode(strKey);
        // 这里使用了 delegate的特性+=, 允许添加相同callback,可能某些View层需要调用次数来处理逻辑。
        node.m_callBack += callBack;
    }

    /// 
    /// 移除一个注册
    /// 
    /// 唯一ID
    /// 回调
    public void RemoveListener(string strKey, Action callBack)
    {
        RedDotNode node;
        if (!m_dicNodes.TryGetValue(strKey, out node))
        {
            Debug.LogWarning("RedDotManager RemoveListener, can not find key : " + strKey);
            return;
        }
        node.m_callBack -= callBack;
    }
    #endregion

    #region 父子节点关系处理
    /// 
    /// 建立父子关系
    /// 
    public void Link(string strKeyChild, string strKeyParent)
    {
        if (strKeyChild == strKeyParent)
        {
            Debug.LogError("RedDotManager Link, can not link self : " + strKeyChild);
            return;
        }
        RedDotNode nodeChild = CreateOrGetNode(strKeyChild);
        RedDotNode nodeParent = CreateOrGetNode(strKeyParent);

        // 整个结构都是多叉树的链结构。所以检查单边,足够作为判断依据
        if (nodeChild.ContainsInTree(nodeParent, true))
        {
            Debug.Log("RedDotManager Link, already exit in parent: " + strKeyChild + " -> " + strKeyParent);
            return;
        }
        if (nodeChild.ContainsInTree(nodeParent, false))
        {
            Debug.Log("RedDotManager Link, already exit in child: " + strKeyChild + " -> " + strKeyParent);
            return;
        }

        //之前没有连接关系,可以顺利连接
        nodeChild.AddParent(nodeParent);
        nodeParent.AddChild(nodeChild);
    }

    #endregion

    #region 计数相关操作
    /// 
    /// 获取计数
    /// 
    /// 唯一ID
    /// 计数
    public int GetRef(string strKey)
    {
        var node = CreateOrGetNode(strKey); // 函数内部 处理返回,一定不会为空
        return node.Ref;
    }

    /// 
    /// 修改计数
    /// 
    /// 唯一ID
    /// 是否是重置
    /// 增量 or 数值
    public void SetRef(string strKey, bool isReset, int nValue)
    {
        RedDotNode node = CreateOrGetNode(strKey);
        if (!CheckIsLeafNode(node))
        {
            Debug.LogWarning("RedDotManager ChangeNodeRef, not Leaf node : " + strKey);
            return;
        }
        if (isReset)
        {
            node.Ref = nValue;
        }
        else
        {
            node.Ref += nValue;
        }
    }

    /// 
    /// 检查自己是不是叶子节点
    /// 
    /// RedDotNode
    /// 是否
    private bool CheckIsLeafNode(RedDotNode node)
    {
        //单个 无父 无子 节点,允许主动修改
        //无子节点 被认为是 叶子节点, 可以主动修改        
        return node.ChildCount == 0;
    }

    #endregion
}

测试代码:


    void Start()
    {
        string[] arrRoot = new string[] { "r1", "r2" };
        string[] arrLevel1 = new string[] { "11", "12", "13" };
        string[] arrLevel2 = new string[] { "21", "22" };
        string[] arrLevel3 = new string[] { "31", "32" };

        var RDMgr = RedDotManager.Instance;
        //Level1 -> Root
        RDMgr.Link(arrLevel1[0], arrRoot[0]);
        RDMgr.Link(arrLevel1[1], arrRoot[0]);

        RDMgr.Link(arrLevel1[1], arrRoot[1]);
        RDMgr.Link(arrLevel1[2], arrRoot[1]);

        //Level2 -> Level1
        RDMgr.Link(arrLevel2[0], arrLevel1[1]);
        RDMgr.Link(arrLevel2[1], arrLevel1[1]);

        //Level3 -> Level2
        RDMgr.Link(arrLevel3[0], arrLevel2[0]);
        RDMgr.Link(arrLevel3[1], arrLevel2[0]);

        Debug.Log("---1---");
        RDMgr.AddListener(arrRoot[0], OnRedDot);
        RDMgr.SetRef(arrLevel3[0], false, 1);
        RDMgr.LogRef();
        RDMgr.SetRef(arrLevel3[0], true, 5);
        RDMgr.LogRef();
        Debug.Log("---2---");
        RDMgr.SetRef(arrLevel3[1], false, 1);
        RDMgr.LogRef();
        RDMgr.SetRef(arrLevel3[0], false, -1);
        RDMgr.LogRef();
        Debug.Log("---3---");
        RDMgr.SetRef(arrLevel2[1], true, 2);
        RDMgr.LogRef();
        Debug.Log("---4---");
        RDMgr.RemoveListener(arrRoot[0], OnRedDot);
        RDMgr.SetRef(arrLevel3[0], false, -1);
        RDMgr.LogRef();

        Debug.Log("---5---");
        //测试异常情况, link父子节点时候,目标节点已经包含在树中
        RDMgr.Link(arrLevel2[0], arrRoot[1]);
        RDMgr.Link(arrLevel2[0], arrLevel2[0]);
    }

    private void OnRedDot(string strKey, int nRef)
    {
        Debug.Log("OnRedDot " + strKey + "  " + nRef);
    }

LUA部分

监听变化: AddListener 和 RemoveListener 需要按照生命周期 成对出现,有Add必有Remove

    local pCallBack = Bind(self, self.FunctionCallBack)  // 注:Lua里需要Bind
    function xxx:FunctionCallBack()
        ...
    end
    self:AddListener(RedDotKey.Activity, pCallBack)
    self:RemoveListener(RedDotKey.Activity, pCallBack)   // 注:lua内部实现C# delegate使用的是table遍历对比

具体代码:(Lua + EmmyLua)

---@class RedDotKey    每100个跨度 为一个功能模块
local RedDotKey =
{
    Activity = 100,
    Activity_SevenDay = 101,
}

return ConstClass("RedDotKey", RedDotKey)

---@class RedDotNode
---@field eKey          RedDotKey      唯一ID 键
---@field nRef          number      计数,每一层都需要记录,可能是相邻节点变化,本节点不需要重新计算自己的子节点
---@field callBack      function[]  计数变更的回调
---@field parent        RedDotNode[]   父节点 可能会有多个
---@field child         RedDotNode[]   子节点 可能会有多个

---@class RedDotManager : Singleton
---@field map   table 键值对hash
local RedDotManager = BaseClass("RedDotManager", Singleton)

function RedDotManager:__init()
    self.map = {}

    self:Link(RedDotKey.Activity, RedDotKey.Activity_SevenDay)
end

---单个节点--------------------------------------------------------------------------------------------------------------

---添加加入一个注册
---@public
---@param eKey      RedDotKey   唯一ID
---@param callBack  function        回调
function RedDotManager:AddListener(eKey, callBack)
    ---@type RedDotNode
    local node = self:CreateOrGetNode(eKey)
    local result = table.indexof(node.callBack, callBack)
    if result == false then
        ---没有找到 相同的注册, 正常加入 callback list
        table.insert(node.callBack, callBack)
    else
        ---找到了相同的callback
        Debug.Log("!!! RedDotManager:AddNode, add same callback : "..tostring(eKey))
    end
end

---移除一个注册
---@public
---@param eKey      RedDotKey   唯一ID
---@param callBack  function        回调
function RedDotManager:RemoveListener(eKey, callBack)
    ---@type RedDotNode
    local node = self.map[eKey]
    if node == nil then
        return
    end
    local result = table.indexof(node.callBack, callBack)
    if result ~= false then
        table.remove(node.callBack, result)
    else
        ---没有找到这个callback
        Debug.Log("!!! RedDotManager:Remove, add same callback : "..tostring(eKey))
    end
end

---创建或者获取一个节点
---@private
---@param eKey RedDotKey
function RedDotManager:CreateOrGetNode(eKey)
    ---@type RedDotNode
    local node = self.map[eKey]
    if node == nil then
        self.map[eKey] = {}
        node = self.map[eKey]
        ---初始化
        node.eKey = eKey
        node.nRef = 0
        node.callBack = {}
        node.parent = {}
        node.child = {}
    end
    return node
end

---父子节点关系--------------------------------------------------------------------------------------------------------------

---建立父子关系
---@public
---@param eKeyChild     RedDotKey 唯一ID
---@param eKeyParent    RedDotKey 唯一ID
function RedDotManager:Link(eKeyChild, eKeyParent)
    ---@type RedDotNode
    local nodeChild = self:CreateOrGetNode(eKeyChild)
    ---@type RedDotNode
    local nodeParent = self:CreateOrGetNode(eKeyParent)

    if self:CheckRepeat(nodeChild, nodeParent.child, true) then
        Debug.Log("!!! RedDotManager:LinkParent already exit in c "..tostring(eKeyChild).."  "..tostring(eKeyChild))
        return
    end
    if self:CheckRepeat(nodeChild, nodeParent.parent, false) then
        Debug.Log("!!! RedDotManager:LinkParent already exit in p "..tostring(eKeyChild).."  "..tostring(eKeyChild))
        return
    end
    ---之前没有连接关系,可以顺利连接
    table.insert(nodeChild.parent, nodeParent)
    table.insert(nodeParent.child, nodeChild)
end

---检查自己是不是在树里,会造成无限循环的情况
---@private
---@param node      RedDotNode 被检查的对象
---@param nodeLink  RedDotNode 当前树的某一个节点
---@param isCorP    boolean 递归向Child or parent, true=child
function RedDotManager:CheckRepeat(node, nodeLink, isCorP)
    local nCount = #nodeLink
    if nCount == 0 then
        return false
    end
    if table.indexof(nodeLink, node) ~= false then
        return true
    end
    for i = 1, nCount do
        ---@type RedDotNode
        local one = nodeLink[i]
        if self:CheckRepeat(node, isCorP and one.child or one.parent) then
            return true
        end
    end
    return false
end

---计数和通知--------------------------------------------------------------------------------------------------------------

---修改计数
---@public
---@param eKey      RedDotKey   唯一ID
---@param isReset   boolean     是否是重置
---@param nValue    number      增量 or 数值
function RedDotManager:ChangeNodeRef(eKey, isReset, nValue)
    ---@type RedDotNode
    local node = self:CreateOrGetNode(eKey)
    if not self:CheckIsLeafNode(node) then
        Debug.Log("!!! RedDotManager:ChangeNodeRef not Leaf node : "..tostring(eKey))
        return
    end
    ---缓存上一次的计数
    local nTemp = node.nRef
    if isReset then
        node.nRef = nValue
    else
        node.nRef = node.nRef + nValue
    end
    ---如果修改后计数不同,
    if nTemp ~= node.nRef then
        ---调用回调 通知修改
        self:DoCallBack(node)
        ---刷新上层
        self:RefreshParentRef(node)
    end
end

---检查自己是不是叶子节点
---@private
---@param node RedDotNode
---@return boolean 是否
function RedDotManager:CheckIsLeafNode(node)
    ---单个 无父 无子 节点,允许主动修改
    ---无子节点 被认为是 叶子节点, 可以主动修改
    return #node.child == 0
end

---刷新父节点的计数
---@private
---@param node RedDotNode
function RedDotManager:RefreshParentRef(node)
    local nCount = #node.parent
    if nCount == 0 then
        return
    end
    for i = 1,nCount do
        ---@type RedDotNode
        local one = node.parent[i]
        one.nRef = self:CollectChildRef(one)
        self:DoCallBack(one)
        ---递归向上
        self:RefreshParentRef(one)
    end
end

---累加子节点计数
---@private
---@param node RedDotNode
---@return number 子节点的计数all
function RedDotManager:CollectChildRef(node)
    local nResult = 0
    local nCount = #node.child
    for i = 1, nCount do
        ---@type RedDotNode
        local one = node.child[i]
        nResult = nResult + one.nRef
    end
    return nResult
end

---回调通知
---@private
---@param node RedDotNode
function RedDotManager:DoCallBack(node)
    ---调用回调 通知修改
    local nCount = #node.callBack
    for i = 1, nCount do
        node.callBack[i](node.eKey, node.nRef)
    end
end


程序学无止尽。
欢迎大家沟通,有啥不明确的,或者不对的,也可以和我私聊
我的QQ 334524067 神一般的狄狄

你可能感兴趣的:(Unity技术,C#技术,c#,开发语言)