using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using System.Linq; public class UILine : Graphic { #region InspectorVariables [Header("General")] public List<Vector2> points = new(); public float width = 5f; [Header("Rounding")] public bool roundCorners = true; public bool roundStart = false; public bool roundEnd = false; [Header("Dashed")] public bool dashed = false; public int dashLength = 10; public int dashSpacing = 5; #endregion InspectorVariables #region UnityMethods protected override void OnPopulateMesh(VertexHelper vh) { // clear old vertecies and triangles vh.Clear(); // a line with less than 2 points does not make any sense => return if (points.Count < 2) return; if (dashed) GenerateLinesDashed(vh); else GenerateLinesStandard(vh); if (roundStart) CreateCircle(vh, points[0], width); if (roundEnd) CreateCircle(vh, points[^1], width); } #endregion UnityMethods #region Implementation #region GenerateLines private void GenerateLinesStandard(VertexHelper vh) { points.Zip(points.Skip(1), (a, b) => Tuple.Create(a, b)) .ToList() .ForEach(tup => CreateSegment(vh, tup.Item1, tup.Item2)); if (roundCorners) { // take all points except for start and end and apply rounding points.Take(points.Count - 1).Skip(1).ToList() .ForEach(p => CreateCircle(vh, p, width)); } } private void GenerateLinesDashed(VertexHelper vh) { float restLen = dashLength; bool isDash = true; var lines = points.Zip(points.Skip(1), (a, b) => Tuple.Create(a, b)).ToList(); for (int i = 0; i < lines.Count; i++) { var tup = lines[i]; Vector2 start = tup.Item1; Vector2 end = tup.Item2; Vector2 dir = (end - start).normalized; // main spacing algorithm Vector2 current = start; while (Vector2.Distance(current, end) >= restLen) { Vector2 segmentEnd = current + restLen * dir; if (isDash) CreateSegment(vh, current, segmentEnd); current = segmentEnd; isDash = !isDash; restLen = isDash ? dashLength : dashSpacing; } // is there a dash wrapping around a corner? bool dashOverCorner = false; float distLeft = Vector2.Distance(current, end); if (!isDash) restLen -= distLeft; // dont fill remaining distance with dash, if it would be short (shorter than width/2) else if (isDash && distLeft > width/2) { CreateSegment(vh, current, end); restLen -= distLeft; dashOverCorner = true; } // discard rest of dash if it is too short (shorter than width/2) if (isDash && restLen < width/2) { isDash = false; restLen = dashSpacing; dashOverCorner = false; // dash does not wrap around corner as dashSpacing will follow } // only create round corners if roundCorners are enabled and there is a dash wrapping around the corner and not last corner (aka end) if (roundCorners && dashOverCorner && i != lines.Count-1) CreateCircle(vh, end, width); } } #endregion GenerateLines #region CreateLine private void CreateSegment(VertexHelper vh, Vector2 start, Vector2 end) { //start += (Vector2)transform.position; //end += (Vector2)transform.position; UIVertex vertex = UIVertex.simpleVert; vertex.color = color; Vector2 dir = end - start; Vector2 perp = Vector2.Perpendicular(dir).normalized; Vector2 off = perp * width / 2; vertex.position = new Vector3(start.x + off.x, start.y + off.y); vh.AddVert(vertex); vertex.position = new Vector3(end.x + off.x, end.y + off.y); vh.AddVert(vertex); vertex.position = new Vector3(end.x - off.x, end.y - off.y); vh.AddVert(vertex); vertex.position = new Vector3(start.x - off.x, start.y - off.y); vh.AddVert(vertex); int offset = vh.currentVertCount - 4; vh.AddTriangle(0 + offset, 1 + offset, 2 + offset); vh.AddTriangle(2 + offset, 3 + offset, 0 + offset); } #endregion CreateLine #region Rounding private void CreateCircle(VertexHelper vh, Vector2 center, float diameter, int sideCount = 20) { //center += (Vector2)transform.position; UIVertex vertex = UIVertex.simpleVert; vertex.color = color; Vector3[] vertices = GetCirclePoints(diameter/2, sideCount, center).Union(new Vector3[] { center }).ToArray(); vertices.ToList().ForEach(vert => { vertex.position = vert; vh.AddVert(vertex); } ); int startVert = vh.currentVertCount - vertices.Length; for (int i = 0; i < vertices.Length - 1; i++) { vh.AddTriangle( vh.currentVertCount - 1, // center startVert + i, startVert + ((i + 1) % (vertices.Length - 1)) ); } return; } protected static Vector3[] GetCirclePoints(float circleRadius, int pointCount, Vector3 offset) { Vector3[] circle = new Vector3[pointCount]; float slice = (2f * Mathf.PI) / pointCount; for (int i = 0; i < pointCount; i++) { float angle = i * slice; circle[i] = new Vector3(circleRadius * Mathf.Sin(angle), circleRadius * Mathf.Cos(angle)) + offset; } return circle; } #endregion Rounding #endregion Implementation }