UGUI 源码研究及扩展 :利用Graphic做UI折线图

近日项目中需要用到一些数据分析的功能,查阅资料后发现了UGUI有一个用来显示图像抽象类Graphic,另外一个用来显示可遮罩图形的MaskableGraphic就是派生自Graphic, Image,Text等组件都是派生自MaskableGraphic。利用这两个类我们可以做一些自定义组件。
本文利用Graphic来做一个简易的折线图。

下图是利用这个组件分析地形高度的图片

UGUI 源码研究及扩展 :利用Graphic做UI折线图_第1张图片

1.Graphic

  这个类是UGUI中所有的可视化组件的基类,这里定义了一个名为OnPopulateMesh的虚函数,我们的图形渲染代码就在这里编写。

2.UIVertex

UGUI 源码研究及扩展 :利用Graphic做UI折线图_第2张图片
  UIVertex包含了顶点信息,其位置信息是以像素为单位的。 文章开头的动图上的线其实是由很多个小四边形组成的,一个小四边形由四个UIVertex组成,构建好一个UIVextex数组并赋值后,调用VertexHelper.AddUIVertexQuad(UIVertexs),将一个四边形加入渲染任务。最后要调用SetVerticesDirty()来刷新一次图像。当我们的数据发生变化的时候,也要调用这个函数来刷新图像。

3.LineChat

  附上完整的组件代码

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

namespace XFramework.UI
{
    /// 
    /// 折线图
    /// 
    public class LineChat : Graphic
    {
        [Header("坐标轴显示属性")]
        public float width = 200;
        public float height = 200;
        public float lineWidth = 5;
        public Vector2 offset = new Vector2(2, 2);
        public Color axleColor = Color.red;

        [Header("坐标轴数据属性")]
        public Vector2 axleMaxValue = new Vector2(100, 100);
        public Vector2 axleMinValue = Vector3.zero;
        public List<Vector2[]> datas = new List<Vector2[]>();

        [Space]
        [Header("刻度线")]
        public int scaleLineHeight = 5;
        public Vector2 subdivisions = new Vector2(10, 10);

        [Space]
        [Header("数据")]
        public Color lineColor = Color.blue;

        protected override void OnPopulateMesh(VertexHelper vh)
        {
            vh.Clear();
            DrawAxle(vh);
            DrawLine(vh);
        }

        /// 
        /// 坐标轴
        /// 
        /// 
        private void DrawAxle(VertexHelper vh)
        {
            UIVertex[] verts = new UIVertex[4];
            for (int i = 0; i < verts.Length; i++)
            {
                verts[i].color = axleColor;
            }

            // Y轴
            verts[0].position = offset;
            verts[1].position = new Vector2(lineWidth, 0) + offset;
            verts[2].position = new Vector2(lineWidth, height) + offset;
            verts[3].position = new Vector2(0, height) + offset;
            vh.AddUIVertexQuad(verts);

            // X轴
            verts[0].position = offset;
            verts[1].position = new Vector2(width, 0) + offset;
            verts[2].position = new Vector2(width, lineWidth) + offset;
            verts[3].position = new Vector2(0, lineWidth) + offset;
            vh.AddUIVertexQuad(verts);

            ScaleLine();
            Arrow();

            // 刻度线
            void ScaleLine()
            {
                Vector2 offset;

                for (int i = 1; i < subdivisions.x; i++)
                {
                    offset = this.offset + new Vector2(width / subdivisions.x * i, 0);
                    verts[0].position = offset;
                    verts[1].position = new Vector2(lineWidth, 0) + offset;
                    verts[2].position = new Vector2(lineWidth, scaleLineHeight) + offset;
                    verts[3].position = new Vector2(0, scaleLineHeight) + offset;
                    vh.AddUIVertexQuad(verts);
                }

                for (int i = 1; i < subdivisions.y; i++)
                {
                    offset = this.offset + new Vector2(0, height / subdivisions.y * i);
                    verts[0].position = offset;
                    verts[1].position = new Vector2(scaleLineHeight, 0) + offset;
                    verts[2].position = new Vector2(scaleLineHeight, lineWidth) + offset;
                    verts[3].position = new Vector2(0, lineWidth) + offset;
                    vh.AddUIVertexQuad(verts);
                }
            }

            // 箭头
            void Arrow()
            {
                Vector2 offset;
                float arrowLength = width / subdivisions.x / 2;

                // X轴
                offset = this.offset + new Vector2(width, 0);
                verts[0].position = offset;
                verts[1].position = new Vector2(0, lineWidth) + offset;
                verts[2].position = new Vector2(-arrowLength, lineWidth + scaleLineHeight) + offset;
                verts[3].position = new Vector2(-arrowLength, scaleLineHeight) + offset;
                vh.AddUIVertexQuad(verts);

                verts[0].position = new Vector2(0, lineWidth) + offset;
                verts[1].position = offset;
                verts[2].position = new Vector2(-arrowLength, -lineWidth - scaleLineHeight) + offset;
                verts[3].position = new Vector2(-arrowLength, -scaleLineHeight) + offset;
                vh.AddUIVertexQuad(verts);

                // Y轴
                offset = this.offset + new Vector2(0, height);
                verts[0].position = offset;
                verts[1].position = new Vector2(lineWidth, 0) + offset;
                verts[2].position = new Vector2(lineWidth + scaleLineHeight, -arrowLength) + offset;
                verts[3].position = new Vector2(scaleLineHeight, -arrowLength) + offset;
                vh.AddUIVertexQuad(verts);

                verts[0].position = offset;
                verts[1].position = new Vector2(lineWidth, 0) + offset;
                verts[2].position = new Vector2(lineWidth - scaleLineHeight, -arrowLength) + offset;
                verts[3].position = new Vector2(-scaleLineHeight, -arrowLength) + offset;
                vh.AddUIVertexQuad(verts);
            }
        }

        /// 
        /// 画数据折线
        /// 
        private void DrawLine(VertexHelper vh)
        {
            foreach (var data in datas)
            {
                Vector2[] pixelPoints = new Vector2[data.Length];
                for (int i = 0; i < data.Length; i++)
                {
                    pixelPoints[i].x = (data[i].x - axleMinValue.x) / axleMaxValue.x * width;
                    pixelPoints[i].y = (data[i].y - axleMinValue.y) / axleMaxValue.y * height;
                    pixelPoints[i] += offset;
                }

                UIVertex[] verts = new UIVertex[4];
                for (int i = 0; i < verts.Length; i++)
                {
                    verts[i].color = lineColor;
                }

                for (int i = 0; i < pixelPoints.Length - 1; i++)
                {
                    SetVerts(pixelPoints[i], pixelPoints[i + 1], lineWidth, verts);
                    vh.AddUIVertexQuad(verts);
                }

                // 点状数据
                //foreach (var item in pixelPoints)
                //{
                //    verts[0].position = item;
                //    verts[1].position = new Vector2(5, 0) + item;
                //    verts[2].position = new Vector2(5, 5) + item;
                //    verts[3].position = new Vector2(0, 5) + item;
                //    vh.AddUIVertexQuad(verts);
                //}

                void SetVerts(Vector2 _start, Vector2 _end, float _width, UIVertex[] _verts)
                {
                    Vector2[] tmp = PhysicsMath.GetRect(_start, _end, _width);
                    _verts[0].position = tmp[0];
                    _verts[1].position = tmp[1];
                    _verts[2].position = tmp[3];
                    _verts[3].position = tmp[2];
                }
            }
        }

        /// 
        /// 添加数据
        /// 
        /// 
        public void AddDatas(Vector2[] datas)
        {
            this.datas.Add(datas);
            SetVerticesDirty();
        }

        /// 
        /// 清空数据
        /// 
        public void Clear()
        {
            datas.Clear();
            SetVerticesDirty();
        }

        /// 
        /// 刷新数据
        /// 
        public void Refresh()
        {
            SetAllDirty();
        }

#if UNITY_EDITOR
        /// 
        /// 属性面板值发生变化时调用
        /// 
        protected override void OnValidate()
        {
            SetVerticesDirty();
        }
#endif
    }
}

   PhysicsMath.GetRect是在其他地方实现的,其内部实现如下:

public static Vector2[] GetRect(Vector2 _start, Vector2 _end, float _width)
{
	Vector2[] rect = new Vector2[4];
	Vector2 dir = GetHorizontalDir(_end - _start);
	rect[0] = _start + dir * _width;
	rect[1] = _start - dir * _width;
	rect[2] = _end + dir * _width;
	rect[3] = _end - dir * _width;
	
	return rect;
	
	Vector2 GetHorizontalDir(Vector2 _dir)
	{
		return new Vector2(_dir.y, -_dir.x).normalized;
	}
}

你可能感兴趣的:(Unity,UGUI)