Unity3D开发之折线图制作

来源:互联网 发布:java 创建线程 sleep 编辑:程序博客网 时间:2024/05/16 14:57
 柱状图、折线图这个在数据可视化中会经常用到,可是unity自身并不带创建折线图功能。所以,需要我们自己去写。第一次遇到这个需求是在我刚参加工作时,当时实在没法子,只好选择做了柱状图,那时候柱状图还是用实例化Image然后设置他的高度来显示。想想300,400个数据同时实例化,当时的设备得卡成啥样。还好,现在我找到了一个更好更省性能的方法。如果你想在我这复制代码去完成你的需求那你没必要往下看了,你只有了解整个过程原理才可以灵活运用转接到你的项目里。
先放一张效果图吸睛。嘿嘿。。

因为之前写的早也比较仓促,没那么细,所以趁最近不是很忙打算给翻新细化下。
unity的Image,Text等他们均继承自MaskableGraphic(ui的东西最好继承自他,在做mask遮罩时是会被支持的),所以我们写的这个类也是继承自MaskableGraphic,如:
public class Chart : MaskableGraphic{}


其提供的函数OnPopulateMesh(VertexHelper vh)负责渲染出来的功能。要做折线图需要一个一个长方形来组成拼接。一个长方形由四个顶点组成,下面贴上渲染一个长方形的代码:
protected override void OnPopulateMesh(VertexHelper vh)    {         UIVertex[] verts = new UIVertex[4];                verts[0].position = new Vector3(0, 0);                verts[0].color = Color.yellow;                verts[0].uv0 = Vector2.zero;                verts[1].position = new Vector3(0,10);                verts[1].color = Color.yellow;                verts[1].uv0 = Vector2.zero;                verts[2].position = new Vector3(20 ,20);                verts[2].color = Color.yellow;                verts[2].uv0 = Vector2.zero;                verts[3].position = new Vector3(20 , 0);                verts[3].color = Color.yellow;                verts[3].uv0 = Vector2.zero;                vh.AddUIVertexQuad(verts);    }
 vh.AddUIVertexQuad(verts);是将长方形的四个顶点添加到渲染数组中供unity渲染,以上就是unity提供的渲染一个自定义大小矩形的核心代码。下面就说下我们实现自己的需求方法。
首先,我们需要在场景中创建一个canvas,在canvas下创建一个空物体,一会用于挂载我们自写的脚本来显示自绘图形。我们设置这个物体的大小如下图:
由于我们以左下角为原点,所以我们要设置物体的pivot为(0,0),还要设置锚点为左下角,即Anchors的min(0,0) max(0,0)。接下来我们就要开始我们的绘制工作了。假入我们要绘制的温度曲线,前两个数据值是38,25(随机的两个数),如下图:两个黑点的位置,我们可以绘制顶点为1234的平行四边形。在倾斜的平行四边形上这个平行四边形会出现头宽尾窄的不协调现象,并且在斜率不同时,线的粗细程度也有很大不同。这个在整体效果上非常差。于是,我们必须得设置线的宽度以保证整体视觉的协调性。这个计算公式很简单,先求出线的斜率k=(pos1.y-pos0.y)/(pos1.x-pos0.x);你可以在图纸上画图看下会更直观些。假如第一个点坐标为(x0,y0),设置线的宽度为m,k=0时,则长方形的第一个点为(x0,y0-m/2),第二个点为(x0,y0+m/2),但是通常情况下k并不等于0,所以根据勾股定理我们可以求出h²=k²+(m/2)²,即第一个点可设置为(x0,y0-h/2),第二个点为(x0,y0+h/2)。(以上是我经过几次测试觉得会让曲线更匀称漂亮的一种方案)好了,我们的自定义折线图已经完成了,由于折线图是在一次性绘制出来,所以非常的节省性能。真心底层接口对于性能的优化会有一个很大的提升。


现在我们已经可以绘制出我们自己想要的折线图,唯一的缺陷就是锯齿会有些大,这个还没有办法解决(如果有知道能提升画质的记得在下面评论啊)。今天策划那面又提出了新的需求,就是鼠标悬浮到数据上会显示对应的值。大家知道unity自带的UI组件不支持这些功能,所以我们就需要研究下UGUI放出来的源码进行自定义更改。
首先,我们需要在折线图上绘制出每个数据点,为了让更清晰可见,我们选择绘制白色的正方形。假设第一个数据点为38,我们测试矩形边长为3时大小合适,则代码如下:
UIVertex[] verts = new UIVertex[4];        verts[0].position = new Vector3(10-1.5f,38-1.5f);        verts[0].color = Color.white;        verts[0].uv0 = Vector2.zero;        verts[1].position = new Vector3(10-1.5f, 38+1.5f);        verts[1].color = Color.white;        verts[1].uv0 = Vector2.zero;        verts[2].position = new Vector3(10+1.5f, 38+1.5f);        verts[2].color = Color.white;        verts[2].uv0 = Vector2.zero;        verts[3].position = new Vector3(10+1.5f, 38-1.5f);        verts[3].color = Color.white;        verts[3].uv0 = Vector2.zero;        vh.AddUIVertexQuad(verts);
这个是最基本的矩形绘制代码,很简单。接下来就是核心的自定义鼠标悬浮响应区域。在网上搜索一些相关知识以及看了UGUI 的Image代码后,我们可以确定,鼠标悬浮响应区域的设定和IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)这个函数有关,而这个函数是继承自ICanvasRaycastFilter接口。所以我们就在自己写的函数内加一个继承的接口,然后实现他的方法。我就试着使用Image提供的一些方法测试,很幸运我成功了。代码如下:
public bool IsRaycastLocationValid(UnityEngine.Vector2 sp, UnityEngine.Camera eventCamera)    {        Vector2 local;        //求出屏幕坐标在canvas下的2D坐标        RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, sp, eventCamera, out local);        Rect rect = GetPixelAdjustedRect();        // Convert to have lower left corner as reference point.        local.x += rectTransform.pivot.x * rect.width; //算出屏幕坐标在UI元素内的坐标        local.y += rectTransform.pivot.y * rect.height;        //这里的数字是根据每个点来确定的,也就是以这个点为中心的自定义边长的正方形四个顶点。        if (local.x > 10 && local.x < 30 && local.y > 20 && local.y < 30)            return true;        else            return false;    }
在我们调用这个函数时,第一个参数就是Input.mousePosition,第二个参数为Camera cam = (canvas.renderMode == RenderMode.ScreenSpaceOverlay) ? null : canvas.worldCamera;。在Update函数里我们调用这个函数进行检测:
private void Update()      {        bool lal= IsRaycastLocationValid(Input.mousePosition, null);        //print(lal);      }
如果返回true,我们就把提前设定好的Text给setactive true,并赋给相应的值。接下来的处理就很简单可以按照自己的想法来实现。我这个方法虽然可以实现现在的需求,但是有个很大的缺点,就是在数据有几百个的时候,每次都要计算鼠标是否在这几百个点的位置范围内,计算量很大。所以,接下来打算做点的选取设置。在被遮罩遮挡的区域点不被纳入对比计算范围。只将显示出来的数据纳入对比计算范围内。
最后的效果图。









原创粉丝点击