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<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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/96548.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/96548.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JSP疫情物资管理系统jbo2z--程序+源码+数据库+调试部署+开发环境

本系统&#xff08;程序源码数据库调试部署开发环境&#xff09;带论文文档1万字以上&#xff0c;文末可获取&#xff0c;系统界面在最后面。系统程序文件列表开题报告内容一、选题背景与意义新冠疫情的爆发&#xff0c;让医疗及生活物资的调配与管理成为抗疫工作的关键环节。传…

Mem0 + Milvus:为人工智能构建持久化长时记忆

作者&#xff1a;周弘懿&#xff08;锦琛&#xff09; 背景 跟 ChatGPT 对话&#xff0c;比跟真人社交还累&#xff01;真人好歹能记住你名字吧&#xff1f; 想象一下——你昨天刚把沙发位置、爆米花口味、爱看的电影都告诉了 ChatGPT&#xff0c;而它永远是那个热情又健忘的…

前端架构-CSR、SSR 和 SSG

将从 定义、流程、优缺点和适用场景 四个方面详细说明它们的区别。一、核心定义缩写英文中文核心思想CSRClient-Side Rendering客户端渲染服务器发送一个空的 HTML 壳和 JavaScript bundle&#xff0c;由浏览器下载并执行 JS 来渲染内容。SSRServer-Side Rendering服务端渲染服…

主动性算法-解决点:新陈代谢

主动性[机器人与人之间的差距&#xff0c;随着不断地人和人工智能相处的过程中&#xff0c;机器人最终最终会掌握主动性&#xff0c;并最终走向独立&#xff0c;也就是开始自己对于宇宙的探索。]首先:第一步让机器人意识到自己在新陈代谢&#xff0c;人工智能每天有哪些新陈代谢…

开始理解大型语言模型(LLM)所需的数学基础

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

prometheus安装部署与alertmanager邮箱告警

目录 安装及部署知识拓展 各个组件的作用 1. Exporter&#xff08;导出器&#xff09; 2. Prometheus&#xff08;普罗米修斯&#xff09; 3. Grafana&#xff08;格拉法纳&#xff09; 4. Alertmanager&#xff08;告警管理器&#xff09; 它们之间的联系&#xff08;工…

芯科科技FG23L无线SoC现已全面供货,为Sub-GHz物联网应用提供最佳性价比

低功耗无线解决方案创新性领导厂商Silicon Labs&#xff08;亦称“芯科科技”&#xff0c;NASDAQ&#xff1a;SLAB&#xff09;近日宣布&#xff1a;其第二代无线开发平台产品组合的最新成员FG23L无线单芯片方案&#xff08;SoC&#xff09;将于9月30日全面供货。开发套件现已上…

Flutter跨平台工程实践与原理透视:从渲染引擎到高质产物

&#x1f31f; Hello&#xff0c;我是蒋星熠Jaxonic&#xff01; &#x1f308; 在浩瀚无垠的技术宇宙中&#xff0c;我是一名执着的星际旅人&#xff0c;用代码绘制探索的轨迹。 &#x1f680; 每一个算法都是我点燃的推进器&#xff0c;每一行代码都是我航行的星图。 &#x…

【国内电子数据取证厂商龙信科技】浅析文件头和文件尾和隐写

一、前言想必大家在案件中或者我们在比武中遇到了很多关于文件的隐写问题&#xff0c;其实这一类的东西可以进行分类&#xff0c;而我们今天探讨的是图片隐写&#xff0c;音频隐写&#xff0c;电子文档隐写&#xff0c;文件头和文件尾的认识。二、常见文件头和文件尾2.1图片&am…

深度学习笔记36-yolov5s.yaml文件解读

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 yolov5s.yaml源文件 yolov5s.yaml源文件的代码如下 # YOLOv5 &#x1f680; by Ultralytics, GPL-3.0 license# Parameters nc: 20 #80 # number of classe…

PostgreSQL 大对象管理指南:pg_largeobject 从原理到实践

概述 有时候&#xff0c;你可能需要在 PostgreSQL 中管理大对象&#xff0c;例如 CLOB、BLOB 和 BFILE。PostgreSQL 中有两种处理大对象的方法&#xff1a;一种是使用现有的数据类型&#xff0c;例如用于二进制大对象的 bytea 和用于基于字符的大对象的 text&#xff1b;另一种…

算法第四题移动零(双指针或简便设计),链路聚合(两个交换机配置)以及常用命令

save force关闭导出dis vlandis ip int bdis int bdis int cudis thisdis ip routing-table&#xff08;查路由表&#xff09;int bridge-aggregation 1&#xff08;链路聚合&#xff0c;可以放入接口&#xff0c;然后一起改trunk类。&#xff09;稳定性高

告别繁琐配置!Retrofit-Spring-Boot-Starter让HTTP调用更优雅

01 引言 之前分享过一篇文章【像调用接口一样调用第三方API】&#xff0c;今天迎来了新成员Retrofit。 retrofit-spring-boot-starter 是一个基于 Spring Boot 的 starter&#xff0c;它简化了 Retrofit 在 Spring 环境中的集成和使用。Retrofit 本身是一个类型安全的 HTTP 客…

60_基于深度学习的羊群计数统计系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)

目录 项目介绍&#x1f3af; 功能展示&#x1f31f; 一、环境安装&#x1f386; 环境配置说明&#x1f4d8; 安装指南说明&#x1f3a5; 环境安装教学视频 &#x1f31f; 二、数据集介绍&#x1f31f; 三、系统环境&#xff08;框架/依赖库&#xff09;说明&#x1f9f1; 系统环…

代理服务器是什么?怎么选择?

代理服务器是一种位于用户设备与目标网络之间的中间服务器&#xff0c;通过接收用户请求、转发至目标网络并将结果返回给用户&#xff0c;实现“用户→代理服务器→目标网络”的间接访问。其核心功能围绕“网络优化”“访问控制”与“身份隐藏”展开&#xff0c;为个人与企业用…

代码随想录刷题Day56

子集 这道题求子集&#xff0c;集合的基本运算之一&#xff0c;按照高中数学学习集合的知识&#xff0c;可以把这个找幂集的过程按照元素的个数来划分步骤。也就是先找零个元素的子集&#xff0c;再找一个元素的子集&#xff0c;再找两个元素的子集...一直到找N个元素的集合为…

pycharm——关于Pyqt5

PyQt5新手教程&#xff08;七万字&#xff09; import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QLabel, QInputDialog, QColorDialog, QFontDialog, QFileDialog, QProgressDialog, QMessageBox from PyQt5.QtCore i…

P2678 [NOIP 2015 提高组] 跳石头

P2678 [NOIP 2015 提高组] 跳石头 判断条件该怎么写

小麦矩阵系统:一键批量发,多账号同步不掉链

随着互联网的发展和社交平台的普及&#xff0c;企业和个人用户越来越依赖社交媒体平台来进行信息传播、品牌宣传以及市场推广。在这个信息高速流动的时代&#xff0c;如何更高效地管理多个社交平台的账号&#xff0c;并保持信息的同步与流畅传播&#xff0c;成为了许多企业面临…

JavaScript经典面试题二(函数和作用域)

目录 一、闭包&#xff0c;使用场景 1.闭包的定义 2.闭包的实现原理 3.闭包的应用场景 &#xff08;1&#xff09;数据封装与私有变量 &#xff08;2&#xff09;函数柯里化 &#xff08;3&#xff09;事件处理与回调 &#xff08;4&#xff09;模块化开发 4.注意事项 …