| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.UI;
- namespace LineUI
- {
- // 定义 ArrowInfo 类
- public class ArrowInfo
- {
- public Vector2 Position { get; set; }
- public Vector2 Direction { get; set; }
- public ArrowInfo(Vector2 position, Vector2 direction)
- {
- Position = position;
- Direction = direction;
- }
- }
- [RequireComponent(typeof(CanvasRenderer))]
- [RequireComponent(typeof(RectTransform))]
- public class Line : Graphic
- {
- [Header("绘制线段")]
- [SerializeField] private bool loop = false;
- [SerializeField] private float thickness = 1f;
- [SerializeField] private int roundCount = 0;
- [SerializeField] private List<Vector2> screenPositions = new List<Vector2>();
- //获取当前的points
- [HideInInspector]
- public List<Vector2> ScreenPositions => screenPositions;
- [Header("绘制内四边形")]
- //是否绘制内四边形
- [SerializeField] bool bDrawQuad = true;
- [SerializeField] private Vector2 quadrilateralSize = new Vector2(100, 100);
- [SerializeField] private Color quadColor = Color.red;
- [Header("绘制外围蒙板")]
- //是否绘制外围蒙板
- [SerializeField] bool bDrawMask = false;
- [SerializeField] private Color maskColor = Color.red;
- //控制扇形角绘制
- [Header("扇形角绘制")]
- [SerializeField] private bool bDrawFan = false;
- [SerializeField] private Color fanColor = Color.white;
- [SerializeField] private int fanSegments = 20; // 扇形的平滑度
- [SerializeField] private float fanOuterRadius = 150f; // 扇形的半径
- private float fanInnerRadius = 0f;
- [Tooltip("扇形指向箭头")]
- [SerializeField] private bool bDrawArrow = false;
- [SerializeField] private Color arrowColor = Color.white;
- [SerializeField] float arrowLength = 50f;
- [SerializeField] float arrowWidth = 50f;
- [SerializeField] float arrowHeadHeight = 30f;
- [SerializeField] float arrowDis = 100f;
- private List<ArrowInfo> arrowInfos = new List<ArrowInfo>();
- //获取当前的points
- [HideInInspector]
- public List<ArrowInfo> ArrowInfos => arrowInfos;
- //[SerializeField] private Color quadTextColor = Color.black;
- //[SerializeField] private int quadFontSize = 14;
- //private List<Vector2> quadsToDraw = new List<Vector2>();
- //private List<string> quadTextToDraw = new List<string>();
- //private List<GameObject> quadObjects = new List<GameObject>();
- //private Stack<GameObject> objectPool = new Stack<GameObject>();
- private const int ObjectPoolLimit = 4;
- public RectTransform RectTr => rectTransform;
- public float MyThickness
- {
- get => thickness;
- set
- {
- if (value >= 1)
- {
- thickness = value;
- }
- }
- }
- public float MyFanWidth
- {
- get => fanOuterRadius;
- set
- {
- if (value >= 1)
- {
- fanOuterRadius = value;
- }
- }
- }
- public void SetLine(List<Vector2> screenPositions)
- {
- this.screenPositions = screenPositions;
- SetVerticesDirty();
- }
- public void SetDrawQuad(bool value) {
- bDrawQuad = value;
- SetVerticesDirty();
- }
- public void SetDrawMask(bool value)
- {
- bDrawMask = value;
- SetVerticesDirty();
- }
- public void SetDrawFan(bool value)
- {
- bDrawFan = value;
- SetVerticesDirty();
- }
- protected override void OnPopulateMesh(VertexHelper vh)
- {
- vh.Clear();
- arrowInfos.Clear();
- //quadsToDraw.Clear();
- //quadTextToDraw.Clear();
- if (screenPositions.Count < 2)
- return;
- SetLineVertices(vh);
- SetLineTriangles(vh);
- if(bDrawQuad) SetQuadrilateralVertices(vh);
- if(bDrawMask) DrawMask(vh); // 绘制蒙版
- if (bDrawFan) DrawFansAtCorners(vh); // 在转折角绘制扇形
- }
- private void SetLineVertices(VertexHelper vh)
- {
- UIVertex vert = UIVertex.simpleVert;
- vert.color = color;
- List<Vector2> _screenPositions = new List<Vector2>(screenPositions);
- if (loop)
- {
- _screenPositions.Add(screenPositions[0]);
- _screenPositions.Add(screenPositions[1]);
- }
- float lastAngle = 0;
- for (int i = 1; i < _screenPositions.Count; i++)
- {
- Vector2 previousPos = _screenPositions[i - 1];
- Vector2 currentPos = _screenPositions[i];
- Vector2 dif = currentPos - previousPos;
- float angle = Vector2.SignedAngle(Vector2.right, dif);
- Vector2 offset = Quaternion.Euler(0, 0, angle) * Vector3.up * thickness;
- if (i > 1)
- {
- float anglePerRound = (angle - lastAngle) / roundCount;
- for (int j = 0; j <= roundCount; j++)
- {
- float ang = lastAngle + anglePerRound * j;
- Vector2 roundOffset = Quaternion.Euler(0, 0, ang) * Vector2.up * thickness;
- vert.position = previousPos - roundOffset;
- vh.AddVert(vert);
- vert.position = previousPos + roundOffset;
- vh.AddVert(vert);
- }
- }
- lastAngle = angle;
- vert.position = previousPos - offset;
- vh.AddVert(vert);
- vert.position = previousPos + offset;
- vh.AddVert(vert);
- vert.position = currentPos - offset;
- vh.AddVert(vert);
- vert.position = currentPos + offset;
- vh.AddVert(vert);
- }
- }
- private void SetQuadrilateralVertices(VertexHelper vh)
- {
- UIVertex vert = UIVertex.simpleVert;
- vert.color = quadColor;
- List<Vector2> _screenPositions = new List<Vector2>(screenPositions);
- if (loop)
- {
- _screenPositions.Add(screenPositions[0]);
- _screenPositions.Add(screenPositions[1]);
- }
- for (int i = 1; i < _screenPositions.Count; i++)
- {
- Vector2 previousPos = _screenPositions[i - 1];
- Vector2 currentPos = _screenPositions[i];
- if (i > 1)
- {
- Vector2 prevDir = (_screenPositions[i - 1] - _screenPositions[i - 2]).normalized;
- Vector2 currentDir = (currentPos - previousPos).normalized;
- int index = i == 4 ? 4 : i % 4;
- if (index == 4 || index == 2)
- {
- DrawSquareAtCorner(vh, previousPos, prevDir, currentDir, quadrilateralSize.x, quadrilateralSize.y, index);
- }
- else {
- DrawSquareAtCorner(vh, previousPos, prevDir, currentDir, quadrilateralSize.y, quadrilateralSize.x, index);
- }
- }
- }
- }
- private void DrawSquareAtCorner(VertexHelper vh, Vector2 corner, Vector2 prevDir, Vector2 currentDir, float width, float height, int index)
- {
- UIVertex vert = UIVertex.simpleVert;
- vert.color = quadColor;
- Vector2[] corners = new Vector2[4];
- corners[0] = corner;
- corners[1] = corner + prevDir * -width;
- corners[2] = corner + prevDir * -width + currentDir * height;
- corners[3] = corner + currentDir * height;
- foreach (Vector2 pos in corners)
- {
- vert.position = pos;
- vh.AddVert(vert);
- }
- int startIndex = vh.currentVertCount - 4;
- vh.AddTriangle(startIndex, startIndex + 1, startIndex + 2);
- vh.AddTriangle(startIndex + 2, startIndex + 3, startIndex);
- // Store the quad's center position and text for later use
- //Vector2 quadCenter = corner + (prevDir * -width + currentDir * height) / 2;
- //quadsToDraw.Add(quadCenter);
- //quadTextToDraw.Add(index.ToString());
- }
- private void SetLineTriangles(VertexHelper vh)
- {
- for (int i = 0; i < vh.currentVertCount - 2; i += 2)
- {
- int index = i;
- vh.AddTriangle(index, index + 1, index + 3);
- vh.AddTriangle(index + 3, index + 2, index);
- }
- }
- private void DrawMask(VertexHelper vh)
- {
- UIVertex vert = UIVertex.simpleVert;
- vert.color = maskColor;
- //// 屏幕四个角的坐标
- //Vector2[] screenCorners = new Vector2[]
- //{
- // new Vector2(-Screen.width / 2, -Screen.height / 2), // 左下角
- // new Vector2(Screen.width / 2, -Screen.height / 2), // 右下角
- // new Vector2(Screen.width / 2, Screen.height / 2), // 右上角
- // new Vector2(-Screen.width / 2, Screen.height / 2), // 左上角
- //};
- // 获取 RectTransform 的实际四个角坐标
- Rect rect = rectTransform.rect;
- Vector2[] screenCorners = new Vector2[]
- {
- new Vector2(rect.xMin, rect.yMin), // 左下角
- new Vector2(rect.xMax, rect.yMin), // 右下角
- new Vector2(rect.xMax, rect.yMax), // 右上角
- new Vector2(rect.xMin, rect.yMax), // 左上角
- };
- // 添加四个点作为内框(中间区域的四个顶点)
- Vector2[] innerCorners = screenPositions.ToArray();
- // 分别绘制四个蒙版区域
- // 1. 左上区域:围绕左上角和内框左上、右上
- AddQuad(vh, screenCorners[3], innerCorners[3], innerCorners[2], screenCorners[2]);
- // 2. 右上区域:围绕右上角和内框右上、右下
- AddQuad(vh, innerCorners[2], screenCorners[2], screenCorners[1], innerCorners[1]);
- // 3. 右下区域:围绕右下角和内框右下、左下
- AddQuad(vh, innerCorners[0], innerCorners[1], screenCorners[1], screenCorners[0]);
- // 4. 左下区域:围绕左下角和内框左下、左上
- AddQuad(vh, screenCorners[3], screenCorners[0], innerCorners[0], innerCorners[3]);
- }
- private void AddQuad(VertexHelper vh, Vector2 corner1, Vector2 corner2, Vector2 corner3, Vector2 corner4)
- {
- UIVertex vert = UIVertex.simpleVert;
- vert.color = maskColor;
- vert.position = corner1;
- vh.AddVert(vert);
- vert.position = corner2;
- vh.AddVert(vert);
- vert.position = corner3;
- vh.AddVert(vert);
- vert.position = corner4;
- vh.AddVert(vert);
- //Debug.Log("vh.currentVertCount:"+ vh.currentVertCount);
- int startIndex = vh.currentVertCount - 4;
- vh.AddTriangle(startIndex, startIndex + 1, startIndex + 2);
- vh.AddTriangle(startIndex + 2, startIndex + 3, startIndex);
- }
- /// <summary>
- /// 1. 计算角度方向:
- /// 使用 Vector2.Angle 可以得到两个向量之间的夹角大小,但它不能确定角度的方向。为此,我们需要使用 Mathf.Atan2 来计算相对坐标的角度。Atan2 可以返回一个在 -π 到 π 之间的角度值,并且考虑了顺时针和逆时针的方向。
- /// 2. 从 X 轴正方向为 0 计算角度:
- /// 将角度计算为相对于 x 轴正方向的角度,并根据 Atan2 结果进行调整。
- /// </summary>
- /// <param name="vh"></param>
- private void DrawFansAtCorners(VertexHelper vh)
- {
- List<Vector2> _screenPositions = new List<Vector2>(screenPositions);
- if (loop)
- {
- _screenPositions.Add(screenPositions[0]);
- _screenPositions.Add(screenPositions[1]);
- }
- //比如现在是6个点,实际从第二个点开始绘制
- for (int i = 1; i < _screenPositions.Count - 1; i++)
- {
- Vector2 previousPos = _screenPositions[i - 1];
- Vector2 currentPos = _screenPositions[i];
- Vector2 nextPos = _screenPositions[i + 1];
- // 计算当前点到前一点的方向
- Vector2 prevDir = (currentPos - previousPos).normalized;
- // 计算当前点到下一点的方向
- Vector2 currentDir = (nextPos - currentPos).normalized;
- // 使用 Atan2 计算方向相对于 x 轴的角度
- float prevAngle = Mathf.Atan2(prevDir.y, prevDir.x) * Mathf.Rad2Deg; // 前一个方向相对于 x 轴的角度
- float currentAngle = Mathf.Atan2(currentDir.y, currentDir.x) * Mathf.Rad2Deg; // 当前方向相对于 x 轴的角度
- // 计算两个方向之间的角度差
- float angleBetween = Vector2.Angle(prevDir, currentDir);
- // 判断旋转方向
- float cross = prevDir.x * currentDir.y - prevDir.y * currentDir.x;
- if (cross < 0)
- {
- // 逆时针旋转
- angleBetween = 360f - angleBetween;
- }
- // 内角的计算(补充180度)
- float innerAngle = 180f - angleBetween;
- // 计算开始角度,这里用的是 x 轴为起始点来计算
- float startAngle = currentAngle;
- //Debug.Log($"Index {i}: startAngle = {startAngle}, innerAngle = {innerAngle}");
- // 根据当前角的位置调整内角和起始角
- DrawFanAtCorner(vh, currentPos, startAngle, innerAngle, fanInnerRadius, fanOuterRadius, fanSegments, fanColor);
- // 计算箭头的中心角度,确保角度在 0-360° 范围内
- float centerAngle = (startAngle + innerAngle / 2) % 360f;
- float radians = Mathf.Deg2Rad * centerAngle;
- // 计算箭头位置:在扇形外半径的基础上延长一定距离(箭头偏移量)
- float arrowOffset = fanOuterRadius + arrowDis;
- Vector2 arrowPosition = currentPos + new Vector2(Mathf.Cos(radians), Mathf.Sin(radians)) * arrowOffset;
- // 箭头方向
- Vector2 arrowDirection = new Vector2(Mathf.Cos(radians), Mathf.Sin(radians)).normalized;
- // 将箭头信息保存到列表中
- arrowInfos.Add(new ArrowInfo(arrowPosition, arrowDirection));
- // 在所有扇形处绘制箭头
- // 在扇形中心绘制箭头
- if (bDrawArrow)
- {
- // 调试信息
- //Debug.Log($"Index {i}: arrowPosition = {arrowPosition}, startAngle = {startAngle}, centerAngle = {centerAngle}, direction = {arrowDirection}");
- // 绘制箭头
- DrawArrow(vh, arrowPosition, arrowDirection, arrowLength, arrowWidth, arrowHeadHeight);
- }
- }
- }
- private void DrawFanAtCorner(VertexHelper vh, Vector2 center, float startAngle, float angleDegree, float innerRadius, float outerRadius, int segments ,Color color)
- {
- UIVertex vert = UIVertex.simpleVert;
- vert.color = color;
- float angleRad = Mathf.Deg2Rad * angleDegree; // 将绘制角度转换为弧度
- float startAngleRad = Mathf.Deg2Rad * startAngle; // 将起始角度转换为弧度
- float angleStep = angleRad / segments; // 每个扇形段的角度步长
- int initialVertCount = vh.currentVertCount; // 记录当前顶点数量
- // 添加中心点顶点
- vert.position = center;
- vert.uv0 = new Vector2(0.5f, 0.5f); // 中心UV值
- vh.AddVert(vert);
- // 绘制外圈和内圈的顶点
- for (int i = 0; i <= segments; i++)
- {
- float currentAngle = startAngleRad + i * angleStep;
- float cosA = Mathf.Cos(currentAngle);
- float sinA = Mathf.Sin(currentAngle);
- // 外圈顶点
- vert.position = center + new Vector2(cosA * outerRadius, sinA * outerRadius);
- vert.uv0 = new Vector2(0.5f + cosA * 0.5f, 0.5f + sinA * 0.5f);
- vh.AddVert(vert);
- // 内圈顶点
- vert.position = center + new Vector2(cosA * innerRadius, sinA * innerRadius);
- vert.uv0 = new Vector2(0.5f + cosA * innerRadius / outerRadius * 0.5f, 0.5f + sinA * innerRadius / outerRadius * 0.5f);
- vh.AddVert(vert);
- }
- // 创建三角形索引来绘制扇形
- for (int i = 1; i <= segments; i++)
- {
- int baseIndex = initialVertCount + (i - 1) * 2;
- vh.AddTriangle(initialVertCount, baseIndex + 1, baseIndex + 3); // 中心点连接外圈
- vh.AddTriangle(baseIndex + 1, baseIndex + 2, baseIndex + 3); // 内圈连接外圈
- }
- }
- // 新增箭头绘制方法
- private void DrawArrow(VertexHelper vh, Vector2 position, Vector2 direction, float arrowLength, float arrowWidth, float arrowHeadHeight)
- {
- // 标准化方向向量
- direction.Normalize();
- // 反向箭头的方向(箭头指向扇形方向)
- Vector2 reversedDirection = -direction;
- // 箭头尾部位置
- Vector2 basePosition = position - reversedDirection * arrowLength;
- // 计算垂直向量用于箭头宽度
- Vector2 perpendicular = new Vector2(-reversedDirection.y, reversedDirection.x);
- // 让箭头矩形部分的宽度比三角形的底边窄
- float adjustedArrowWidth = arrowWidth * 0.6f; // 调整宽度,使矩形比三角形窄
- // 箭头矩形部分的四个顶点
- Vector2 baseLeft = basePosition + perpendicular * (adjustedArrowWidth / 2);
- Vector2 baseRight = basePosition - perpendicular * (adjustedArrowWidth / 2);
- Vector2 headLeft = position + perpendicular * (adjustedArrowWidth / 2);
- Vector2 headRight = position - perpendicular * (adjustedArrowWidth / 2);
- Vector2 triangleHeadLeft = position + perpendicular * (arrowHeadHeight / 2);
- Vector2 triangleHeadRight = position - perpendicular * (arrowHeadHeight / 2);
- // 顶点颜色
- UIVertex vert = UIVertex.simpleVert;
- vert.color = arrowColor;
- // 先添加矩形部分的顶点
- int rectStartIndex = vh.currentVertCount;
- vert.position = baseLeft;
- vh.AddVert(vert);
- vert.position = baseRight;
- vh.AddVert(vert);
- vert.position = headLeft;
- vh.AddVert(vert);
- vert.position = headRight;
- vh.AddVert(vert);
- // 添加矩形部分的两条三角形索引
- vh.AddTriangle(rectStartIndex, rectStartIndex + 1, rectStartIndex + 2);
- vh.AddTriangle(rectStartIndex + 1, rectStartIndex + 3, rectStartIndex + 2);
- // 然后添加三角形头部的顶点
- Vector2 headPosition = position + reversedDirection * arrowHeadHeight;
- int headStartIndex = vh.currentVertCount;
- vert.position = triangleHeadLeft;
- vh.AddVert(vert);
- vert.position = triangleHeadRight;
- vh.AddVert(vert);
- vert.position = headPosition; // 添加箭头尖端
- vh.AddVert(vert);
- // 添加三角形的索引
- int triangleStartIndex = vh.currentVertCount - 3; // 三角形部分的开始索引
- vh.AddTriangle(triangleStartIndex, triangleStartIndex + 1, triangleStartIndex + 2); // 三角形索引
- }
- //private void LateUpdate()
- //{
- // foreach (var quadObj in quadObjects)
- // {
- // if (quadObj != null)
- // {
- // objectPool.Push(quadObj);
- // }
- // }
- // quadObjects.Clear();
- // for (int i = 0; i < quadsToDraw.Count; i++)
- // {
- // GameObject quadGO = GetQuadObject();
- // if (quadGO != null)
- // {
- // quadGO.SetActive(true);
- // quadGO.GetComponent<RectTransform>().anchoredPosition = quadsToDraw[i];
- // Text quadTextComponent = quadGO.GetComponent<Text>();
- // quadTextComponent.text = quadTextToDraw[i];
- // quadTextComponent.fontSize = quadFontSize;
- // quadTextComponent.color = quadTextColor;
- // quadTextComponent.alignment = TextAnchor.MiddleCenter;
- // }
- // }
- // quadsToDraw.Clear();
- // quadTextToDraw.Clear();
- //}
- //private GameObject GetQuadObject()
- //{
- // if (objectPool.Count > 0)
- // {
- // GameObject quadGO = objectPool.Pop();
- // if (quadGO != null)
- // {
- // quadObjects.Add(quadGO);
- // return quadGO;
- // }
- // }
- // if (quadObjects.Count < ObjectPoolLimit)
- // {
- // GameObject newQuadGO = new GameObject("QuadText", typeof(RectTransform), typeof(Text));
- // newQuadGO.transform.SetParent(this.transform);
- // Text quadTextComponent = newQuadGO.GetComponent<Text>();
- // quadTextComponent.alignment = TextAnchor.MiddleCenter;
- // quadTextComponent.rectTransform.sizeDelta = new Vector2(quadrilateralSize.x, quadrilateralSize.y);
- // quadObjects.Add(newQuadGO);
- // return newQuadGO;
- // }
- // return null;
- //}
- }
- }
|