using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using UnityEngine.UI;
/*
使用方法:
在場景中新建一個空的 GameObject(右鍵 -> UI -> 空對象,或直接創建空對象后添加 RectTransform 組件)
給這個新對象掛載 LineDrawer 腳本(此時會自動添加 CanvasRenderer 組件,無需手動添加 Image)
調整該對象的 RectTransform 大小和位置,使其覆蓋你需要繪制的區域
*/
[RequireComponent(typeof(CanvasRenderer))]
public class LineDrawer : MaskableGraphic, IPointerDownHandler, IDragHandler, IPointerUpHandler
{[Header("線段的寬度")][Tooltip("線段的寬度,單位為像素。值越大,繪制的線條越粗。建議取值范圍:1-20")][SerializeField] private float lineWidth = 5f;[Header("線段的填充顏色")][Tooltip("通過調整RGBA值可以改變線條的顏色和透明度")][SerializeField] private Color lineColor = Color.black;[Header("最小距離閾值")][Tooltip("鼠標拖動時添加新點的最小距離閾值(像素)。當鼠標移動距離超過此值時才會添加新點,值越小線條越精確但點數量越多,過小將影響性能")][SerializeField] private float minSegmentDistance = 5f;[Header("平滑處理")][Tooltip("是否啟用貝塞爾曲線平滑處理。勾選后線條會更流暢自然,不勾選則為直線段連接")][SerializeField] private bool drawSmoothLines = true;[Header("平滑精細度")][Tooltip("平滑線條的精細程度,控制貝塞爾曲線的分段數量。值越大曲線越平滑但性能消耗增加,建議取值范圍:3-10,僅在啟用平滑線條時生效")][SerializeField] private int smoothness = 5;[Header("多線段模式")][Tooltip("勾選后可以繪制任意數量的獨立線段,它們會同時顯示;取消勾選則每次鼠標按下會清除之前所有線條,只顯示當前正在繪制的單一線段")][SerializeField] private bool multiLineMode = true;// 線段類,存儲一條線段的所有點、顏色和粗細private class Line{public List<Vector2> points = new List<Vector2>();public Color color;public float width;}private Line currentLine = null;private List<Line> allLines = new List<Line>();private bool isDrawing = false;// 重寫顏色屬性public override Color color{get => lineColor;set{lineColor = value;SetVerticesDirty();}}// 線段粗細屬性public float LineWidth{get => lineWidth;set{lineWidth = Mathf.Max(0.1f, value);// 更新當前正在繪制的線段(如果存在)if (isDrawing && currentLine != null){currentLine.width = lineWidth;SetVerticesDirty();}}}protected override void OnPopulateMesh(VertexHelper vh){vh.Clear();// 繪制所有已完成的線段foreach (var line in allLines){if (line.points.Count < 2) continue;DrawLine(vh, line);}// 繪制當前正在繪制的線段if (currentLine != null && currentLine.points.Count >= 2){DrawLine(vh, currentLine);}}// 繪制單條線段private void DrawLine(VertexHelper vh, Line line){List<Vector2> pointsToDraw = line.points;// 如果需要平滑線段,應用貝塞爾曲線if (drawSmoothLines && line.points.Count > 2){pointsToDraw = ApplySmoothing(line.points);}// 繪制線段DrawLineSegments(vh, pointsToDraw, line.color, line.width);}// 應用平滑處理private List<Vector2> ApplySmoothing(List<Vector2> points){List<Vector2> smoothedPoints = new List<Vector2>();for (int i = 0; i < points.Count - 1; i++){Vector2 start = points[i];Vector2 end = points[i + 1];Vector2 control1 = i > 0 ? points[i] : start;Vector2 control2 = i < points.Count - 2 ? points[i + 1] : end;for (int j = 0; j <= smoothness; j++){float t = j / (float)smoothness;smoothedPoints.Add(BezierCurve(start, control1, control2, end, t));}}return smoothedPoints;}// 繪制線段(帶獨立粗細參數)private void DrawLineSegments(VertexHelper vh, List<Vector2> points, Color color, float lineWidth){int count = points.Count;if (count < 2) return;float halfWidth = lineWidth * 0.5f;// 存儲所有計算出的頂點List<Vector2> vertices = new List<Vector2>();for (int i = 0; i < count; i++){if (i == 0){// 處理第一個點AddFirstPointVertices(vertices, points[i], points[i + 1], halfWidth);}else if (i == count - 1){// 處理最后一個點AddLastPointVertices(vertices, points[i - 1], points[i], halfWidth);}else{// 處理中間點AddMidPointVertices(vertices, points[i - 1], points[i], points[i + 1], halfWidth);}}// 添加頂點到VertexHelperint startVertexIndex = vh.currentVertCount;for (int i = 0; i < vertices.Count; i++){// 計算UV,這里簡單處理為0-1范圍float uvX = Mathf.InverseLerp(0, rectTransform.rect.width, vertices[i].x);float uvY = Mathf.InverseLerp(0, rectTransform.rect.height, vertices[i].y);vh.AddVert(vertices[i], color, new Vector2(uvX, uvY));}// 添加三角形for (int i = 0; i < vertices.Count - 2; i += 2){vh.AddTriangle(startVertexIndex + i, startVertexIndex + i + 1, startVertexIndex + i + 3);vh.AddTriangle(startVertexIndex + i, startVertexIndex + i + 3, startVertexIndex + i + 2);}}// 計算貝塞爾曲線上的點private Vector2 BezierCurve(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t){float u = 1 - t;float tt = t * t;float uu = u * u;float uuu = uu * u;float ttt = tt * t;Vector2 p = uuu * p0;p += 3 * uu * t * p1;p += 3 * u * tt * p2;p += ttt * p3;return p;}// 添加第一個點的頂點(帶粗細參數)private void AddFirstPointVertices(List<Vector2> vertices, Vector2 start, Vector2 next, float halfWidth){Vector2 dir = next - start;Vector2 normal = new Vector2(-dir.y, dir.x).normalized;vertices.Add(start + normal * halfWidth);vertices.Add(start - normal * halfWidth);}// 添加最后一個點的頂點(帶粗細參數)private void AddLastPointVertices(List<Vector2> vertices, Vector2 prev, Vector2 end, float halfWidth){Vector2 dir = end - prev;Vector2 normal = new Vector2(-dir.y, dir.x).normalized;vertices.Add(end + normal * halfWidth);vertices.Add(end - normal * halfWidth);}// 添加中間點的頂點(帶粗細參數)private void AddMidPointVertices(List<Vector2> vertices, Vector2 prev, Vector2 current, Vector2 next, float halfWidth){Vector2 dir1 = current - prev;Vector2 dir2 = next - current;Vector2 normal1 = new Vector2(-dir1.y, dir1.x).normalized;Vector2 normal2 = new Vector2(-dir2.y, dir2.x).normalized;// 計算平均法線Vector2 avgNormal = (normal1 + normal2).normalized;// 計算角度float angle = Vector2.Angle(normal1, normal2) * Mathf.Deg2Rad * 0.5f;float radiusMultiplier = 1 / Mathf.Cos(angle);vertices.Add(current + avgNormal * halfWidth * radiusMultiplier);vertices.Add(current - avgNormal * halfWidth * radiusMultiplier);}// 鼠標按下開始畫線public void OnPointerDown(PointerEventData eventData){if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out Vector2 localPos)){// 如果不是多線段模式,清除所有線段if (!multiLineMode){allLines.Clear();}// 開始新的線段currentLine = new Line();currentLine.points.Add(localPos);currentLine.color = lineColor; // 使用當前顏色currentLine.width = lineWidth; // 使用當前粗細isDrawing = true;SetVerticesDirty();}}// 鼠標拖動時添加點public void OnDrag(PointerEventData eventData){if (!isDrawing || currentLine == null) return;if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out Vector2 localPos)){// 只在距離足夠遠時添加新點if (Vector2.Distance(currentLine.points[currentLine.points.Count - 1], localPos) > minSegmentDistance){currentLine.points.Add(localPos);SetVerticesDirty();}}}// 鼠標抬起結束畫線public void OnPointerUp(PointerEventData eventData){if (!isDrawing || currentLine == null) return;isDrawing = false;// 確保最后添加終點if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, eventData.position, eventData.pressEventCamera, out Vector2 localPos)){if (currentLine.points.Count == 1 || Vector2.Distance(currentLine.points[currentLine.points.Count - 1], localPos) > minSegmentDistance * 0.5f){currentLine.points.Add(localPos);}}// 如果當前線段有足夠的點,添加到所有線段列表中if (currentLine.points.Count >= 2){allLines.Add(currentLine);}// 清空當前線段currentLine = null;SetVerticesDirty();}// 清除所有線條public void ClearAllLines(){allLines.Clear();currentLine = null;SetVerticesDirty();}// 設置特定線段的粗細public void SetLineWidth(int lineIndex, float width){if (lineIndex >= 0 && lineIndex < allLines.Count){allLines[lineIndex].width = Mathf.Max(0.1f, width);SetVerticesDirty();}}// 獲取特定線段的粗細public float GetLineWidth(int lineIndex){if (lineIndex >= 0 && lineIndex < allLines.Count){return allLines[lineIndex].width;}return lineWidth;}// 獲取線段數量public int GetLineCount(){return allLines.Count;}// 重寫材質獲取,使用默認UI材質public override Material material{get => defaultMaterial;set => base.material = value;}
}
參考:
https://blog.csdn.net/sdhexu/article/details/126593171?spm=1001.2014.3001.5502