已經(jīng)記不清這是福州的第幾天高溫預(yù)警了钉跷,感覺自己在非洲尼斧,春困,秋乏,夏打盹堵泽,工作還是不能落下=_=Zzz...
言歸正傳
咳咳咳父阻,來看看今天和大家要分享的一個東西:網(wǎng)絡(luò)收包顯示工具杜窄。
在項目開發(fā)過程中,每一個游戲的需求是千變?nèi)f化的蚌本,游戲種類也大相徑庭盔粹,有單機,聯(lián)網(wǎng)程癌,有單人舷嗡,多人。Unity雖然為我們提供了各種很方便的工具嵌莉,但是要解決如此多的需求进萄,還是有些力不從心,所以它開放了強大的編輯器擴展功能锐峭。用它的編輯器擴展中鼠,每一個游戲開發(fā)人員可以很方便的以Unity為平臺開發(fā)自己游戲的工具鏈。
網(wǎng)絡(luò)游戲開發(fā)過程中離不開和服務(wù)端的數(shù)據(jù)交互沿癞。之前我們項目服務(wù)端使用了Mina架構(gòu)援雇,游戲開發(fā)的匆忙(不要問我為什么匆忙,游戲公司的都懂/(ㄒoㄒ)/~~)椎扬,所有服務(wù)端下發(fā)的數(shù)據(jù)都在Console控制臺進行打颖共(你沒有看錯,直接打印...)蚕涤,控制臺還會打印很多不同的信息晶府,客戶端與服務(wù)端聯(lián)調(diào)的時候就十分費勁,需要不斷的尋找下發(fā)的數(shù)據(jù)钻趋,心累川陆,還是寫個工具吧。
最終效果
知識點
1.Unity Editor使用
2.TreeView
3.LitJson 解析Json
動手擼代碼
1.創(chuàng)建一個Editor窗體
新建名為NetListenerWindow的cs文件 蛮位,放入Editor文件夾中(編輯器使用的代碼较沪,統(tǒng)一放Editor命名的文件夾,方便管理),使用MenuItem屬性在編輯器中創(chuàng)建菜單入口失仁。這樣就初始化彈出一個和Unity自帶的Scene和Game一樣的Window窗體尸曼,之后左右的內(nèi)容都需要顯示在里面。
public class NetListenerWindow : EditorWindow
{
[MenuItem("Window/NetListener--消息包監(jiān)聽")]
static void Init()
{
NetListenerWindow window = (NetListenerWindow)EditorWindow.GetWindow(typeof(NetListenerWindow), true, "網(wǎng)絡(luò)監(jiān)聽", true);
window.Show();
}
}
2.窗體布局
新彈出的窗體一片空白什么也沒有萄焦,為了操作和顯示的方便控轿,需要對他進行簡單的布局處理冤竹。Unity編輯器中布局使用的Unity最早的一套GUI系統(tǒng) Immediate Mode GUI (IMGUI),布局主要使用GUILayout和GUIStyle進行純代碼控制茬射,如果使用Unity較早的同學(xué)應(yīng)該很熟悉這套東西鹦蠕,新上手的也沒關(guān)系,可以先看看我的例子在抛,文章末尾的擴展閱讀也會有相關(guān)的資料钟病。
編輯器的UI繪制需要寫到OnGUI方法中,常用的方法有GUI控件的顯示刚梭,GUI布局處理肠阱,GUI樣式調(diào)整等,下面的代碼是當(dāng)前這個例子的GUI顯示代碼朴读,包含詳細的注解屹徘,歡迎食用~
void OnGUI()
{
if (GUILayout.Button("清空數(shù)據(jù)", EditorStyles.toolbarButton, GUILayout.Width(position.width)))
{
//繪制名為“清空數(shù)據(jù)”的按鈕控件
//按鈕的樣式使用內(nèi)置的EditorStyles.toolbarButton樣式,也可以自己new GUIStyle進行設(shè)置
//GUILayout.Width方法設(shè)置按鈕控件的絕對寬度為當(dāng)前窗口的寬度
}
//創(chuàng)建一個橫向流動布局衅金,在一行中顯示兩個按鈕
GUILayout.BeginHorizontal();
if (GUILayout.Button("展開全部", GUILayout.Width(position.width/2)))
{
//繪制名為“展開全部”的按鈕控件
//GUILayout.Width方法設(shè)置按鈕控件的絕對寬度為當(dāng)前窗口寬度的1/2
}
if (GUILayout.Button("折疊全部",GUILayout.Width(position.width/2)))
{
//繪制名為“折疊全部”的按鈕控件
//GUILayout.Width方法設(shè)置按鈕控件的絕對寬度為當(dāng)前窗口寬度的1/2
}
GUILayout.EndHorizontal();
//開始繪制一個滾動視圖布局
GUILayout.BeginScrollView(Vector2.zero, GUILayout.Width(position.width), GUILayout.Height(position.height));
//繪制接收到的網(wǎng)絡(luò)消息TreeView控件
GUILayout.EndScrollView();
}
界面效果:
3.定義接收網(wǎng)絡(luò)包的展示TreeView控件
本來還擔(dān)心自己要實現(xiàn)一個TreeView噪伊,查了下資料發(fā)現(xiàn)Unity已經(jīng)有了這個功能(我使用的是Unity 5.6版),只需要在已有的控件類上進行簡單的擴展就可以很方便的使用了典挑。老規(guī)矩酥宴,帶大量注釋的代碼。
//敲黑板您觉,記得繼承TreeView
class SimpleTreeView : TreeView
{
public TreeViewItem root;//TreeView的根節(jié)點
int itemIndex;//自增節(jié)點索引(每一個節(jié)點都需要一個id索引值拙寡,這里用自增型的)
//構(gòu)造函數(shù)
public SimpleTreeView(TreeViewState treeViewState)
: base(treeViewState)
{
itemIndex = 1;
//TreeView里,depth代表節(jié)點的深度琳水,最上層的根節(jié)點depth為-1
root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };
//初始化的時候需要在根節(jié)點下插入一個子節(jié)點肆糕,不然會報Children null的錯誤
root.AddChild(new TreeViewItem { id = 1, depth = 0, displayName = "網(wǎng)絡(luò)包接收" });
}
/// <summary>
/// 獲取自增的索引值
/// </summary>
/// <returns></returns>
public int GetIndex()
{
itemIndex++;
return itemIndex;
}
//重載BuildRoot方法
protected override TreeViewItem BuildRoot()
{
SetupDepthsFromParentsAndChildren(root);
return root;
}
}
這里定義了一個簡單的TreeView,TreeView中的每一個節(jié)點都用原生的TreeViewItem類實現(xiàn)在孝,沒有進行額外的擴展诚啃,當(dāng)然你也可以根據(jù)實際的需要擴展這個類,用到自己的項目中私沮。
4.定義&初始化
TreeViewState這個字段需要用[SerializeField]修飾始赎,該字段是用來保存TreeView中的序列化后的信息(官方解釋:The TreeViewState contains serializable state information for the TreeView)。netDatas用來保存接收到的網(wǎng)絡(luò)消息仔燕,我這里是Json造垛,各位具體項目中可以替換成自己對于的數(shù)據(jù)。
[SerializeField]
TreeViewState m_TreeViewState;
SimpleTreeView m_SimpleTreeView;
List<JsonData> netDatas;//保存接收到的網(wǎng)絡(luò)消息
初始化數(shù)據(jù)的方法放到了OnEnable中晰搀,將m_TreeViewState傳入我們定義的SimpleTreeView進行初始化五辽。
void OnEnable()
{
if (m_TreeViewState == null)
m_TreeViewState = new TreeViewState();
m_SimpleTreeView = new SimpleTreeView(m_TreeViewState);
m_SimpleTreeView.Reload();
}
5.獲取數(shù)據(jù),生成TreeView
這一部分主要是Json數(shù)據(jù)的解析外恕,并生成對應(yīng)的TreeView杆逗。對于Editor工具乡翅,游戲代碼無法訪問到Editor,需要Editor不斷的在游戲運行時去獲取接收到的數(shù)據(jù)罪郊。這是就需要的客戶端數(shù)據(jù)接收部分預(yù)留一個接口蠕蚜,獲取對應(yīng)的數(shù)據(jù),然后在Editor中調(diào)用這個接口排龄。在OnGUI中的方法做如下改動:
if (Mina.MinaClient.app != null)
{
GetNetDatas();
}
GUILayout.BeginScrollView(Vector2.zero, GUILayout.Width(position.width), GUILayout.Height(position.height));
m_SimpleTreeView.OnGUI(new Rect(0, 0, position.width, position.height));
GUILayout.EndScrollView();
因為編輯器打開就在運行波势,而游戲只有在我們點擊運行后才會執(zhí)行翎朱,所以需要先判斷游戲是否啟動橄维,這里我直接判斷客戶端Mina接收數(shù)據(jù)服務(wù)是否存在,如果存在就獲取數(shù)據(jù)拴曲。
simpleTreeView的OnGUI需要在OnGUI中執(zhí)行争舞。
下面的代碼是獲取數(shù)據(jù)并生成TreeView的代碼,每次GetNetDatas獲取數(shù)據(jù)后澈灼,將緩存數(shù)據(jù)清除竞川,就不會一直重復(fù)獲取刷新,減少不必要的開銷叁熔。
Json的解析包含的Json Object和Json Array兩部分委乌,有需要的朋友可以看看,不是使用Json的朋友可以略過荣回。
生成TreeViewItem的時候要注意兩點:
1.要注意depth值遭贸,depth表示了當(dāng)前節(jié)點在樹種的層級關(guān)系;
2.生成完一個節(jié)點的全部子節(jié)點心软,再生成下一個同級節(jié)點壕吹,因為TreeView的樹結(jié)構(gòu)是從上到下遍歷生成的。
/// <summary>
/// 獲取網(wǎng)絡(luò)包數(shù)據(jù)
/// </summary>
void GetNetDatas()
{
if (Mina.MinaClient.netDatas.Count > 0)
{
netDatas = Mina.MinaClient.netDatas;
TreeViewItem item;
int eventid;//消息id
int retcode;//消息狀態(tài)
JsonData content;//消息內(nèi)容
int index;
for (int i = 0; i < netDatas.Count; i++)
{
eventid = (int)netDatas[i]["e"];
retcode = (int)netDatas[i]["r"];
if (netDatas[i].Keys.Contains("p"))
{
content = netDatas[i]["p"];
}
else
{
content = null;
}
index = m_SimpleTreeView.GetIndex();
item = new TreeViewItem { id = index, depth = 0, displayName = "eventid:" + eventid + " retcode:" + retcode };
m_SimpleTreeView.root.AddChild(item);
AddContentItem(item, content, 1);
}
m_SimpleTreeView.Reload();
netDatas.Clear();
Mina.MinaClient.netDatas.Clear();
}
}
/// <summary>
/// 獲取內(nèi)容項目
/// </summary>
/// <param name="content"></param>
/// <param name="depth"></param>
void AddContentItem(TreeViewItem parentItem, JsonData content, int depth)
{
TreeViewItem item;
JsonData subContent;
int arrayIndex;
if (content == null)
return;
if (content.GetJsonType() == JsonType.Object)
{
foreach (var key in content.Keys)
{
subContent = content[key];
item = new TreeViewItem { id = m_SimpleTreeView.GetIndex(), depth = depth, displayName = key };
parentItem.AddChild(item);
AddContentItem(item, subContent, depth + 1);
}
}
else if (content.GetJsonType() == JsonType.Array)
{
arrayIndex = 0;
foreach (JsonData elem in content)
{
item = new TreeViewItem { id = m_SimpleTreeView.GetIndex(), depth = depth, displayName = arrayIndex.ToString() };
parentItem.AddChild(item);
arrayIndex++;
AddContentItem(item, elem, depth + 1);
}
}
else
{
item = new TreeViewItem { id = m_SimpleTreeView.GetIndex(), depth = depth, displayName = content.ToString() };
parentItem.AddChild(item);
}
}
這里還有個問題删铃,如果大家只是做了這些會發(fā)現(xiàn)每次接收到數(shù)據(jù)的時候并不會實時刷新耳贬,因為當(dāng)你運行游戲的時候,網(wǎng)絡(luò)包接收窗口并不在Focus狀態(tài)猎唁,OnGUI方法并沒有激活執(zhí)行咒劲,這時候你需要點擊一下窗口才能收到數(shù)據(jù),這顯然不是我們想要的結(jié)果诫隅。這是我們就要在NetListenerWindow中加上下面這個方法腐魂,這個問題就完美解決啦。
void OnInspectorUpdate()
{
//這里開啟窗口的重繪阎肝,不然窗口信息不會刷新
this.Repaint();
}
6.補齊按鈕方法
按鈕方法補齊下挤渔,OnGUI完整版
void OnGUI()
{
if (GUILayout.Button("清空數(shù)據(jù)", EditorStyles.toolbarButton, GUILayout.Width(position.width)))
{
m_SimpleTreeView.root.children.Clear();
m_SimpleTreeView.Reload();
}
GUILayout.BeginHorizontal();
if (GUILayout.Button("展開全部", GUILayout.Width(position.width/2)))
{
m_SimpleTreeView.ExpandAll();
}
if (GUILayout.Button("折疊全部",GUILayout.Width(position.width/2)))
{
m_SimpleTreeView.CollapseAll();
}
GUILayout.EndHorizontal();
if (Mina.MinaClient.app != null)
{
GetNetDatas();
}
GUILayout.BeginScrollView(Vector2.zero, GUILayout.Width(position.width), GUILayout.Height(position.height));
m_SimpleTreeView.OnGUI(new Rect(0, 0, position.width, position.height));
GUILayout.EndScrollView();
}
可以改進的幾個地方
上面這些代碼已經(jīng)可以實現(xiàn)一個基本的網(wǎng)絡(luò)收包顯示工具啦,在使用過程中發(fā)現(xiàn)還是有不少地方可以改進:
1.樹結(jié)構(gòu)需要展開和折疊的時候只能點擊左邊小小的箭頭风题,這樣操作不是很方便判导,需要支持雙擊展開嫉父、雙擊折疊功能;
2.網(wǎng)絡(luò)包結(jié)構(gòu)復(fù)雜的時候眼刃,一級一級進行展開不是很方便(雖然有清空绕辖、展開全部、折疊全部這些按鈕輔助)擂红,可以改進成拆分顯示的方式仪际,左邊顯示樹狀結(jié)構(gòu),點擊后右邊直接把網(wǎng)絡(luò)包用字符串打印出來(注意排版)昵骤;
3.恩树碱,樣子有點丑,我需要個美術(shù)同學(xué)~~
這些改進是都可以實現(xiàn)的变秦,具體怎么做嗎成榜,哎,看看有時間再寫加強版咯
擴展閱讀
Unity - Manual: TreeView
Unity - Scripting API: TreeView
Unity 折疊樹 EditorGUI.Foldout
UnityEditor.IMGUI中的TreeView
Immediate Mode GUI (IMGUI) GUIScriptingGuide
結(jié)語
結(jié)語
大夏天寫博客蹦玫,隨手點個贊咯赎婚,共同學(xué)習(xí),共同進步樱溉,共勉之挣输。