Unity で単純な赤いドット ツリー関数を実装するためのファイルを残しておきます。(C# バージョンと xLua バージョンの 2 つの主要なセクション)

---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_l3_1 と node_l3_2 を計算するように
    コールバック
        node_l2_1 に通知し、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です。
    最も外側の表示には複数のモジュール要件がある可能性があるため、たとえば、プロパティの変更に対応する赤い点は、アクティビティ、タスク、およびバックパックの複数のルート ノードです。

ダイヤモンド構造はサポートされていません。たとえば、図では、node_l3_2 も、node_l2_2 を指します。
    この要件は妥当かもしれませんが、実際のアプリケーション シナリオでは
    ダイヤモンド構造はほとんどありません。再帰では人為的な理由により無限ループが発生する可能性があり、バグのトラブルシューティングが困難になったり、ロジックの複雑さが増大したりします。ノードを追加して
    、ダイヤモンド構造を解決するには、同時にカウントを増やします。

リレーションシップ ツリーの構築は静的で固定的である必要があります。
    これは設計レベルの問題であり、関係ツリーを動的に変更する動作は発生すべきではありません。
    リンクされていない個別のノードを動的に作成することができます。パブリック インターフェイス (RemoveListener を除く) はすべて Create または 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;
        }
    }

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

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


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

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

        /// <summary>
        /// 检查目标节点是不是已经存在于树中
        /// </summary>
        /// <param name="node">目标节点</param>
        /// <param name="dir">true=parent false=child</param>
        /// <returns>true=存在</returns>
        public bool ContainsInTree(RedDotNode node, bool dir)
        {
            int nCount = dir ? ParentCount : ChildCount;
            if (nCount > 0)
            {
                List<RedDotNode> 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;
        }

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

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

        #endregion

        #region 计数相关

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

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

        /// <summary>
        /// 累加子节点计数
        /// </summary>
        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
    }

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

    /// <summary>
    /// 测试用,输出所有节点计数
    /// </summary>
    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
    }

    /// <summary>
    /// 创建或者获取一个节点
    /// </summary>
    /// <param name="strKey">唯一ID</param>
    /// <returns>查询到 或者 新建的对象</returns>
    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 事件注册
    /// <summary>
    /// 添加加入一个注册
    /// </summary>
    /// <param name="strKey">唯一ID</param>
    /// <param name="callBack">回调</param>
    public void AddListener(string strKey, Action<string, int> callBack)
    {
        RedDotNode node = CreateOrGetNode(strKey);
        // 这里使用了 delegate的特性+=, 允许添加相同callback,可能某些View层需要调用次数来处理逻辑。
        node.m_callBack += callBack;
    }

    /// <summary>
    /// 移除一个注册
    /// </summary>
    /// <param name="strKey">唯一ID</param>
    /// <param name="callBack">回调</param>
    public void RemoveListener(string strKey, Action<string, int> 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 父子节点关系处理
    /// <summary>
    /// 建立父子关系
    /// </summary>
    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 计数相关操作
    /// <summary>
    /// 获取计数
    /// </summary>
    /// <param name="strKey">唯一ID</param>
    /// <returns>计数</returns>
    public int GetRef(string strKey)
    {
        var node = CreateOrGetNode(strKey); // 函数内部 处理返回,一定不会为空
        return node.Ref;
    }

    /// <summary>
    /// 修改计数
    /// </summary>
    /// <param name="strKey">唯一ID</param>
    /// <param name="isReset">是否是重置</param>
    /// <param name="nValue">增量 or 数值</param>
    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;
        }
    }

    /// <summary>
    /// 检查自己是不是叶子节点
    /// </summary>
    /// <param name="node">RedDotNode</param>
    /// <returns>是否</returns>
    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<function>遍历对比

特定のコード: (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<RedDotKey, number>[]  计数变更的回调
---@field parent        RedDotNode[]   父节点 可能会有多个
---@field child         RedDotNode[]   子节点 可能会有多个

---@class RedDotManager : Singleton
---@field map   table<RedDotKey, RedDotNode> 键值对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<RedDotKey, number>        回调
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


プログラミングには終わりがありません。
誰でも気軽にコミュニケーションできます。不明な点や間違いがある場合は、個人的にチャットすることもできます。My
QQ 334524067 God-like Didi

おすすめ

転載: blog.csdn.net/qq_37776196/article/details/129278376
おすすめ