Unity TreeView 网络收包显示工具(Json版)

已经记不清这是福州的第几天高温预警了,感觉自己在非洲,春困,秋乏,夏打盹,工作还是不能落下=_=Zzz...

想放假,想泡水

言归正传

咳咳咳,来看看今天和大家要分享的一个东西:网络收包显示工具。

在项目开发过程中,每一个游戏的需求是千变万化的,游戏种类也大相径庭,有单机,联网,有单人,多人。Unity虽然为我们提供了各种很方便的工具,但是要解决如此多的需求,还是有些力不从心,所以它开放了强大的编辑器扩展功能。用它的编辑器扩展,每一个游戏开发人员可以很方便的以Unity为平台开发自己游戏的工具链。

网络游戏开发过程中离不开和服务端的数据交互。之前我们项目服务端使用了Mina架构,游戏开发的匆忙(不要问我为什么匆忙,游戏公司的都懂/(ㄒoㄒ)/~~),所有服务端下发的数据都在Console控制台进行打印(你没有看错,直接打印...),控制台还会打印很多不同的信息,客户端与服务端联调的时候就十分费劲,需要不断的寻找下发的数据,心累,还是写个工具吧。

最终效果

运行时有点蓝~

知识点

1.Unity Editor使用
2.TreeView
3.LitJson 解析Json

动手撸代码

1.创建一个Editor窗体

新建名为NetListenerWindow的cs文件 ,放入Editor文件夹中(编辑器使用的代码,统一放Editor命名的文件夹,方便管理),使用MenuItem属性在编辑器中创建菜单入口。这样就初始化弹出一个和Unity自带的Scene和Game一样的Window窗体,之后左右的内容都需要显示在里面。

public class NetListenerWindow : EditorWindow
{
    [MenuItem("Window/NetListener--消息包监听")]
    static void Init()
    {
        NetListenerWindow window = (NetListenerWindow)EditorWindow.GetWindow(typeof(NetListenerWindow), true, "网络监听", true);
        window.Show();
    }
}
2.窗体布局

新弹出的窗体一片空白什么也没有,为了操作和显示的方便,需要对他进行简单的布局处理。Unity编辑器中布局使用的Unity最早的一套GUI系统 Immediate Mode GUI (IMGUI),布局主要使用GUILayout和GUIStyle进行纯代码控制,如果使用Unity较早的同学应该很熟悉这套东西,新上手的也没关系,可以先看看我的例子,文章末尾的扩展阅读也会有相关的资料。

空空如也的Window

编辑器的UI绘制需要写到OnGUI方法中,常用的方法有GUI控件的显示,GUI布局处理,GUI样式调整等,下面的代码是当前这个例子的GUI显示代码,包含详细的注解,欢迎食用~

void OnGUI()
    {
        if (GUILayout.Button("清空数据", EditorStyles.toolbarButton, GUILayout.Width(position.width)))
        {
          //绘制名为“清空数据”的按钮控件
          //按钮的样式使用内置的EditorStyles.toolbarButton样式,也可以自己new GUIStyle进行设置
          //GUILayout.Width方法设置按钮控件的绝对宽度为当前窗口的宽度
        }
        
        //创建一个横向流动布局,在一行中显示两个按钮
        GUILayout.BeginHorizontal();
        if (GUILayout.Button("展开全部", GUILayout.Width(position.width/2)))
        {
           //绘制名为“展开全部”的按钮控件
           //GUILayout.Width方法设置按钮控件的绝对宽度为当前窗口宽度的1/2
        }
        if (GUILayout.Button("折叠全部",GUILayout.Width(position.width/2)))
        {
           //绘制名为“折叠全部”的按钮控件
           //GUILayout.Width方法设置按钮控件的绝对宽度为当前窗口宽度的1/2
        }
        GUILayout.EndHorizontal();

        //开始绘制一个滚动视图布局
        GUILayout.BeginScrollView(Vector2.zero, GUILayout.Width(position.width), GUILayout.Height(position.height));
        //绘制接收到的网络消息TreeView控件
        GUILayout.EndScrollView();
    }

界面效果:

实用主意工具界面(⊙﹏⊙)b
3.定义接收网络包的展示TreeView控件

本来还担心自己要实现一个TreeView,查了下资料发现Unity已经有了这个功能(我使用的是Unity 5.6版),只需要在已有的控件类上进行简单的扩展就可以很方便的使用了。老规矩,带大量注释的代码。

//敲黑板,记得继承TreeView
class SimpleTreeView : TreeView
{
    public TreeViewItem root;//TreeView的根节点
    int itemIndex;//自增节点索引(每一个节点都需要一个id索引值,这里用自增型的)

    //构造函数
    public SimpleTreeView(TreeViewState treeViewState)
        : base(treeViewState)
    {
        itemIndex = 1;

        //TreeView里,depth代表节点的深度,最上层的根节点depth为-1
        root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };
        //初始化的时候需要在根节点下插入一个子节点,不然会报Children null的错误
        root.AddChild(new TreeViewItem { id = 1, depth = 0, displayName = "网络包接收" });
    }

    /// <summary>
    /// 获取自增的索引值
    /// </summary>
    /// <returns></returns>
    public int GetIndex()
    {
        itemIndex++;
        return itemIndex;
    }

    //重载BuildRoot方法
    protected override TreeViewItem BuildRoot()
    {
        SetupDepthsFromParentsAndChildren(root);
        return root;
    }
}

这里定义了一个简单的TreeView,TreeView中的每一个节点都用原生的TreeViewItem类实现,没有进行额外的扩展,当然你也可以根据实际的需要扩展这个类,用到自己的项目中。

4.定义&初始化

TreeViewState这个字段需要用[SerializeField]修饰,该字段是用来保存TreeView中的序列化后的信息(官方解释:The TreeViewState contains serializable state information for the TreeView)。netDatas用来保存接收到的网络消息,我这里是Json,各位具体项目中可以替换成自己对于的数据。

    [SerializeField]
    TreeViewState m_TreeViewState;

    SimpleTreeView m_SimpleTreeView;
    List<JsonData> netDatas;//保存接收到的网络消息

初始化数据的方法放到了OnEnable中,将m_TreeViewState传入我们定义的SimpleTreeView进行初始化。

    void OnEnable()
    {
        if (m_TreeViewState == null)
            m_TreeViewState = new TreeViewState();

        m_SimpleTreeView = new SimpleTreeView(m_TreeViewState);
        m_SimpleTreeView.Reload();
    }
5.获取数据,生成TreeView

这一部分主要是Json数据的解析,并生成对应的TreeView。对于Editor工具,游戏代码无法访问到Editor,需要Editor不断的在游戏运行时去获取接收到的数据。这是就需要的客户端数据接收部分预留一个接口,获取对应的数据,然后在Editor中调用这个接口。在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();

因为编辑器打开就在运行,而游戏只有在我们点击运行后才会执行,所以需要先判断游戏是否启动,这里我直接判断客户端Mina接收数据服务是否存在,如果存在就获取数据。

simpleTreeView的OnGUI需要在OnGUI中执行。

下面的代码是获取数据并生成TreeView的代码,每次GetNetDatas获取数据后,将缓存数据清除,就不会一直重复获取刷新,减少不必要的开销。

Json的解析包含的Json Object和Json Array两部分,有需要的朋友可以看看,不是使用Json的朋友可以略过。

生成TreeViewItem的时候要注意两点:
1.要注意depth值,depth表示了当前节点在树种的层级关系;
2.生成完一个节点的全部子节点,再生成下一个同级节点,因为TreeView的树结构是从上到下遍历生成的。

    /// <summary>
    /// 获取网络包数据
    /// </summary>
    void GetNetDatas()
    {
        if (Mina.MinaClient.netDatas.Count > 0)
        {
            netDatas = Mina.MinaClient.netDatas;

            TreeViewItem item;
            int eventid;//消息id
            int retcode;//消息状态
            JsonData content;//消息内容
            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>
    /// 获取内容项目
    /// </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);
        }
    }

这里还有个问题,如果大家只是做了这些会发现每次接收到数据的时候并不会实时刷新,因为当你运行游戏的时候,网络包接收窗口并不在Focus状态,OnGUI方法并没有激活执行,这时候你需要点击一下窗口才能收到数据,这显然不是我们想要的结果。这是我们就要在NetListenerWindow中加上下面这个方法,这个问题就完美解决啦。

void OnInspectorUpdate()
{
    //这里开启窗口的重绘,不然窗口信息不会刷新
    this.Repaint();
}
6.补齐按钮方法

按钮方法补齐下,OnGUI完整版

 void OnGUI()
    {
        if (GUILayout.Button("清空数据", 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();
    }

可以改进的几个地方

上面这些代码已经可以实现一个基本的网络收包显示工具啦,在使用过程中发现还是有不少地方可以改进:
1.树结构需要展开和折叠的时候只能点击左边小小的箭头,这样操作不是很方便,需要支持双击展开、双击折叠功能;
2.网络包结构复杂的时候,一级一级进行展开不是很方便(虽然有清空、展开全部、折叠全部这些按钮辅助),可以改进成拆分显示的方式,左边显示树状结构,点击后右边直接把网络包用字符串打印出来(注意排版);
3.恩,样子有点丑,我需要个美术同学~~

这些改进是都可以实现的,具体怎么做吗,哎,看看有时间再写加强版咯

扩展阅读

Unity - Manual: TreeView
Unity - Scripting API: TreeView
Unity 折叠树 EditorGUI.Foldout
UnityEditor.IMGUI中的TreeView
Immediate Mode GUI (IMGUI) GUIScriptingGuide

结语

结语
大夏天写博客,随手点个赞咯,共同学习,共同进步,共勉之。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • WebSocket-Swift Starscream的使用 WebSocket 是 HTML5 一种新的协议。它实...
    香橙柚子阅读 22,956评论 8 183
  • 我对陕西的感情比较特殊。 爷爷,太爷爷都出生在陕西。 打我记事起,经常听家人说起先前的事情。当时好像很富裕,住着四...
    银子姐阅读 362评论 0 0
  • 今天太迟了,没有通读,明天补读,感赏自己越来越能稳住情绪,即使孩子不起来吃晚饭也能理解他,让他自己把握,没有太多的...
    zhangxiaoyu阅读 107评论 0 1
  • 昨天作为发泄后的第一天上课,我的精神还有些恍惚,不过还好上课前有时间缓冲一下,也好多了,上午中午都没有什么好说的,...
    坚志阅读 136评论 0 0