Hinterlassen Sie eine Datei, um eine einfache Red-Dot-Tree-Funktion in Unity zu implementieren. (Zwei Hauptabschnitte, C#-Version und xLua-Version)

---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

Benennungsregeln für Legenden: node_l (layer)_serial number (beginnend mit 1)

Nur Blattknoten können auf bestimmte Werte eingestellt werden und alle nach oben gerichteten Bäume werden berechnet.

In der Abbildung können node_l3_1, node_l3_2, node_l2_2, node_l1_1, node_l1_3 auf Werte gesetzt werden.

Ein Knoten wird auf eine numerische Sequenz eingestellt: (Beispiel: Knoten_l3_1 wird auf einen numerischen Wert gesetzt) ​​Knoten_l3_1
    benachrichtigt den Rückruf
        Knoten_l2_1, um Knoten_l3_1 und Knoten_l3_2 zu berechnen, akkumuliert Knoten_l2_1, benachrichtigt
        den Rückruf Knoten_l1_2, berechnet
                Knoten_l2_1 und Knoten_l2_2, akkumuliert                                 Knoten_l1_2, benachrichtigt den Rückruf Knoten_r1, berechnet Knoten_l1_1 und Knoten_l1_2, akkumuliert Knoten_r1-
                Benachrichtigungsrückruf                                 ,                                         Knoten_r2 berechnet Knoten_l1_3 und Knoten_l1_2, akkumuliert                                         Knoten_r2-Benachrichtigungsrückruf



Unterstützt x-förmige Bäume. Zum Beispiel der zentrale Knoten node_l1_2.
    Da die äußerste Anzeige beispielsweise mehrere Modulanforderungen haben kann, handelt es sich bei dem roten Punkt, der der Requisitenänderung entspricht, um mehrere Stammknoten in Aktivitäten, Aufgaben und Rucksäcken.

Rautenstrukturen werden nicht unterstützt. In der Abbildung zeigt beispielsweise node_l3_2 auch auf node_l2_2.
    Diese Anforderung mag sinnvoll sein, es gibt jedoch nur sehr wenige tatsächliche Anwendungsszenarien
    . Die Rautenstruktur in der Rekursion kann aus menschlichen Gründen zu einer Endlosschleife führen, was die Fehlerbehebung erschwert oder eine hohe Logikkomplexität verursacht. Sie können einen Knoten hinzufügen
    und erhöhen zählen, um die Rautenstruktur zu lösen. Das Problem

Der Aufbau eines Beziehungsbaums sollte statisch und fest sein.
    Dies ist eine Frage der Entwurfsebene, und das Verhalten einer dynamischen Änderung des Beziehungsbaums sollte nicht auftreten.
    Das dynamische Erstellen separater, nicht verknüpfter Knoten ist zulässig. Die öffentlichen Schnittstellen (außer RemoveListener) sind alle Create oder Load


C#-Teil

/*
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
}

Testcode:


    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-Teil

Überwachen Sie Änderungen: AddListener und RemoveListener müssen entsprechend dem Lebenszyklus paarweise angezeigt werden. Wenn Add vorhanden ist, muss Remove vorhanden sein.

    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>遍历对比

Spezifischer Code: (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


Es gibt kein Ende, etwas über das Programmieren zu lernen.
Jeder ist willkommen, sich mitzuteilen. Wenn etwas unklar oder falsch ist, können Sie auch privat mit mir chatten.
Mein QQ 334524067 Gottähnlicher Didi

Ich denke du magst

Origin blog.csdn.net/qq_37776196/article/details/129278376
Empfohlen
Rangfolge