公司动态

在Unity UI中实现UILineRenderer组件绘制线条

📅 2026/8/27 13:38:01
在Unity UI中实现UILineRenderer组件绘制线条
背景介绍在Unity的UI系统中绘制线条并不像在3D世界中那样直观(使用Unity自带的LineRender组件在UI中连线并不方便,它在三维中更合适)。没有内置的工具来处理这种需求(当然可是使用canvas的第二种模式渲染3维物体到ui然后使用LineRender,这增加了一点复杂性但也不失为一种好办法。如果你只希望在常规UI元素之间绘制连接线例如在UI上连接不同的图标或控件需要自己编写逻辑。为了满足这种需求我封装了一个名为UILineRenderer的组件。该组件能够处理UI中的线条绘制并且可以适应UI元素复杂的父子关系提供线条宽度、颜色设置等功能非常适合在UI界面中使用。功能简介UILineRenderer的主要功能包括连接两个UI元素(当然你也可以根据我的思路拓展更多).支持自定义线条宽度与颜色能够根据UI元素的坐标变化实时更新线条通过鼠标位置动态调整线条的终点位置处理UI元素复杂的父子层级关系目前仅支持连接两个Ui元素(有一个中间状态就是只添加了一个UI元素,你可以设置鼠标作为第二个临时点,然后在合适的时候设置第二个UI元素)但是我重写的OnPopulateMesh逻辑实际上是支持连接多个点的,你可以修改这个组件实现你的需求.先看效果可以连接两个UI元素在设置了第一个UI元素的时候,可以设置鼠标位置,从而实现始终连接鼠标(这里截图导致鼠标没了)源码using UnityEngine.UI; using UnityEngine; using System.Collections.Generic; [RequireComponent(typeof(CanvasRenderer))]//需要该组件才能生效 public class UILineRenderer : Graphic { private ListVector2 points new ListVector2(); // 用于存储线条的点 [SerializeField] private float lineWidth 5f; // 线条宽度 [SerializeField] private Color lineColor Color.white; // 默认线条颜色 // 每次需要重新绘制UI时调用 protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); // 清空当前顶点数据 // 如果没有足够的点则不绘制任何东西 if (points null || points.Count 2) return; // 遍历每个点创建线段 for (int i 0; i points.Count - 1; i) { Vector2 start points[i]; Vector2 end points[i 1]; // 计算垂直方向的法线使线条有宽度 Vector2 direction (end - start).normalized; Vector2 perpendicular new Vector2(-direction.y, direction.x) * lineWidth / 2f; // 四个顶点左下、左上、右上、右下 UIVertex vertex UIVertex.simpleVert; vertex.color lineColor; // 定义颜色 // 左下 vertex.position new Vector3(start.x - perpendicular.x, start.y - perpendicular.y); vh.AddVert(vertex); // 左上 vertex.position new Vector3(start.x perpendicular.x, start.y perpendicular.y); vh.AddVert(vertex); // 右上 vertex.position new Vector3(end.x perpendicular.x, end.y perpendicular.y); vh.AddVert(vertex); // 右下 vertex.position new Vector3(end.x - perpendicular.x, end.y - perpendicular.y); vh.AddVert(vertex); // 添加两个三角形来组成矩形线条 int index vh.currentVertCount; vh.AddTriangle(index - 4, index - 3, index - 2); vh.AddTriangle(index - 4, index - 2, index - 1); } } /// summary /// 设置一个Ui元素 /// 为什么要转换坐标?因为UI元素极可能不在同一个父物体下,存在错综复杂的父子关系 /// 先获取UiElement世界坐标系转屏幕坐标系再转到此脚本所在的Ui坐标系 /// /summary /// param nameuiElement/param public void AppendUIElement(RectTransform uiElement) { Vector2 localPoint; RectTransformUtility.ScreenPointToLocalPointInRectangle( rectTransform, // 当前 UILineRenderer 的 RectTransform RectTransformUtility.WorldToScreenPoint(null, uiElement.position), // UI 元素的世界坐标转换为屏幕坐标 null, out localPoint // 输出的局部坐标 ); // 如果已经有两个点则移除第二个点以保持绘制最新线条 if (points.Count 2) { points.RemoveAt(1); } // 添加转换后的局部坐标到点列表中 points.Add(localPoint); // 标记为需要重新绘制 SetVerticesDirty(); } /// summary /// 设置鼠标位置为第二个点,此时鼠标和第一个UiElement可以构成一条线 /// /summary /// param namepoint/param public void SetMouse() { if (points.Count2) { points.RemoveAt(1); } var mousePostion Input.mousePosition; RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, mousePostion, null, out Vector2 point); points.Add(point); SetVerticesDirty(); } /// summary /// 设置线的颜色 /// /summary /// param namenewColor/param public void SetLineColor(Color newColor) { lineColor newColor; SetVerticesDirty(); } /// summary /// 设置线的宽带 /// /summary /// param namewidth/param public void SetWidth(float width) { lineWidth width; SetVerticesDirty(); } /// summary /// 重置组件 /// /summary public void ResetSelf() { points.Clear(); lineColor Color.white; lineWidth 5f; SetVerticesDirty(); } }示例using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class Test : MonoBehaviour { public UILineRenderer t; public Image img1;//拖拽 public Image img2; private bool keyA false; // Start is called before the first frame update void Start() { t.AppendUIElement(img1.rectTransform); } // Update is called once per frame void Update() { if (keyAfalse) { t.SetMouse(); } if (Input.GetKeyDown(KeyCode.A)) { keyA true; t.AppendUIElement(img2.rectTransform); } } }示例结构图挂载了UILineRender的组件和其他UI元素一样,所以要放在下面