赞
踩
目前Unity官方提供的UI扩展包中包含了UILineRenderer组件,本篇实现与UILineRenderer实现一致,主要讲解其基本使用与实现过程。不想看的同学可以直接下载官方扩展包。
代码库:unity-ui-extensionshttps://bitbucket.org/UnityUIExtensions/unity-ui-extensions/wiki/browse/
canvas上的渲染均通过CanvasRenderer,unity-ui扩展包实现了一套ui基础类UIPrimitiveBase,我们要讲的UILineRenderer正是继承于UIPrimitiveBase实现的Canvas上画线功能。
添加UILineRenderer组件,设置Points值即可根据输入点绘制出线段。Points数组为UILineRenderer子级UI坐标点。下面是UILineRenderer的一个使用范例。
- public void DrawLines(List<Vector3> worldPositionList)
- {
- //ui摄像机 为了将世界坐标转换为本组件下坐标。
- //注意:这个摄像机与worldPositionList相关联,本例中worldPositionList是被ui摄像机渲染的对象的世界坐标。
- var uiCamera = GameObject.FindWithTag(Tag.GuiCamera).GetComponent<Camera>();
- //UILineRenderer组件
- var lineRenderer = GetComponent<UILineRenderer>();
- //与LineRenderer同级RectTransform
- var myRectTransform = GetComponent<RectTransform>();
- var pointsList = new List<Vector2>();
- for (var i = 0; i < worldPositionList.Count; i++)
- {
- var screenPoint = uiCamera.WorldToScreenPoint(worldPositionList[i]);
- RectTransformUtility.ScreenPointToLocalPointInRectangle(myRectTransform, screenPoint, uiCamera,
- out var localPoint);
- pointsList.Add(localPoint);
- }
- //设置坐标,触发UILineRenderer上的CanvasRenderer重绘
- lineRenderer.Points = pointsList.ToArray();
- }
首先本文会忽略不详细讲解Line List,Bezier Mode这两个参数带来的变化,Line List将点列表按照两两成对的方式对待点列表,Bezier Mode会在处理点之前先将点按照贝塞尔曲线方式处理点,形成曲线。我们这里主要讲解一个基础的流程。
在我们设置了坐标点数组后,UILineRenderer调用SetAllDirty方法后回调到我们今天要讲的重点方法OnPopulateMesh,此方法会传入一个VertextHelper类,我们将需要设置的顶点数据塞进这个vh中即可。
假设有3个点P1,P2,P3,传递给UILineRenderer后,根据这3个点来绘制连线。如果线只有一个像素就很简单,依次将3个点连成线即可,但是UILineRenderer有lineThickness属性,可以设置线的宽度。
- private UIVertex[] CreateLineSegment(Vector2 start, Vector2 end, SegmentType type, UIVertex[] previousVert = null)
- {
- Vector2 offset = new Vector2((start.y - end.y), end.x - start.x).normalized * lineThickness / 2;
-
- Vector2 v1 = Vector2.zero;
- Vector2 v2 = Vector2.zero;
- if (previousVert != null) {
- v1 = new Vector2(previousVert[3].position.x, previousVert[3].position.y);
- v2 = new Vector2(previousVert[2].position.x, previousVert[2].position.y);
- } else {
- v1 = start - offset;
- v2 = start + offset;
- }
-
- var v3 = end + offset;
- var v4 = end - offset;
- //Return the VDO with the correct uvs
- switch (type)
- {
- case SegmentType.Start:
- return SetVbo(new[] { v1, v2, v3, v4 }, startUvs);
- case SegmentType.End:
- return SetVbo(new[] { v1, v2, v3, v4 }, endUvs);
- case SegmentType.Full:
- return SetVbo(new[] { v1, v2, v3, v4 }, fullUvs);
- default:
- return SetVbo(new[] { v1, v2, v3, v4 }, middleUvs);
- }
- }
遍历点数组,每两个点形成一条直线,创建一个由四边形来表示一条线段。这个函数比较难理解地方是如何根据点P1,P2以及线宽来计算出两边的点来的,就是上面offset是如何计算出来的。首先我们将上面的3个点把所有辅助线画出来如下。
start参数即P1点,end参数即P2点。我们最终绘制的线段是a2,a1这两边的线段。offset即P1->a2的偏移*-1或者P1->a1的偏移*1
Vector2 offset = new Vector2((start.y - end.y), end.x - start.x).normalized * lineThickness / 2;
其实上面就是求就是一个P1到P2向量一个旋转矩阵,旋转度数为90度,旋转矩阵为:
右乘P1->P2的向量即得到(start.y-end.y,end.x-start.x),标准化后乘以线宽除以2即是我们要得到的偏移量。各自2个顶点加减偏移得到4个顶点后放进UIVertex数组,就可以形成一个由两个三角形组成的四边形,顺便提下2个三角形顶点顺序如下:
- public void AddUIVertexQuad(UIVertex[] verts)
- {
- int currentVertCount = this.currentVertCount;
- for (int index = 0; index < 4; ++index)
- this.AddVert(verts[index].position, verts[index].color, verts[index].uv0, verts[index].uv1, verts[index].normal, verts[index].tangent);
- this.AddTriangle(currentVertCount, currentVertCount + 1, currentVertCount + 2);
- this.AddTriangle(currentVertCount + 2, currentVertCount + 3, currentVertCount);
- }
所有点处理好后,如果直接绘制线段,我们会发现如下现象:
在P2点会存在接缝的情况,下一步就是处理接缝的逻辑
- // Add the line segments to the vertex helper, creating any joins as needed
- for (var i = 0; i < segments.Count; i++) {
- if (!lineList && i < segments.Count - 1) {
- var vec1 = segments [i] [1].position - segments [i] [2].position;
- var vec2 = segments [i + 1] [2].position - segments [i + 1] [1].position;
- var angle = Vector2.Angle (vec1, vec2) * Mathf.Deg2Rad;
-
- // Positive sign means the line is turning in a 'clockwise' direction
- var sign = Mathf.Sign (Vector3.Cross (vec1.normalized, vec2.normalized).z);
-
- // Calculate the miter point
- var miterDistance = lineThickness / (2 * Mathf.Tan (angle / 2));
- var miterPointA = segments [i] [2].position - vec1.normalized * miterDistance * sign;
- var miterPointB = segments [i] [3].position + vec1.normalized * miterDistance * sign;
-
- var joinType = LineJoins;
- if (joinType == JoinType.Miter) {
- // Make sure we can make a miter join without too many artifacts.
- if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_MITER_JOIN) {
- segments [i] [2].position = miterPointA;
- segments [i] [3].position = miterPointB;
- segments [i + 1] [0].position = miterPointB;
- segments [i + 1] [1].position = miterPointA;
- } else {
- joinType = JoinType.Bevel;
- }
- }
-
- if (joinType == JoinType.Bevel) {
- if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_BEVEL_NICE_JOIN) {
- if (sign < 0) {
- segments [i] [2].position = miterPointA;
- segments [i + 1] [1].position = miterPointA;
- } else {
- segments [i] [3].position = miterPointB;
- segments [i + 1] [0].position = miterPointB;
- }
- }
-
- var join = new UIVertex[] { segments [i] [2], segments [i] [3], segments [i + 1] [0], segments [i + 1] [1] };
- vh.AddUIVertexQuad (join);
- }
- }
-
- vh.AddUIVertexQuad (segments [i]);
- }
处理的方式如下图所示:
代码中的miterDistance即图中H->P2P1的距离,首先求出vec1与vec2如图所示得到角度,根据三角函数就求得距离。两个向量叉乘判断方向来加或者减这个偏移量。
走完这一步就完成了点的处理。
这个组件用起来其实很简单,设置点数组即可,主要对这个偏移计算比较感兴趣,就写成了文章记录一下,用到了旋转矩阵,叉乘判断方向这些数学基础运用到实际开发中。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。