unity UGUI 鼠标画线

发布于:2025-09-12 ⋅ 阅读:(20) ⋅ 点赞:(0)
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);
            }
        }

        // 添加顶点到VertexHelper
        int 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


网站公告

今日签到

点亮在社区的每一天
去签到