ゲーム開発でツリー構造を使用する最も一般的な機能は、レッド ドット システムとビヘイビア ツリーです。
今日はまずレッドドットシステムの開発について書きます。
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/