当前位置:   article > 正文

Unity在Canvas上画线(Draw Line)实现_unity drawline

unity drawline

# 前言

目前Unity官方提供的UI扩展包中包含了UILineRenderer组件,本篇实现与UILineRenderer实现一致,主要讲解其基本使用与实现过程。不想看的同学可以直接下载官方扩展包。

代码库:unity-ui-extensionsicon-default.png?t=L892https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/wiki/browse/

# 正文

canvas上的渲染均通过CanvasRenderer,unity-ui扩展包实现了一套ui基础类UIPrimitiveBase,我们要讲的UILineRenderer正是继承于UIPrimitiveBase实现的Canvas上画线功能。

 

 添加UILineRenderer组件,设置Points值即可根据输入点绘制出线段。Points数组为UILineRenderer子级UI坐标点。下面是UILineRenderer的一个使用范例。

  1. public void DrawLines(List<Vector3> worldPositionList)
  2. {
  3. //ui摄像机 为了将世界坐标转换为本组件下坐标。
  4. //注意:这个摄像机与worldPositionList相关联,本例中worldPositionList是被ui摄像机渲染的对象的世界坐标。
  5. var uiCamera = GameObject.FindWithTag(Tag.GuiCamera).GetComponent<Camera>();
  6. //UILineRenderer组件
  7. var lineRenderer = GetComponent<UILineRenderer>();
  8. //与LineRenderer同级RectTransform
  9. var myRectTransform = GetComponent<RectTransform>();
  10. var pointsList = new List<Vector2>();
  11. for (var i = 0; i < worldPositionList.Count; i++)
  12. {
  13. var screenPoint = uiCamera.WorldToScreenPoint(worldPositionList[i]);
  14. RectTransformUtility.ScreenPointToLocalPointInRectangle(myRectTransform, screenPoint, uiCamera,
  15. out var localPoint);
  16. pointsList.Add(localPoint);
  17. }
  18. //设置坐标,触发UILineRenderer上的CanvasRenderer重绘
  19. lineRenderer.Points = pointsList.ToArray();
  20. }

## 发生了什么?

### 生成线段

首先本文会忽略不详细讲解Line List,Bezier Mode这两个参数带来的变化,Line List将点列表按照两两成对的方式对待点列表,Bezier Mode会在处理点之前先将点按照贝塞尔曲线方式处理点,形成曲线。我们这里主要讲解一个基础的流程。

在我们设置了坐标点数组后,UILineRenderer调用SetAllDirty方法后回调到我们今天要讲的重点方法OnPopulateMesh,此方法会传入一个VertextHelper类,我们将需要设置的顶点数据塞进这个vh中即可。

 假设有3个点P1,P2,P3,传递给UILineRenderer后,根据这3个点来绘制连线。如果线只有一个像素就很简单,依次将3个点连成线即可,但是UILineRenderer有lineThickness属性,可以设置线的宽度。

  1. private UIVertex[] CreateLineSegment(Vector2 start, Vector2 end, SegmentType type, UIVertex[] previousVert = null)
  2. {
  3. Vector2 offset = new Vector2((start.y - end.y), end.x - start.x).normalized * lineThickness / 2;
  4. Vector2 v1 = Vector2.zero;
  5. Vector2 v2 = Vector2.zero;
  6. if (previousVert != null) {
  7. v1 = new Vector2(previousVert[3].position.x, previousVert[3].position.y);
  8. v2 = new Vector2(previousVert[2].position.x, previousVert[2].position.y);
  9. } else {
  10. v1 = start - offset;
  11. v2 = start + offset;
  12. }
  13. var v3 = end + offset;
  14. var v4 = end - offset;
  15. //Return the VDO with the correct uvs
  16. switch (type)
  17. {
  18. case SegmentType.Start:
  19. return SetVbo(new[] { v1, v2, v3, v4 }, startUvs);
  20. case SegmentType.End:
  21. return SetVbo(new[] { v1, v2, v3, v4 }, endUvs);
  22. case SegmentType.Full:
  23. return SetVbo(new[] { v1, v2, v3, v4 }, fullUvs);
  24. default:
  25. return SetVbo(new[] { v1, v2, v3, v4 }, middleUvs);
  26. }
  27. }

 遍历点数组,每两个点形成一条直线,创建一个由四边形来表示一条线段。这个函数比较难理解地方是如何根据点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个三角形顶点顺序如下:

  1. public void AddUIVertexQuad(UIVertex[] verts)
  2. {
  3. int currentVertCount = this.currentVertCount;
  4. for (int index = 0; index < 4; ++index)
  5. this.AddVert(verts[index].position, verts[index].color, verts[index].uv0, verts[index].uv1, verts[index].normal, verts[index].tangent);
  6. this.AddTriangle(currentVertCount, currentVertCount + 1, currentVertCount + 2);
  7. this.AddTriangle(currentVertCount + 2, currentVertCount + 3, currentVertCount);
  8. }

### 处理接缝

所有点处理好后,如果直接绘制线段,我们会发现如下现象:

在P2点会存在接缝的情况,下一步就是处理接缝的逻辑

  1. // Add the line segments to the vertex helper, creating any joins as needed
  2. for (var i = 0; i < segments.Count; i++) {
  3. if (!lineList && i < segments.Count - 1) {
  4. var vec1 = segments [i] [1].position - segments [i] [2].position;
  5. var vec2 = segments [i + 1] [2].position - segments [i + 1] [1].position;
  6. var angle = Vector2.Angle (vec1, vec2) * Mathf.Deg2Rad;
  7. // Positive sign means the line is turning in a 'clockwise' direction
  8. var sign = Mathf.Sign (Vector3.Cross (vec1.normalized, vec2.normalized).z);
  9. // Calculate the miter point
  10. var miterDistance = lineThickness / (2 * Mathf.Tan (angle / 2));
  11. var miterPointA = segments [i] [2].position - vec1.normalized * miterDistance * sign;
  12. var miterPointB = segments [i] [3].position + vec1.normalized * miterDistance * sign;
  13. var joinType = LineJoins;
  14. if (joinType == JoinType.Miter) {
  15. // Make sure we can make a miter join without too many artifacts.
  16. if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_MITER_JOIN) {
  17. segments [i] [2].position = miterPointA;
  18. segments [i] [3].position = miterPointB;
  19. segments [i + 1] [0].position = miterPointB;
  20. segments [i + 1] [1].position = miterPointA;
  21. } else {
  22. joinType = JoinType.Bevel;
  23. }
  24. }
  25. if (joinType == JoinType.Bevel) {
  26. if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_BEVEL_NICE_JOIN) {
  27. if (sign < 0) {
  28. segments [i] [2].position = miterPointA;
  29. segments [i + 1] [1].position = miterPointA;
  30. } else {
  31. segments [i] [3].position = miterPointB;
  32. segments [i + 1] [0].position = miterPointB;
  33. }
  34. }
  35. var join = new UIVertex[] { segments [i] [2], segments [i] [3], segments [i + 1] [0], segments [i + 1] [1] };
  36. vh.AddUIVertexQuad (join);
  37. }
  38. }
  39. vh.AddUIVertexQuad (segments [i]);
  40. }

 处理的方式如下图所示:

 代码中的miterDistance即图中H->P2P1的距离,首先求出vec1与vec2如图所示得到角度,根据三角函数就求得距离。两个向量叉乘判断方向来加或者减这个偏移量。

走完这一步就完成了点的处理。

 

 # 结语

这个组件用起来其实很简单,设置点数组即可,主要对这个偏移计算比较感兴趣,就写成了文章记录一下,用到了旋转矩阵,叉乘判断方向这些数学基础运用到实际开发中。

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/881854
推荐阅读
相关标签
  

闽ICP备14008679号