unity UGUI 鼠标画线 - 详解

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 points = new List();
public Color color;
public float width;
}
private Line currentLine = null;
private List allLines = new List();
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)
{
DrawLine(vh, currentLine);
}
}
// 绘制单条线段
private void DrawLine(VertexHelper vh, Line line)
{
List pointsToDraw = line.points;
// 如果需要平滑线段,应用贝塞尔曲线
if (drawSmoothLines && line.points.Count > 2)
{
pointsToDraw = ApplySmoothing(line.points);
}
// 绘制线段
DrawLineSegments(vh, pointsToDraw, line.color, line.width);
}
// 应用平滑处理
private List ApplySmoothing(List points)
{
List smoothedPoints = new List();
for (int i = 0; i  0 ? points[i] : start;
Vector2 control2 = i  points, Color color, float lineWidth)
{
int count = points.Count;
if (count  vertices = new List();
for (int i = 0; i  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 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 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 = 0 && lineIndex  defaultMaterial;
set => base.material = value;
}
}

参考:

https://blog.csdn.net/sdhexu/article/details/126593171?spm=1001.2014.3001.5502

posted @ 2025-09-13 13:27  yfceshi  阅读(15)  评论(0)    收藏  举报