using System.Collections.Generic; using UnityEngine; using UnityEngine.Serialization; using UnityEngine.UI; #if UNITY_EDITOR using UnityEditor; #endif namespace XericLibrary.Runtime.UIGraph { /// /// UI上线条绘制组件 /// 支持设置线条厚度、居中显示,并为线段之间添加斜角边缘 /// 继承自 MaskableGraphic,可与 UI 遮罩系统配合使用 /// [AddComponentMenu("Xeric Library/UI/UILineRenderer", 15)] public class UILineRenderer : XericRendererComponent { // todo 根据优弧劣弧绘制线段,这么说可能不太准确,但我需要在90度以内绘制漂亮的转角,大于90度则用中心圆代替 #region 字段属性 /// /// 线条的顶点数组,至少需要 2 个点才能绘制线条 /// 点的坐标基于本地空间,中心点由 center 属性控制 /// public List points = new List(); private Vector2[] _points; /// /// 线路宽度 /// public float thickness = 10f; /// /// 居中对齐 /// public bool center = true; /// /// 循环线路 /// public bool cycleLoop = false; /// /// 启用额外绘制的矢量箭头 /// public bool enableVectorArrow = false; /// /// 矢量箭头尺寸(宽长) /// public Vector2 vectorArrowSize = Vector2.one; public bool absVectorProgress = true; public float vectorProgress = 15f; public float vectorArrowProgress = 0.5f; public bool reverseVector = false; /// /// 是/否启用倒角功能 /// public bool useChamfer = false; /// /// 倒角细分数量,控制圆弧的平滑度 /// public int chamferSegments = 4; /// /// 内圆弧半径,当大于0时启用内圆弧 /// public float innerRadius = 0f; #endregion /// /// 添加顶点 /// /// /// 合并弧度,指定一个弧度,在添加顶点时,自动合并之前的顶点为直线 public void AddPoint(Vector2 point, float mergingRadius = -1) { if (mergingRadius > 0 && points.Count >= 2) { var a = points[^1]; var b = Vector3.Dot((a - points[^2]).normalized, (a - point).normalized); if (b < mergingRadius) points[^1] = point; return; } points.Add(point); Refresh(); } public void RemovePoint(int index) { points.RemoveAt(index); Refresh(); } public void CleanPoint(Vector2 point) { points.Clear(); Refresh(); } /// /// 生成线条网格 /// /// 用于构建网格的 VertexHelper 对象 protected override void OnPopulateMesh(VertexHelper vh) { // 清空现有的网格数据 vh.Clear(); var offset = XericRendererUtils.SwitchCenterOffset(center, rectTransform.sizeDelta); _points = points.ToArray(); // 切换线路绘制模式 if (useChamfer) { vh.PopulateLineByPointsArray_CFAL(_points, thickness, cycleLoop, color, offset, chamferSegments, innerRadius); } else { vh.PopulateLineByPointsArray_DSL5(_points, thickness, cycleLoop, color, offset, enableVectorArrow, color, vectorArrowSize, vectorProgress, vectorArrowProgress, absVectorProgress, reverseVector); } return; // 至少需要 2 个点才能绘制线条 if (_points.Length < 2) return; // 计算线路总长度 float totalLength = _points.CalculateTotalLength(); float currentLength = 0f; // 遍历所有点,创建线段并添加到网格中 for (int i = 0; i < _points.Length-1; i++) { // 使用静态工具方法创建当前点与下一个点之间的线段 // 传递当前累积长度和总长度用于UV坐标计算 vh.CreateLineSegment_CFAL(_points[i], _points[i+1], i > 0 ? Mathf.Acos(Vector2.Dot((_points[i] - _points[i -1]).normalized, (_points[i + 1] - _points[i]).normalized)) * Mathf.Rad2Deg : 0, i < _points.Length-2 ? Mathf.Acos(Vector2.Dot((_points[i] - _points[i + 1]).normalized, (_points[i + 1] - _points[i +2]).normalized)) * Mathf.Rad2Deg : 0, thickness, center, rectTransform.sizeDelta, color, currentLength, totalLength); // 更新累积长度 currentLength += Vector2.Distance(_points[i], _points[i+1]); // 计算当前线段的顶点索引(每个线段包含 5 个顶点) int index = i * 5; // 添加线段的两个三角形(构成矩形线段) vh.AddTriangle(index, index+1, index+3); vh.AddTriangle(index+3, index+2, index); // 为线段之间添加斜角边缘 // 使用上一个线段的终点和当前线段的起点创建过渡三角形 if (i != 0) { vh.AddTriangle(index, index-1, index-3); vh.AddTriangle(index+1, index-1, index-2); } } } #if UNITY_EDITOR [MenuItem("GameObject/Xeric Library/UI/UILineRenderer", false, 10)] private static void CreateGameObject() { GameObject newObject = new GameObject(nameof(UILineRenderer), typeof(UILineRenderer)); if (Selection.activeGameObject != null) newObject.transform.SetParent(Selection.activeGameObject.transform); Undo.RegisterCreatedObjectUndo(newObject, $"Create {nameof(UILineRenderer)}"); Selection.activeObject = newObject; } #endif } }