[Unity][UIExtensions]在UILineRenderer实现IsRaycastLocationValid方法并被EventTrigger正确识别

做项目Runtime编辑器时遇到了一个需求:在UI上绘制直线/连续线段/贝塞尔曲线,并能够选中/拖动。第一时间想到的就是上网捞一个现成的绘制组件,然后就找到了官方的UI拓展包。
Unity UI Extensions README | Unity-UI-Extensions.GitHub.io

拓展包里的UILineRenderer能够很好的在UI上绘制直线/连续线段/贝塞尔曲线,但当我把EventTrigger挂到UILineRenderer所在GameObject上后,脚本里给PointerEnter和PointerExit事件写了两个用来测试的匿名。

            EventTrigger eventTrigger = lineRenderer.GetComponent<EventTrigger>();
            EventTrigger.Entry onEnterLine = new EventTrigger.Entry();
            onEnterLine.eventID = EventTriggerType.PointerEnter;
            onEnterLine.callback.AddListener((x) =>
            {
                Debug.Log("Enter");
            });

            EventTrigger.Entry onExitLine = new EventTrigger.Entry();
            onExitLine.eventID = EventTriggerType.PointerExit;
            onExitLine.callback.AddListener((x) =>
            {
                Debug.Log("Exit");
            });

            eventTrigger.triggers = new List<EventTrigger.Entry>() { onEnterLine, onExitLine};

运行后把鼠标移到线段上并移出线段发现并未打印Enter和Exit,于是就在想是不是射线没拿到UILineRenderer,所以EventTrigger中两个事件也没触发。

在检查UILineRenderer代码后发现确实没有实现IsRaycastLocation方法,其父类UIPrimitiveBase实现的IsRaycastLocation方法也完全不适用于UILineRenderer的需求。

public virtual bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
{
    // add test for line check
    if (m_EventAlphaThreshold >= 1)
        return true;

    Sprite sprite = overrideSprite;
    if (sprite == null)
        return true;

    Vector2 local;
    RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local);

    Rect rect = GetPixelAdjustedRect();

    // Convert to have lower left corner as reference point.
    local.x += rectTransform.pivot.x * rect.width;
    local.y += rectTransform.pivot.y * rect.height;

    local = MapCoordinate(local, rect);

    //test local coord with Mesh

    // Normalize local coordinates.
    Rect spriteRect = sprite.textureRect;
    Vector2 normalized = new Vector2(local.x / spriteRect.width, local.y / spriteRect.height);

    // Convert to texture space.
    float x = Mathf.Lerp(spriteRect.x, spriteRect.xMax, normalized.x) / sprite.texture.width;
    float y = Mathf.Lerp(spriteRect.y, spriteRect.yMax, normalized.y) / sprite.texture.height;

    try
    {
        return sprite.texture.GetPixelBilinear(x, y).a >= m_EventAlphaThreshold;
    }
    catch (UnityException e)
    {
        Debug.LogError("Using clickAlphaThreshold lower than 1 on Image whose sprite texture cannot be read. " + e.Message + " Also make sure to disable sprite packing for this sprite.", this);
        return true;
    }
}

父类实现的简单粗暴,完全没法判断鼠标在不在绘制的线区域里。

所以手动给UILineRenderer实现了IsRaycastLocationValid方法。UILineRenderer的绘制逻辑可以参考以下文章Unity在Canvas上画线(Draw Line)实现_unity drawline-CSDN博客

在知道大致原理后,我们只需要把原始点列表处理后的绘制点列表存一下,将绘制点列表循环生成若干线段,将鼠标位置转换成UILineRenderer所在画布的局部坐标得到点1,将点1依次向若干线段做投影得到投影点,判断能不能得到投影点,如果能得到投影点判断投影点与点1的距离,如果距离<=线宽/2即可认为鼠标在线上,反之不在。

       public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
	{
		if (lineThickness<=0||drawPoint == null || drawPoint.Count < 2) return false;
           Vector2 local;
           RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local);
           for (int i = 0; i < drawPoint.Count-1; i++)
           {
			Vector2 startPoint = drawPoint[i];
			Vector2 endPoint = drawPoint[i+1];
			if (TryGetProjectedPointOnSegment(startPoint, endPoint, local, out Vector2 projectedPoint))
			{
                   return Vector2.Distance(local, projectedPoint) <= lineThickness/2;
               }
			else
			{
                   return false;
               }
           }
		return false;
       }
        /// <summary>
        /// Attempt to obtain the projection point of a point on a line segment
        /// </summary>
        /// <param name="p1">Starting point of line segment</param>
        /// <param name="p2">Ending point of line segment</param>
        /// <param name="p3">Desired / Selected point</param>
        /// <param name="projectPoint">Project point</param>
        /// <returns>Is there a projection point?</returns>
        public bool TryGetProjectedPointOnSegment(Vector2 p1, Vector2 p2, Vector2 p3,out Vector2 projectPoint)
		{
            Vector2 from_p1_to_p3 = p3 - p1;
            Vector2 from_p1_to_p2 = p2 - p1;
            float dot = Vector2.Dot(from_p1_to_p3, from_p1_to_p2.normalized);
            dot /= from_p1_to_p2.magnitude;
			if(dot>=0&& dot <= 1)
			{
                float t = Mathf.Clamp01(dot);
				projectPoint = p1 + from_p1_to_p2 * t;
                return true;
			}
			else
			{
				projectPoint = Vector2.negativeInfinity;
                return false;
			}
        }

这样就能使UILineRenderer被射线正确作用了,EventTrigger也可以成功的拦截事件。

完善后的UILineRenderer全部代码如下,可以直接替换掉原始的脚本(Base on Unity UI Extensions Release 2.3.2)

/// Credit jack.sydorenko, firagon
/// Sourced from - http://forum.unity3d.com/threads/new-ui-and-line-drawing.253772/
/// Updated/Refactored from - http://forum.unity3d.com/threads/new-ui-and-line-drawing.253772/#post-2528050

using System.Collections.Generic;
using System.Linq;

namespace UnityEngine.UI.Extensions
{
    [AddComponentMenu("UI/Extensions/Primitives/UILineRenderer")]
    [RequireComponent(typeof(RectTransform))]
    public class UILineRenderer : UIPrimitiveBase
	{
		private enum SegmentType
		{
			Start,
            Middle,
            End,
            Full,
		}

		public enum JoinType
		{
			Bevel,
            Miter
		}

		public enum BezierType
		{
			None,
            Quick,
            Basic,
            Improved,
            Catenary,
        }

		private const float MIN_MITER_JOIN = 15 * Mathf.Deg2Rad;

		// A bevel 'nice' join displaces the vertices of the line segment instead of simply rendering a
		// quad to connect the endpoints. This improves the look of textured and transparent lines, since
		// there is no overlapping.
        private const float MIN_BEVEL_NICE_JOIN = 30 * Mathf.Deg2Rad;

		private static Vector2 UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_TOP_CENTER_LEFT, UV_TOP_CENTER_RIGHT, UV_BOTTOM_CENTER_LEFT, UV_BOTTOM_CENTER_RIGHT, UV_TOP_RIGHT, UV_BOTTOM_RIGHT;
		private static Vector2[] startUvs, middleUvs, endUvs, fullUvs;

        [SerializeField, Tooltip("Points to draw lines between\n Can be improved using the Resolution Option")]
        internal Vector2[] m_points;
        [SerializeField, Tooltip("Segments to be drawn\n This is a list of arrays of points")]
		internal List<Vector2[]> m_segments;

        [SerializeField, Tooltip("Thickness of the line")]
        internal float lineThickness = 2;
        [SerializeField, Tooltip("Use the relative bounds of the Rect Transform (0,0 -> 0,1) or screen space coordinates")]
        internal bool relativeSize;
        [SerializeField, Tooltip("Do the points identify a single line or split pairs of lines")]
        internal bool lineList;
        [SerializeField, Tooltip("Add end caps to each line\nMultiple caps when used with Line List")]
        internal bool lineCaps;
        [SerializeField, Tooltip("Resolution of the Bezier curve, different to line Resolution")]
        internal int bezierSegmentsPerCurve = 10;

        public float LineThickness
        {
            get { return lineThickness; }
            set { lineThickness = value; SetAllDirty(); }
        }

        public bool RelativeSize
        {
            get { return relativeSize; }
            set { relativeSize = value; SetAllDirty(); }
        }

        public bool LineList
        {
            get { return lineList; }
            set { lineList = value; SetAllDirty(); }
        }

        public bool LineCaps
        {
            get { return lineCaps; }
            set { lineCaps = value; SetAllDirty(); }
        }

        [Tooltip("The type of Join used between lines, Square/Mitre or Curved/Bevel")]
		public JoinType LineJoins = JoinType.Bevel;

        [Tooltip("Bezier method to apply to line, see docs for options\nCan't be used in conjunction with Resolution as Bezier already changes the resolution")]
        public BezierType BezierMode = BezierType.None;

        public int BezierSegmentsPerCurve
        {
            get { return bezierSegmentsPerCurve; }
            set { bezierSegmentsPerCurve = value; }
        }

        [HideInInspector]
        public bool drivenExternally = false;


		/// <summary>
		/// Points to be drawn in the line.
		/// </summary>
        public Vector2[] Points
		{
			get
			{
				return m_points;
			}

			set
			{
				if (m_points == value) return;

				if (value == null || value.Length == 0)
				{
					m_points = new Vector2[1];
				}
				else
				{
                    m_points = value;
                }

                SetAllDirty();
			}
		}

		/// <summary>
		/// List of Segments to be drawn.
		/// </summary>
        public List<Vector2[]> Segments
		{
			get
			{
				return m_segments;
			}

			set
			{
				m_segments = value;
				SetAllDirty();
			}
		}

		private List<Vector2> drawPoint = new List<Vector2>();

		private void PopulateMesh(VertexHelper vh, Vector2[] pointsToDraw)
		{
			//If Bezier is desired, pick the implementation
			if (BezierMode != BezierType.None && BezierMode != BezierType.Catenary && pointsToDraw.Length > 3) {
				BezierPath bezierPath = new BezierPath ();

				bezierPath.SetControlPoints (pointsToDraw);
				bezierPath.SegmentsPerCurve = bezierSegmentsPerCurve;
				List<Vector2> drawingPoints;
				switch (BezierMode) {
				case BezierType.Basic:
					drawingPoints = bezierPath.GetDrawingPoints0 ();
					break;
				case BezierType.Improved:
					drawingPoints = bezierPath.GetDrawingPoints1 ();
					break;
				default:
					drawingPoints = bezierPath.GetDrawingPoints2 ();
					break;
				}

				pointsToDraw = drawingPoints.ToArray ();
			}
			if (BezierMode == BezierType.Catenary && pointsToDraw.Length == 2) {
				CableCurve cable = new CableCurve (pointsToDraw);
				cable.slack = Resolution;
				cable.steps = BezierSegmentsPerCurve;
				pointsToDraw = cable.Points ();
			}

			if (ImproveResolution != ResolutionMode.None) {
				pointsToDraw = IncreaseResolution (pointsToDraw);
			}

			drawPoint = pointsToDraw.ToList();

            // scale based on the size of the rect or use absolute, this is switchable
            var sizeX = !relativeSize ? 1 : rectTransform.rect.width;
			var sizeY = !relativeSize ? 1 : rectTransform.rect.height;
			var offsetX = -rectTransform.pivot.x * sizeX;
			var offsetY = -rectTransform.pivot.y * sizeY;

			// Generate the quads that make up the wide line
			var segments = new List<UIVertex[]> ();
			if (lineList) {
				//Loop through list in line pairs, skipping drawing between lines
				for (var i = 1; i < pointsToDraw.Length; i += 2) {
					var start = pointsToDraw [i - 1];
					var end = pointsToDraw [i];
					start = new Vector2 (start.x * sizeX + offsetX, start.y * sizeY + offsetY);
					end = new Vector2 (end.x * sizeX + offsetX, end.y * sizeY + offsetY);

					if (lineCaps) {
						segments.Add (CreateLineCap (start, end, SegmentType.Start));
					}

					// Originally, UV's had to be wrapped per segment to ensure textures rendered correctly, however when tested in 2019.4, this no longer seems to be an issue.
					segments.Add(CreateLineSegment(start, end, SegmentType.Middle));

					if (lineCaps) {
						segments.Add (CreateLineCap (start, end, SegmentType.End));
					}
				}
			} else {
				//Draw full lines
				for (var i = 1; i < pointsToDraw.Length; i++) {
					var start = pointsToDraw [i - 1];
					var end = pointsToDraw [i];
					start = new Vector2 (start.x * sizeX + offsetX, start.y * sizeY + offsetY);
					end = new Vector2 (end.x * sizeX + offsetX, end.y * sizeY + offsetY);

					if (lineCaps && i == 1) {
						segments.Add (CreateLineCap (start, end, SegmentType.Start));
					}

					segments.Add (CreateLineSegment (start, end, SegmentType.Middle));

					if (lineCaps && i == pointsToDraw.Length - 1) {
						segments.Add (CreateLineCap (start, end, SegmentType.End));
					}
				}
			}
			// Add the line segments to the vertex helper, creating any joins as needed
			for (var i = 0; i < segments.Count; i++) {
				if (!lineList && i < segments.Count - 1) {
					var vec1 = segments [i] [1].position - segments [i] [2].position;
					var vec2 = segments [i + 1] [2].position - segments [i + 1] [1].position;
					var angle = Vector2.Angle (vec1, vec2) * Mathf.Deg2Rad;

					// Positive sign means the line is turning in a 'clockwise' direction
					var sign = Mathf.Sign (Vector3.Cross (vec1.normalized, vec2.normalized).z);

					// Calculate the miter point
					var miterDistance = lineThickness / (2 * Mathf.Tan (angle / 2));
					var miterPointA = segments [i] [2].position - vec1.normalized * miterDistance * sign;
					var miterPointB = segments [i] [3].position + vec1.normalized * miterDistance * sign;

					var joinType = LineJoins;
					if (joinType == JoinType.Miter) {
						// Make sure we can make a miter join without too many artifacts.
						if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_MITER_JOIN) {
							segments [i] [2].position = miterPointA;
							segments [i] [3].position = miterPointB;
							segments [i + 1] [0].position = miterPointB;
							segments [i + 1] [1].position = miterPointA;
						} else {
							joinType = JoinType.Bevel;
						}
					}

					if (joinType == JoinType.Bevel) {
						if (miterDistance < vec1.magnitude / 2 && miterDistance < vec2.magnitude / 2 && angle > MIN_BEVEL_NICE_JOIN) {
							if (sign < 0) {
								segments [i] [2].position = miterPointA;
								segments [i + 1] [1].position = miterPointA;
							} else {
								segments [i] [3].position = miterPointB;
								segments [i + 1] [0].position = miterPointB;
							}
						}

						var join = new UIVertex[] { segments [i] [2], segments [i] [3], segments [i + 1] [0], segments [i + 1] [1] };
						vh.AddUIVertexQuad (join);
					}
				}

				vh.AddUIVertexQuad (segments [i]);
			}
			if (vh.currentVertCount > 64000) {
				Debug.LogError ("Max Verticies size is 64000, current mesh verticies count is [" + vh.currentVertCount + "] - Cannot Draw");
				vh.Clear ();
				return;
			}

		}

        protected override void OnPopulateMesh(VertexHelper vh)
		{
			if (m_points != null && m_points.Length > 0) {
				GeneratedUVs ();
				vh.Clear ();

				PopulateMesh (vh, m_points);

			}
			if (m_segments != null && m_segments.Count > 0) {
				GeneratedUVs ();
				vh.Clear ();

				for (int s = 0; s < m_segments.Count; s++) {
					Vector2[] pointsToDraw = m_segments [s];
					PopulateMesh (vh, pointsToDraw);
				}
			} 
        }

		private UIVertex[] CreateLineCap(Vector2 start, Vector2 end, SegmentType type)
		{
			if (type == SegmentType.Start)
			{
				var capStart = start - ((end - start).normalized * lineThickness / 2);
				return CreateLineSegment(capStart, start, SegmentType.Start);
			}
			else if (type == SegmentType.End)
			{
				var capEnd = end + ((end - start).normalized * lineThickness / 2);
				return CreateLineSegment(end, capEnd, SegmentType.End);
			}

			Debug.LogError("Bad SegmentType passed in to CreateLineCap. Must be SegmentType.Start or SegmentType.End");
			return null;
		}

		private UIVertex[] CreateLineSegment(Vector2 start, Vector2 end, SegmentType type, UIVertex[] previousVert = null)
		{
			Vector2 offset = new Vector2((start.y - end.y), end.x - start.x).normalized * lineThickness / 2;

			Vector2 v1 = Vector2.zero;
			Vector2 v2 = Vector2.zero;
			if (previousVert != null) {
				v1 = new Vector2(previousVert[3].position.x, previousVert[3].position.y);
				v2 = new Vector2(previousVert[2].position.x, previousVert[2].position.y);
			} else {
				v1 = start - offset;
				v2 = start + offset;
			}

			var v3 = end + offset;
			var v4 = end - offset;
            //Return the VDO with the correct uvs
            switch (type)
            {
                case SegmentType.Start:
                    return SetVbo(new[] { v1, v2, v3, v4 }, startUvs);
                case SegmentType.End:
                    return SetVbo(new[] { v1, v2, v3, v4 }, endUvs);
                case SegmentType.Full:
                    return SetVbo(new[] { v1, v2, v3, v4 }, fullUvs);
                default:
                    return SetVbo(new[] { v1, v2, v3, v4 }, middleUvs);
            }
		}
        public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
		{
			if (lineThickness<=0||drawPoint == null || drawPoint.Count < 2) return false;
            Vector2 local;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local);
            for (int i = 0; i < drawPoint.Count-1; i++)
            {
				Vector2 startPoint = drawPoint[i];
				Vector2 endPoint = drawPoint[i+1];
				if (TryGetProjectedPointOnSegment(startPoint, endPoint, local, out Vector2 projectedPoint))
				{
                    return Vector2.Distance(local, projectedPoint) <= lineThickness/2;
                }
				else
				{
                    return false;
                }
            }
			return false;
        }

        protected override void GeneratedUVs()
        {
            if (activeSprite != null)
            {
                var outer = Sprites.DataUtility.GetOuterUV(activeSprite);
                var inner = Sprites.DataUtility.GetInnerUV(activeSprite);
                UV_TOP_LEFT = new Vector2(outer.x, outer.y);
                UV_BOTTOM_LEFT = new Vector2(outer.x, outer.w);
                UV_TOP_CENTER_LEFT = new Vector2(inner.x, inner.y);
                UV_TOP_CENTER_RIGHT = new Vector2(inner.z, inner.y);
                UV_BOTTOM_CENTER_LEFT = new Vector2(inner.x, inner.w);
                UV_BOTTOM_CENTER_RIGHT = new Vector2(inner.z, inner.w);
                UV_TOP_RIGHT = new Vector2(outer.z, outer.y);
                UV_BOTTOM_RIGHT = new Vector2(outer.z, outer.w);
            }
            else
            {
                UV_TOP_LEFT = Vector2.zero;
                UV_BOTTOM_LEFT = new Vector2(0, 1);
                UV_TOP_CENTER_LEFT = new Vector2(0.5f, 0);
                UV_TOP_CENTER_RIGHT = new Vector2(0.5f, 0);
                UV_BOTTOM_CENTER_LEFT = new Vector2(0.5f, 1);
                UV_BOTTOM_CENTER_RIGHT = new Vector2(0.5f, 1);
                UV_TOP_RIGHT = new Vector2(1, 0);
                UV_BOTTOM_RIGHT = Vector2.one;
            }


            startUvs = new[] { UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_BOTTOM_CENTER_LEFT, UV_TOP_CENTER_LEFT };
            middleUvs = new[] { UV_TOP_CENTER_LEFT, UV_BOTTOM_CENTER_LEFT, UV_BOTTOM_CENTER_RIGHT, UV_TOP_CENTER_RIGHT };
            endUvs = new[] { UV_TOP_CENTER_RIGHT, UV_BOTTOM_CENTER_RIGHT, UV_BOTTOM_RIGHT, UV_TOP_RIGHT };
            fullUvs = new[] { UV_TOP_LEFT, UV_BOTTOM_LEFT, UV_BOTTOM_RIGHT, UV_TOP_RIGHT };
        }

        protected override void ResolutionToNativeSize(float distance)
        {
            if (UseNativeSize)
            {
                m_Resolution = distance / (activeSprite.rect.width / pixelsPerUnit);
                lineThickness = activeSprite.rect.height / pixelsPerUnit;
            }
        }

        private int GetSegmentPointCount()
        {
            if (Segments?.Count > 0)
            {
                int pointCount = 0;
                foreach (var segment in Segments)
                {
                    pointCount += segment.Length;
                }
                return pointCount;
            }
            return Points.Length;
        }

        /// <summary>
        /// Get the Vector2 position of a line index
        /// </summary>
        /// <remarks>
        /// Positive numbers should be used to specify Index and Segment
        /// </remarks>
        /// <param name="index">Required Index of the point, starting from point 1</param>
        /// <param name="segmentIndex">(optional) Required Segment the point is held in, Starting from Segment 1</param>
        /// <returns>Vector2 position of the point within UI Space</returns>
        public Vector2 GetPosition(int index, int segmentIndex = 0)
        {
            if (segmentIndex > 0)
            {
                return Segments[segmentIndex - 1][index - 1];
            }
            else if (Segments?.Count > 0)
            {
                var segmentIndexCount = 0;
                var indexCount = index;
                foreach (var segment in Segments)
                {
                    if (indexCount - segment.Length > 0)
                    {
                        indexCount -= segment.Length;
                        segmentIndexCount += 1;
                    }
                    else
                    {
                        break;    
                    }
                }
                return Segments[segmentIndexCount][indexCount - 1];
            }
            else
            {
                return Points[index - 1];
            }
        }

		/// <summary>
        /// Calculates the position of a point on the curve, given t (0-1), start point, control points and end point.
        /// </summary>
        /// <param name="t">Required Percentage between start and end point, in the range 0 to 1</param>
		/// <param name="p1">Required Starting point</param>
		/// <param name="p1">Required Control point 1</param>
		/// <param name="p1">Required Control point 2</param>
		/// <param name="p1">Required End point</param>
        /// <returns>Vector2 position of point on curve at t percentage between p1 and p4</returns>
		public Vector2 CalculatePointOnCurve(float t, Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4)
        {
            var t2 = t * t;
            var t3 = t2 * t;

            var x = p1.x + (-p1.x * 3 + t * (3 * p1.x - p1.x * t)) * t + (3 * p2.x + t * (-6 * p2.x + p2.x * 3 * t)) * t +
                    (p3.x * 3 - p3.x * 3 * t) * t2 + p4.x * t3;
            
            var y = p1.y + (-p1.y * 3 + t * (3 * p1.y - p1.y * t)) * t + (3 * p2.y + t * (-6 * p2.y + p2.y * 3 * t)) * t +
                    (p3.y * 3 - p3.y * 3 * t) * t2 + p4.y * t3;

            return new Vector2(x, y);
        }

        /// <summary>
        /// Get the Vector2 position of a line within a specific segment
        /// </summary>
        /// <param name="index">Required Index of the point, starting from point 1</param>
        /// <param name="segmentIndex"> Required Segment the point is held in, Starting from Segment 1</param>
        /// <returns>Vector2 position of the point within UI Space</returns>
        public Vector2 GetPositionBySegment(int index, int segment)
        {
            return Segments[segment][index - 1];
        }

        /// <summary>
        /// Get the closest point between two given Vector2s from a given Vector2 point
        /// </summary>
        /// <param name="p1">Starting position</param>
        /// <param name="p2">End position</param>
        /// <param name="p3">Desired / Selected point</param>
        /// <returns>Closest Vector2 position of the target within UI Space</returns>
        public Vector2 GetClosestPoint(Vector2 p1, Vector2 p2, Vector2 p3)
        {
            Vector2 from_p1_to_p3 = p3 - p1;
            Vector2 from_p1_to_p2 = p2 - p1;
            float dot = Vector2.Dot(from_p1_to_p3, from_p1_to_p2.normalized);
            dot /= from_p1_to_p2.magnitude;
			Debug.Log(dot);
            float t = Mathf.Clamp01(dot);
            return p1 + from_p1_to_p2 * t;
        }

        /// <summary>
        /// Attempt to obtain the projection point of a point on a line segment
        /// </summary>
        /// <param name="p1">Starting point of line segment</param>
        /// <param name="p2">Ending point of line segment</param>
        /// <param name="p3">Desired / Selected point</param>
        /// <param name="projectPoint">Project point</param>
        /// <returns>Is there a projection point?</returns>
        public bool TryGetProjectedPointOnSegment(Vector2 p1, Vector2 p2, Vector2 p3,out Vector2 projectPoint)
		{
            Vector2 from_p1_to_p3 = p3 - p1;
            Vector2 from_p1_to_p2 = p2 - p1;
            float dot = Vector2.Dot(from_p1_to_p3, from_p1_to_p2.normalized);
            dot /= from_p1_to_p2.magnitude;
			if(dot>=0&& dot <= 1)
			{
                float t = Mathf.Clamp01(dot);
				projectPoint = p1 + from_p1_to_p2 * t;
                return true;
			}
			else
			{
				projectPoint = Vector2.negativeInfinity;
                return false;
			}
        }

        protected override void OnEnable()
        {
			base.OnEnable();
            if (m_points == null || m_points?.Length == 0)
            {
				m_points = new Vector2[1];
            }
			if (transform.GetComponent<RectTransform>().position != Vector3.zero)
			{
				Debug.LogWarning("A Line Renderer component should be on a RectTransform positioned at (0,0,0), do not use in child Objects.\nFor best results, create separate RectTransforms as children of the canvas positioned at (0,0) for a UILineRenderer and do not move.");
			}
        }
    }
}

(疑惑的是UILineRenderer本就实现了一个叫GetClosestPoint的Public方法但却从未使用过,这个方法内容恰好是判断鼠标是否在线段内的核心代码)
 

### 回答1: UILineRenderer.cs是Unity的一个脚本,用于在Unity中绘制2D线段,该脚本通常用于UI元素,例如在屏幕上绘制HUD元素、指示器、边框等。 该脚本使用的是Unity的UI系统,因此可以直接添加到UI元素上,例如Image或RawImage。它包含以下属性: - Points: 一个Vector2类型的数组,用于指定线段的顶点。 - LineThickness: 线段的宽度。 - Color: 线段的颜色。 除了这些属性之外,该脚本还包含了一些方法,例如AddPoint和RemovePoint,用于在运行时添加或删除线段的顶点。 使用UILineRenderer.cs,你可以轻松地在Unity中创建自定义的线段,例如在UI中绘制进度条、血条等。 ### 回答2: UILineRenderer.cs 是 Unity 中的一个脚本,用于在屏幕上绘制线段。它是 Unity UI 系统中的一部分,可以在 UI 元素上绘制 2D 线条。 使用 UILineRenderer.cs 可以轻松地创建和控制线段的外观和行为。可以根据需要设置线段的起点和终点,在两个点之间绘制一条直线。还可以通过更改线段的宽度、颜色和材质来定制其外观。 UILineRenderer.cs 提供了一些方法和属性来方便地操控线段。其中最主要的方法是 SetPoints,可以通过传入一个 Vector2 数组来设置线段的顶点坐标。此外,还有 SetColor、SetWidth 和 SetMaterial 等方法可以用来设置线段的颜色、宽度和材质。 使用 UILineRenderer.cs 绘制线段的过程非常简单。首先,需要在场景中创建一个空的 GameObject,添加 UILineRenderer.cs 脚本组件。然后,在脚本中调用相应的方法来设置线段的属性,比如起点、终点、颜色等。最后,将该脚本所在的 GameObject 放置在 UI 元素上,就可以在屏幕上绘制出线段了。 总的来说,UILineRenderer.cs 是 Unity 中一个非常有用的工具,可以方便地在 UI 元素上绘制线段,可以用于实现各种需要线条表达的功能和效果,如画笔工具、连接线路等。 ### 回答3: UILineRenderer.cs 是 Unity 引擎中的一个脚本,用于在 Unity UI 中绘制线段。该脚本利用了 Unity 的 GraphicRaycaster 和 RectTransform 来实现线段的绘制。 UILineRenderer.cs 提供了一系列属性和方法,用于控制线段的形状、颜色和宽度。通过设置线段的起点和终点坐标,可以在画布上绘制出直线。同时,也可以通过设置控制点来绘制曲线和多段线。 在绘制线段之前,需要将 UILineRenderer.cs 组件添加到 Canvas 对象上。在脚本中,需要将起点、终点、控制点等信息传递给 UILineRenderer通过调用其 DrawLine() 方法实现线段的绘制。 UILineRenderer.cs 利用 Unity 的 GraphicRaycaster 来实现线段的交互功能。我们可以通过代码设置触发事件,如点击或拖拽线段。同时,UILineRenderer.cs 也支持对线段进行颜色、宽度等属性的设置,使其更加美观和有趣。 总的来说,UILineRenderer.cs 是一个在 Unity UI 中绘制线段的实用脚本。通过使用该脚本,我们可以方便地在 Unity UI 中绘制直线、曲线和多段线,同时也可以实现线段与用户交互的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

This is Kkong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值