2025/Unity
Unity - UI용 Line Renderer
rimugiri
2025. 7. 26. 11:57
728x90
일방적인 lineRenderer또한 UI상에서 사용할 수 는있지만 실제로는 게임 오브젝트로 적용되기 때문에 sortting이 제대로 적용되지 않거나 해상도에 따라 위치가 제대로 반영이 안되는 경우가 발생할 수 있다 이를 해결하기 위해 grapic ui를 조정하여 UI 용 line renderer를 제작해 보았다
using System.Collections.Generic;
using System.Net;
using UnityEngine;
using UnityEngine.UI;
public class UILineConnector : Graphic
{
[Header("스프라이트")]
public Sprite lineSprite; // 적용하고자 하는 스프라이트 없을시 일반 텍스쳐 적용
public override Texture mainTexture
{
get
{
return lineSprite == null ? s_WhiteTexture : lineSprite.texture;
}
}
[Header("연결 대상")]
public RectTransform StartPoint;
public RectTransform EndPoint;
[Header("선 속성")]
public float Thickness = 10f;
[Range(2, 100)]
public int SegmentCount = 20;
[Header("곡선 형태 조절")]
[Tooltip("곡선이 휘는 정도")]
public float CurveHeight = 50f;
[Tooltip("곡선이 휘는 방향")]
public Vector2 CurveDirection = Vector2.up;
[SerializeField] private List<Vector2> points;
public IReadOnlyList<Vector2> Points => points; // 지정해줄 위치 값들
protected override void Awake()
{
base.Awake();
}
public void SetPoint(RectTransform startPos, RectTransform endPos)
{
if (startPos != null)
{
StartPoint = startPos;
}
if (endPos != null)
{
EndPoint = endPos;
}
SetVerticesDirty(); // OnPopulateMesh를 호출하여 메쉬를 새롭게 그려준다
}
protected override void OnPopulateMesh(VertexHelper vh)
{
// 기존 매쉬 초기화
vh.Clear();
// 지점이 설정되어 있지 않다면 아무것도 그리지 않는다
if (StartPoint == null || EndPoint == null || SegmentCount < 1)
{
return;
}
List<Vector2> points = new List<Vector2>();
Vector3 startWorldPos = StartPoint.position;
Vector3 endWorldPos = EndPoint.position;
RectTransform ownRect = this.rectTransform;
// 위치를 로컬 좌표로 변환
Vector2 p0 = ownRect.InverseTransformPoint(startWorldPos);
Vector2 p2 = ownRect.InverseTransformPoint(endWorldPos);
// 곡선의 방향 계산
Vector2 curveDirection = (p2 - p0).normalized;
// 시계 반대 방향으로 회전
CurveDirection = new Vector2(-curveDirection.y, curveDirection.x);
Vector2 midPointBase = (p0 + p2) * 0.5f;
Vector2 controlPointOffset = CurveDirection * CurveHeight;
// 곡선의 가운데 지점
Vector2 p1 = midPointBase + controlPointOffset;
for (int i = 0; i <= SegmentCount; i++)
{
float t = (float)i / SegmentCount;
Vector2 point = Mathf.Pow(1 - t, 2) * p0 + 2 * (1 - t) * t * p1 + Mathf.Pow(t, 2) * p2;
points.Add(point);
}
// 각 지점 보간을 위해 각 지점의 법선 벡터를 계산
List<Vector2> normals = new List<Vector2>();
for (int i = 0; i < points.Count; i++)
{
Vector2 normal;
if (i == 0)
{
Vector2 direction = (points[i + 1] - points[i]).normalized;
normal = new Vector2(-direction.y, direction.x);
}
else if (i == points.Count - 1)
{
Vector2 direction = (points[i] - points[i - 1]).normalized;
normal = new Vector2(-direction.y, direction.x);
}
else
{
Vector2 dir1 = (points[i] - points[i - 1]).normalized;
Vector2 dir2 = (points[i + 1] - points[i]).normalized;
Vector2 averageDir = (dir1 + dir2).normalized;
normal = new Vector2(-averageDir.y, averageDir.x);
}
normals.Add(normal * (Thickness / 2));
}
for (int i = 0; i < points.Count - 1; i++)
{
Vector2 pointStart = points[i];
Vector2 pointEnd = points[i + 1];
// 계산된 노멀을 이용하여 위치를 조정해 준다
Vector2 normalStart = normals[i];
Vector2 normalEnd = normals[i + 1];
// 해당 텍스쳐 하나를 구간별로 나눠 적용하는 방식
float uStart = (float)i / (points.Count - 1);
float uEnd = (float)(i + 1) / (points.Count - 1);
UIVertex[] quad = new UIVertex[4];
quad[0] = new UIVertex { position = pointStart - normalStart, color = this.color, uv0 = new Vector2(uStart, 0) };
quad[1] = new UIVertex { position = pointStart + normalStart, color = this.color, uv0 = new Vector2(uStart, 1) };
quad[2] = new UIVertex { position = pointEnd + normalEnd, color = this.color, uv0 = new Vector2(uEnd, 1) };
quad[3] = new UIVertex { position = pointEnd - normalEnd, color = this.color, uv0 = new Vector2(uEnd, 0) };
vh.AddUIVertexQuad(quad);
}
}
}
조금더 자세하게 분석해 보자
1. OnPopulateMesh
UI모양을 그려주는 핵심 함수이다
UI또한 vertex로 구성되어 있는 요소로 모양을 잡아주고, UV텍스쳐를 입혀주는 작업을 해준다
2. 베지어 곡선
// 곡선의 방향 계산
Vector2 curveDirection = (p2 - p0).normalized;
// 시계 반대 방향으로 회전
CurveDirection = new Vector2(-curveDirection.y, curveDirection.x);
Vector2 midPointBase = (p0 + p2) * 0.5f;
Vector2 controlPointOffset = CurveDirection * CurveHeight;
// 곡선의 가운데 지점
Vector2 p1 = midPointBase + controlPointOffset;
for (int i = 0; i <= SegmentCount; i++)
{
float t = (float)i / SegmentCount;
Vector2 point = Mathf.Pow(1 - t, 2) * p0 + 2 * (1 - t) * t * p1 + Mathf.Pow(t, 2) * p2;
points.Add(point);
}
이 부분은 어떤 위치 지점에 이미지를 그려 넣어줄지 위치를 지정해 주는 동작이다
곡선의 형태가 아닌 다른 형태의 선을 표현하고자 한다면 해당 부분이 변화하면 된다
3. 이미지 깨짐 방지 보간
// 각 지점 보간을 위해 각 지점의 법선 벡터를 계산
List<Vector2> normals = new List<Vector2>();
for (int i = 0; i < points.Count; i++)
{
Vector2 normal;
if (i == 0)
{
Vector2 direction = (points[i + 1] - points[i]).normalized;
normal = new Vector2(-direction.y, direction.x);
}
else if (i == points.Count - 1)
{
Vector2 direction = (points[i] - points[i - 1]).normalized;
normal = new Vector2(-direction.y, direction.x);
}
else
{
Vector2 dir1 = (points[i] - points[i - 1]).normalized;
Vector2 dir2 = (points[i + 1] - points[i]).normalized;
Vector2 averageDir = (dir1 + dir2).normalized;
normal = new Vector2(-averageDir.y, averageDir.x);
}
normals.Add(normal * (Thickness / 2));
}
여기에서 0 과 마지막 번째가 아닐때 두 벡터의 평균으로 법선벡터를 설정해 주지 않는다면 서로 틈이 벌어진 이미지가 생성되어 매끄러운 선이 생성되지 않는 문제가 발생하므로 주의하자
4. UV설정 및 선 생성
for (int i = 0; i < points.Count - 1; i++)
{
Vector2 pointStart = points[i];
Vector2 pointEnd = points[i + 1];
// 계산된 노멀을 이용하여 위치를 조정해 준다
Vector2 normalStart = normals[i];
Vector2 normalEnd = normals[i + 1];
// 해당 텍스쳐 하나를 구간별로 나눠 적용하는 방식
float uStart = (float)i / (points.Count - 1);
float uEnd = (float)(i + 1) / (points.Count - 1);
UIVertex[] quad = new UIVertex[4];
quad[0] = new UIVertex { position = pointStart - normalStart, color = this.color, uv0 = new Vector2(uStart, 0) };
quad[1] = new UIVertex { position = pointStart + normalStart, color = this.color, uv0 = new Vector2(uStart, 1) };
quad[2] = new UIVertex { position = pointEnd + normalEnd, color = this.color, uv0 = new Vector2(uEnd, 1) };
quad[3] = new UIVertex { position = pointEnd - normalEnd, color = this.color, uv0 = new Vector2(uEnd, 0) };
vh.AddUIVertexQuad(quad);
}
각 지점마다 사각형의 vertex를 잡아준뒤 각 사각형마자 입힐 이미지를 설정해 주기위해 UV를 설정해 준다
현재 확인해보면 하나의 이미즈를 통해 전체 공간을 표현하기위해 UV를 0 ~ 1까지 잘게 나누어서 설정해 주는데 각 사각형마다 이미지를 사용하고자 하면 UV를 나누는 과정을 생략하면 된다.
728x90