【Unity】ゲーム開発におけるツリー構造 - レッドドットシステム

ゲーム開発でツリー構造を使用する最も一般的な機能は、レッド ドット システムとビヘイビア ツリーです。

今日はまずレッドドットシステムの開発について書きます。

1. 需要分析

赤い点の機能は、プレーヤーに通知することです。たとえば、プレーヤーに未読の電子メールがある場合、メイン インターフェイスのメール機能に赤い点が表示されます。赤い点を見た後、プレーヤーはメールのエントリをクリックします。メール機能を起動し、メール機能のメインインターフェースに入ると、「メール」タブページが赤色で表示されます。クリックし、メールラベルをクリックしてメールリストに入り、多数のメールの中から赤い点が付いた未読メールを見つけます。

通知プロセス全体は、メイン インターフェイスのメール エントリ → メール インターフェイスのメール タブ → メール リストの未読メールです。直感的には、外側から内側へ層ごとに促します。

しかし、レッドドット機能を実装する場合、メインインターフェース、メールインターフェース、メーリングリストインターフェースにそれぞれレッドドットプロンプトコードを記述する必要があるのでしょうか?

言うまでもなく、私は誰かがこれをやっているのを見たことがあります。関数の赤い点プロンプトはどこにでも書く必要があります。彼が作成した関数、特にメインインターフェイスにどれだけ多くのコードが含まれているか想像できます。コードはもう読めません。 . インターフェイス呼び出し 10 や 20 を超えるモジュールのメソッドは、赤い点が表示されるかどうかを判断します。

つまり、エレガントなコードの誕生は怠惰から始まります。赤点プロンプトの他の場所に表示されるべきではない XXX.IsShowRedPoint() の文を追加したくありません。更新ロジックに文を追加するだけで済むことを望みます。メーリング リストの RedPointMgr.ShowPoint( RedPointType.Mail, true) を入力して、上記のプロンプト プロセス全体を完了します。

2. 機能実現

SetState が呼び出されると、現在のノードからレイヤーごとに前方にトラバースします。つまり、レッド ドット関数をトリガーするノードのすべての親ノードに対して、レッド ドットのプロンプトが 1 回表示されます。 。

PS: ルート ノードから子ノードまでトラバースしてみませんか? 明らかに、各ノードには親ノードが 1 つだけあり、多くの子ノードがあります。現在のノードがどのノードの下にあるかを知ることは不可能です。現在のノードを見つけるために 1 回トラバースするだけで、その後、現在のノードから逆方向にトラバースできます。

ここで非常に明らかなのは、まず第一に、ツリー構造である red dot クラスが存在する必要があるということです。

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

public enum RedPointType
{
    None,
    Enternal,//一直存在
    Once,//点击一次就消失
}

public enum RedPointState
{
    None,
    Show,
    Hide,
}

public class RedPoint
{
    /// <summary>
    /// 主关键字(属于哪一个根节点)
    /// </summary>
    public string key
    {
        get
        {
            return m_Key;
        }
    }

    /// <summary>
    /// 自己的关键字
    /// </summary>
    public string subKey
    {
        get
        {
            return m_SubKey;
        }
    }

    /// <summary>
    /// 是否是根节点
    /// </summary>
    public bool isRoot
    {
        get
        {
            return m_IsRoot;
        }
    }

    /// <summary>
    /// 红点类型
    /// </summary>
    public RedPointType type
    {
        get
        {
            return m_Type;
        }
    }

    /// <summary>
    /// 当前状态
    /// </summary>
    public RedPointState state
    {
        get
        {
            return m_State;
        }
    }

    /// <summary>
    /// 数据
    /// </summary>
    public int data
    {
        get
        {
            return m_Data;
        }
    }

    /// <summary>
    /// 父节点
    /// </summary>
    public RedPoint parent
    {
        get
        {
            return m_Parent;
        }
    }

    /// <summary>
    /// 子节点
    /// </summary>
    public List<RedPoint> children
    {
        get
        {
            return m_Children;
        }
    }


    public RedPoint(string key, string subKey, bool isRoot, RedPointType type)
    {
        m_Key = key;
        m_SubKey = subKey;
        m_IsRoot = isRoot;
        m_Type = type;
        m_State = RedPointState.Hide;
        m_Data = 0;
        m_Children = new List<RedPoint>();
    }

    public void Init(Action<RedPointState, int> showEvent, Button btn)
    {
        m_ShowEvent = showEvent;

        if (btn != null)
        {
            m_Btn = btn;
            m_Btn.onClick.AddListener(OnClick);
        }

        m_ShowEvent?.Invoke(m_State, m_Data);
    }

    public void AddChild(RedPoint node, string parentKey)
    {
        if (m_SubKey.Equals(parentKey))
        {
            node.SetParent(this);
            m_Children.Add(node);
            return;
        }

        for (int i = 0; i < m_Children.Count; i++)
        {
            m_Children[i].AddChild(node, parentKey);
        }
    }

    public RedPoint GetChild(string subKey)
    {
        if (m_SubKey.Equals(subKey))
        {
            return this;
        }

        if (m_Children == null)
        {
            return null;
        }

        for (int i = 0; i < m_Children.Count; i++)
        {
            RedPoint node = m_Children[i].GetChild(subKey);

            if (node != null)
            {
                return node;
            }
        }

        return null;
    }

    public void RemoveChild(string subKey)
    {
        if (m_SubKey.Equals(subKey))
        {
            m_Parent.children.Remove(this);
            Dispose();
            return;
        }

        if (m_Children == null)
        {
            return;
        }

        for (int i = 0; i < m_Children.Count; i++)
        {
            m_Children[i].RemoveChild(subKey);
        }
    }

    public void SetParent(RedPoint parent)
    {
        m_Parent = parent;
    }

    public void SetState(string subKey, RedPointState state, int data)
    {
        RedPoint node = GetChild(subKey);

        if (node == null)
        {
            return;
        }

        node.SetTreeState(subKey, state, data);

        m_Data = 0;

        for (int i = 0; i < m_Children.Count; i++)
        {
            m_Data += m_Children[i].m_Data;
        }

        m_ShowEvent?.Invoke(m_State, m_Data);
    }

    private void SetTreeState(string subKey, RedPointState state, int data)
    {
        m_State = state;

        if (m_SubKey.Equals(subKey))
        {
            m_Data = data;
        }
        else
        {
            m_Data = 0;

            for (int i = 0; i < m_Children.Count; i++)
            {
                if (m_Children[i].state == RedPointState.Show)
                {
                    m_State = RedPointState.Show;
                    m_Data += m_Children[i].data;
                }
            }
        }

        if (m_Parent != null)
        {
            m_Parent.SetTreeState(subKey, state, data);
        }

        m_ShowEvent?.Invoke(m_State, m_Data);
    }

    private void OnClick()
    {
        if (m_Type == RedPointType.Once)
        {
            HideChildren();
            SetState(m_SubKey, RedPointState.Hide, m_Data);
        }
    }

    private void HideChildren()
    {
        m_State = RedPointState.Hide;

        for (int i = 0; i < m_Children.Count; i++)
        {
            m_Children[i].HideChildren();
        }

        m_ShowEvent?.Invoke(m_State, m_Data);
    }

    public void Dispose()
    {
        for (int i = 0; i < m_Children.Count; i++)
        {
            m_Children[i].Dispose();
        }

        m_Children.Clear();
        m_Children = null;

        if (m_Btn != null)
        {
            m_Btn.onClick.RemoveListener(OnClick);
        }

        m_Btn = null;
        m_Parent = null;
        m_Key = null;
        m_SubKey = null;
        m_ShowEvent = null;
        m_Type = RedPointType.None;
        m_State = RedPointState.None;
    }

    private string m_Key = string.Empty;
    private string m_SubKey = string.Empty;
    private bool m_IsRoot = false;
    private int m_Data = 0;
    private RedPointType m_Type = RedPointType.None;
    private RedPointState m_State = RedPointState.None;
    private Action<RedPointState, int> m_ShowEvent = null;
    private Button m_Btn;
    private RedPoint m_Parent = null;
    private List<RedPoint> m_Children = null;
}


ここでのキーは属性、つまりどの主要な機能に属しているかであり、subKey はそれ自体のキーワードです。



次に、ゲーム内のすべての赤い点のルート ノードを管理するマネージャーが必要になります。必要に応じて、キーワードを通じて赤い点のルート ノードを見つけて、そのノードの 1 つに子ノードを挿入します。

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


public class RedPointMgr : IDisposable
{
    public static RedPointMgr instance
    {
        get
        {
            if (s_Instance == null)
            {
                s_Instance = new RedPointMgr();
            }

            return s_Instance;
        }
    }
    public RedPointMgr()
    {
        m_ListRedPointTrees = new List<RedPoint>();
    }

    public void Add(string key, string subKey, string parentKey, RedPointType type)
    {
        RedPoint root = GetRoot(key);

        if (string.IsNullOrEmpty(subKey) || key.Equals(subKey))
        {
            if (root != null)
            {
                Debug.LogError("The red point root [" + key + "] is already exist!");
                return;
            }

            root = new RedPoint(key, key, true, type);
            m_ListRedPointTrees.Add(root);
        }
        else
        {
            if (root == null)
            {
                Debug.LogError("The red point root [" + key + "] is invalid,please add it first");
                return;
            }

            RedPoint node = new RedPoint(key, subKey, false, type);
            root.AddChild(node, parentKey);
        }
    }

    public void Remove(string key, string subKey)
    {
        if (string.IsNullOrEmpty(subKey) || key.Equals(subKey))
        {
            for (int i = m_ListRedPointTrees.Count - 1; i >= 0; i--)
            {
                if (m_ListRedPointTrees[i].key.Equals(key))
                {
                    m_ListRedPointTrees[i].Dispose();
                    m_ListRedPointTrees.RemoveAt(i);
                    return;
                }
            }

            return;
        }

        RedPoint root = GetRoot(key);

        if (root == null)
        {
            return;
        }

        root.RemoveChild(subKey);
    }

    public void Init(string key, string subKey, Action<RedPointState, int> showEvent, Button btn = null)
    {
        RedPoint root = GetRoot(key);

        if (root == null)
        {
            Debug.LogError("The red point root [" + key + "] is invalid,please add it first");
            return;
        }

        RedPoint node = root.GetChild(subKey);

        if (node == null)
        {
            Debug.LogError("The red point node [" + subKey + "] is invalid,please add it first");
            return;
        }

        node.Init(showEvent, btn);
    }

    public void SetState(string key, string subKey, RedPointState state, int data = 0)
    {
        RedPoint root = GetRoot(key);

        if (root == null)
        {
            Debug.LogError("The red point root [" + key + "] is invalid,please add it first");
            return;
        }

        root.SetState(subKey, state, data);
    }


    private RedPoint GetRoot(string key)
    {
        if (string.IsNullOrEmpty(key))
        {
            return null;
        }

        for (int i = 0; i < m_ListRedPointTrees.Count; i++)
        {
            if (m_ListRedPointTrees[i].key.Equals(key))
            {
                return m_ListRedPointTrees[i];
            }
        }

        return null;
    }

    public void Dispose()
    {
        for (int i = m_ListRedPointTrees.Count - 1; i >= 0; i--)
        {
            m_ListRedPointTrees[i].Dispose();
        }

        m_ListRedPointTrees.Clear(); ;
    }

    private static RedPointMgr s_Instance = null;
    private List<RedPoint> m_ListRedPointTrees = null;
}

これを使用する場合は、まず Add メソッドを呼び出して、ゲームの初期化時にレッド ドット ツリーを構築するどのレッド ドットを宣言します。次に、Init メソッドを呼び出してレッド ドット表示を追加し、UI インターフェイスの初期化時にコールバックをクリックします。最後に SetState を呼び出します。関数ロジックのメソッド。

3. テスト例

例: 現在、赤点プロンプトが必要な mail1、mail2、mail3、mail4、mail5、および mail6 があります。mail4、mail5、および mail6 は特定のビジネス管理であり、すべて mail3、mial1、mail2、および mail3 の子ノードです。子ノードの Variety をフォローします。

最初にビルドし、次に初期化して、最後に表示コールバックを作成します。(業務は虎のように熾烈で、給料は一目見て2k5)

using UnityEngine;
using UnityEngine.UI;

public class Test : MonoBehaviour
{
    public GameObject mail1RedPoint;
    public GameObject mail2RedPoint;
    public GameObject mail3RedPoint;
    public GameObject mail4RedPoint;
    public GameObject mail5RedPoint;
    public GameObject mail6RedPoint;

    public Text txtMail1;
    public Text txtMail2;
    public Text txtMail3;
    public Text txtMail4;
    public Text txtMail5;
    public Text txtMail6;

    public Button mail4Btn;
    public Button mail5Btn;
    public Button mail6Btn;
    public Button btnSet1;
    public Button btnSet2;
    public Button btnSet3;

    public int count1 = 5;
    public int count2 = 6;
    public int count3 = 7;

    string mail1 = "mail1";
    string mail2 = "mail2";
    string mail3 = "mail3";
    string mail4 = "mail4";
    string mail5 = "mail5";
    string mail6 = "mail6";

    private void Awake()
    {
        RedPointMgr.Init(gameObject);

        //在实际开发中,整个游戏的红点树要在游戏初始化时全部构建出来
        //声明mail1根节点,它的主key是mail1,无subKey,无父节点,红点类型是随着子节点变化
        RedPointMgr.instance.Add(mail1, null, null, RedPointType.Enternal);
        //声明mail2节点,它的主key是mail1,subKey是mail2,父节点是mail1,红点类型是随着子节点变化
        RedPointMgr.instance.Add(mail1, mail2, mail1, RedPointType.Enternal);
        //声明mail3节点,它的主key是mail1,subKey是mail3,父节点是mail2,红点类型是随着子节点变化
        RedPointMgr.instance.Add(mail1, mail3, mail2, RedPointType.Enternal);
        //声明mai4节点,它的主key是mail1,subKey是mail4,父节点是mail3,红点类型是点击即消失
        RedPointMgr.instance.Add(mail1, mail4, mail3, RedPointType.Once);
        //声明mai5节点,它的主key是mail1,subKey是mail5,父节点是mail3,红点类型是点击即消失
        RedPointMgr.instance.Add(mail1, mail5, mail3, RedPointType.Once);
        //声明mai5节点,它的主key是mail1,subKey是mail6,父节点是mail3,红点类型是点击即消失
        RedPointMgr.instance.Add(mail1, mail6, mail3, RedPointType.Once);

        //在实际开发中,初始化代码要写在对应UI界面的初始化函数中
        RedPointMgr.instance.Init(mail1, mail1, OnMail1Show);
        RedPointMgr.instance.Init(mail1, mail2, OnMail2Show);
        RedPointMgr.instance.Init(mail1, mail3, OnMail3Show);
        RedPointMgr.instance.Init(mail1, mail4, OnMail4Show, mail4Btn);
        RedPointMgr.instance.Init(mail1, mail5, OnMail5Show, mail5Btn);
        RedPointMgr.instance.Init(mail1, mail6, OnMail6Show, mail6Btn);

        btnSet1.onClick.AddListener(OnBtnSet1Click);
        btnSet2.onClick.AddListener(OnBtnSet2Click);
        btnSet3.onClick.AddListener(OnBtnSet3Click);
    }

    private void OnMail1Show(RedPointState state, int data)
    {
        mail1RedPoint.SetActive(state == RedPointState.Show);
        txtMail1.text = data.ToString();
    }

    private void OnMail2Show(RedPointState state, int data)
    {
        mail2RedPoint.SetActive(state == RedPointState.Show);
        txtMail2.text = data.ToString();
    }

    private void OnMail3Show(RedPointState state, int data)
    {
        mail3RedPoint.SetActive(state == RedPointState.Show);
        txtMail3.text = data.ToString();
    }

    private void OnMail4Show(RedPointState state, int data)
    {
        mail4RedPoint.SetActive(state == RedPointState.Show);
        txtMail4.text = data.ToString();
    }

    private void OnMail5Show(RedPointState state, int data)
    {
        mail5RedPoint.SetActive(state == RedPointState.Show);
        txtMail5.text = data.ToString();
        
    }

    private void OnMail6Show(RedPointState state, int data)
    {
        mail6RedPoint.SetActive(state == RedPointState.Show);
        txtMail6.text = data.ToString();
    }

    private void OnBtnSet1Click()
    {
        RedPointMgr.instance.SetState(mail1, mail4, count1 == 0 ? RedPointState.Hide : RedPointState.Show, count1);
    }

    private void OnBtnSet2Click()
    {
        RedPointMgr.instance.SetState(mail1, mail5, count2 == 0 ? RedPointState.Hide : RedPointState.Show, count2);
    }

    private void OnBtnSet3Click()
    {
        RedPointMgr.instance.SetState(mail1, mail6, count3 == 0 ? RedPointState.Hide : RedPointState.Show, count3);
    }
}

RedPointMgr.instance.SetState メソッドの 3 番目のパラメータは、赤い点に表示される数値であり、これも業務でカウントする必要があるデータです。赤い点だけが表示され、数値が表示されない場合は、渡す必要はありません。

 テスト スクリプトを実行すると、結果は次のようになります。
 

4. 結論


上記は、レッド ドット システムの簡単な実装です。私がこれを書いたのは、私の偉大な同僚が各レッド ドットを手書きしているのを見て、本当に驚いたからです。

あまりCVコードを書きたくない。

追伸:

数日前に hexo を使って個人ブログを作成しました。遊びに来てください。ミンイーhttps://ming-e.space/

おすすめ

転載: blog.csdn.net/s_GQY/article/details/126900574