返回首页DA系统C#IDE文件同步服务屏保 今天是: 2026-05-05    "立夏"  夏季的第一个节气,表示盛夏时节的正式开始

搜索
热搜: linux 技术
Hi~登录注册
查看: 1690|回复: 0

[转载] 【转载】在Unity UI中实现UILineRenderer组件绘制线条

[复制链接]
发表于 2025-1-15 17:59:55 | 显示全部楼层 |阅读模式
在Unity UI中实现UILineRenderer组件绘制线条


背景介绍
        在Unity的UI系统中,绘制线条并不像在3D世界中那样直观(使用Unity自带的LineRender组件在UI中连线并不方便,它在三维中更合适)。没有内置的工具来处理这种需求(当然可是使用canvas的第二种模式渲染3维物体到ui,然后使用LineRender,这增加了一点复杂性,但也不失为一种好办法)。如果你只希望在常规UI元素之间绘制连接线(例如在UI上连接不同的图标或控件),需要自己编写逻辑。

        为了满足这种需求,我封装了一个名为UILineRenderer的组件。该组件能够处理UI中的线条绘制,并且可以适应UI元素复杂的父子关系,提供线条宽度、颜色设置等功能,非常适合在UI界面中使用。


功能简介
UILineRenderer的主要功能包括:



连接两个UI元素(当然你也可以根据我的思路拓展更多).

支持自定义线条宽度与颜色

能够根据UI元素的坐标变化实时更新线条

通过鼠标位置动态调整线条的终点位置

处理UI元素复杂的父子层级关系

目前仅支持连接两个Ui元素(有一个中间状态就是只添加了一个UI元素,你可以设置鼠标作为第二个临时点,然后在合适的时候设置第二个UI元素)

但是我重写的OnPopulateMesh逻辑实际上是支持连接多个点的,你可以修改这个组件实现你的需求.


先看效果
可以连接两个UI元素




在设置了第一个UI元素的时候,可以设置鼠标位置,从而实现始终连接鼠标(这里截图导致鼠标没了)



gitee示例
连线模块小功能: 这是一个连线功能的小插件


绘制线段核心源码:

using UnityEngine.UI;
using UnityEngine;
using System.Collections.Generic;

[RequireComponent(typeof(CanvasRenderer))]//需要该组件才能生效
public class NSZXTPoint1_script : Graphic
{
    private List<Vector2> points = new List<Vector2>(); // 用于存储线条的点
    [SerializeField] private float lineWidth = 5f; // 线条宽度
    [SerializeField] private Color lineColor = Color.white; // 默认线条颜色

    // 每次需要重新绘制UI时调用
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh.Clear(); // 清空当前顶点数据

        // 如果没有足够的点,则不绘制任何东西
        //points.Count可以修改为自定义的点
        if (points == null || points.Count < 3)
        {
            print("points.Count: " + points.Count);
            return;
        }


        // 遍历每个点,创建线段
        for (int i = 0; i < points.Count - 1; i++)
        {
            print("绘制循环中");

            Vector2 start = points;
            Vector2 end = points[i + 1];

            // 计算垂直方向的法线,使线条有宽度
            Vector2 direction = (end - start).normalized;
            Vector2 perpendicular = new Vector2(-direction.y, direction.x) * lineWidth / 2f;

            // 四个顶点(左下、左上、右上、右下)
            UIVertex vertex = UIVertex.simpleVert;
            vertex.color = lineColor; // 定义颜色

            // 左下
            vertex.position = new Vector3(start.x - perpendicular.x, start.y - perpendicular.y);
            vh.AddVert(vertex);

            // 左上
            vertex.position = new Vector3(start.x + perpendicular.x, start.y + perpendicular.y);
            vh.AddVert(vertex);

            // 右上
            vertex.position = new Vector3(end.x + perpendicular.x, end.y + perpendicular.y);
            vh.AddVert(vertex);

            // 右下
            vertex.position = new Vector3(end.x - perpendicular.x, end.y - perpendicular.y);
            vh.AddVert(vertex);

            // 添加两个三角形来组成矩形线条
            int index = vh.currentVertCount;
            vh.AddTriangle(index - 4, index - 3, index - 2);
            vh.AddTriangle(index - 4, index - 2, index - 1);
        }
    }

    /// <summary>
    /// 设置一个Ui元素
    /// 为什么要转换坐标?因为UI元素极可能不在同一个父物体下,存在错综复杂的父子关系
    /// 先获取UiElement世界坐标系转屏幕坐标系再转到此脚本所在的Ui坐标系
    /// </summary>
    /// <param name="uiElement"></param>
    public void AppendUIElement(RectTransform uiElement)
    {
        Vector2 localPoint;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            rectTransform, // 当前 UILineRenderer 的 RectTransform
            RectTransformUtility.WorldToScreenPoint(null, uiElement.position), // UI 元素的世界坐标转换为屏幕坐标
            null,
            out localPoint // 输出的局部坐标
        );

        // 如果已经有三个点,则移除第是三个点,以保持绘制最新线条
        //points.Count可以修改为自定义的点
        if (points.Count == 3)
        {
            //自定义的点修改后,这里的RemoveAt记得也要修改
            points.RemoveAt(2);
        }

        // 添加转换后的局部坐标到点列表中
        points.Add(localPoint);

        // 标记为需要重新绘制
        SetVerticesDirty();
    }

    /// <summary>
    /// 设置鼠标位置为第二个点,此时鼠标和第一个UiElement可以构成一条线,如果不需要鼠标拖拽,此方法则不适用,其实此方法内部也是添加点的数组而已。
    /// </summary>
    /// <param name="point"></param>
    public void SetMouse()
    {
        if (points.Count == 2)
        {
            points.RemoveAt(1);
        }
        var mousePostion = Input.mousePosition;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, mousePostion, null, out Vector2 point);
        points.Add(point);
        SetVerticesDirty();
    }

    /// <summary>
    /// 设置线的颜色
    /// </summary>
    /// <param name="newColor"></param>
    public void SetLineColor(Color newColor)
    {
        lineColor = newColor;
        SetVerticesDirty();
    }

    /// <summary>
    /// 设置线的宽带
    /// </summary>
    /// <param name="width"></param>
    public void SetWidth(float width)
    {
        lineWidth = width;
        SetVerticesDirty();
    }

    /// <summary>
    /// 重置组件
    /// </summary>
    public void ResetSelf()
    {
        points.Clear();
        lineColor = Color.white;
        lineWidth = 5f;
        SetVerticesDirty();
    }
}



使用示例-尿酸折线图(测试项目使用的是,在其他界面按钮触发本方法,一开始给定几个点的坐标,直接绘制,不需要鼠标拖拽方法了)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System;

public class NSManager_script : MonoBehaviour
{
    //折线图界面Panel
    public GameObject NSZXTPanel;

    //折线图本体Panel
    public GameObject NSZXTMainPanel;

    public NSZXTPoint1_script t;

    private Vector2 NSZXTMainPanelPoint1, NSZXTMainPanelPoint2, NSZXTMainPanelPoint1Zone, NSZXTMainPanelPoint2Zone;


    public void NSZXTMainPanelOpenFF()
    {
        NSZXTPanel.SetActive(true);

        //折线图点设置位置
        NSZXTMainPanelPoint1 = new Vector2(-450, 30);
        NSZXTMainPanelPoint2 = new Vector2(-300, -270);

        NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[1].anchoredPosition = NSZXTMainPanelPoint1;
        NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[2].anchoredPosition = NSZXTMainPanelPoint2;

        //折线图点的设置
        t.AppendUIElement(NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[1]);
        t.AppendUIElement(NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[2]);
        t.AppendUIElement(NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[3]);


        print("折线图点1位置:" + NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[1].name + " : " + NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[1].anchoredPosition.x + " : " + NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[1].anchoredPosition.y);
        print("折线图点2位置:" + NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[2].name + " : " + NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[2].anchoredPosition.x + " : " + NSZXTMainPanel.GetComponentsInChildren<RectTransform>()[2].anchoredPosition.y);

    }

}



示例结构图
挂载了UILineRender的组件和其他UI元素一样,所以要放在下面,如果没有放在本层级最下面,可能会出现线段显示异常!





我这里示例将绘制线段脚本组件放到了一个叫Line的空物体上,然后将此空物体拉到本层级的最下面,显示成功!




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册成为修仙之旅的少年~

x
游客
回复
*滑块验证:

DA论坛飞机票来了~
快速回复 返回顶部 返回列表