unity UGUI 鼠标画线

cs 复制代码
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

相关推荐
HWL567918 小时前
显示器缩放和更改分辨率的区别
前端·css·vue.js·计算机外设·html5
Jwest202118 小时前
工业显示器在智能寄件机中的应用
计算机外设
孟无岐19 小时前
【Laya】LocalStorage 本地存储
typescript·游戏引擎·游戏程序·laya
爱喝矿泉水的猛男1 天前
如何测试鼠标是否支持mac mouse fix
macos·计算机外设
妙为1 天前
unreal engine5角色把敌人 “挤飞”
游戏引擎·虚幻·ue·unrealengine5
4Forsee1 天前
【增强现实】快速上手 Vuforia Unity Android AR 应用开发
android·unity·ar
两水先木示1 天前
【Unity】对指定物体进行描边——模板测试法
unity·游戏引擎·shader·外描边
Miss_SQ1 天前
实现Unity录音、百度云语音转文字
unity·语音识别
CreasyChan1 天前
unity 对象池实测可用
unity·c#
weixin_424294671 天前
Unity项目的Artifacts文件夹过大怎么解决?
unity·游戏引擎