본문 바로가기
개발/Unity

Unity) 화살표 만들기 (Direction Arrow)

by 테샤르 2025. 6. 13.

화살표 만들기 (Direction Arrow)

간단하게 화살표로 방향이나 위치를 표시하고 싶을때 간단하게 사용하기 위해서 

Graphic을 상속받아서 화살표를 만들었다.

반응형

< 화살표 곡선 >

Sgement Per Curve값에 따라서 곡선의 정도를 처리할 수 있게 했다.

 

 

< Insepctor >

 

 

<  활용도 >

 

포인터를 추가해서 각종 곡선과 해당 움직임을 표시할수 있도록 했고 실질적으로 해당 위치의 좌표를 리턴이 가능하다.

 

< 코드 >

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

public class UIArrow : Graphic
{
	public List<Vector2> pathPoints = new List<Vector2>() {
		new Vector2(0, 0), new Vector2(20, 10), new Vector2(40.8f, 10), new Vector2(48.6f, 20.94f),
		new Vector2(15.6f, 23), new Vector2(-4.08f, 16.2f)
	};

	public float arrowHeadLength = 20f;
	public float arrowHeadAngle = 25f;
	public float lineThickness = 10f;
	public int segmentPerCurve = 30;
	[SerializeField] private float debugSphereSize = 0.05f;

#if UNITY_EDITOR
	private void OnDrawGizmosSelected()
	{
		if (pathPoints == null || pathPoints.Count == 0) return;
		Gizmos.color = Color.yellow;
		foreach (Vector2 point in pathPoints)
		{
			Vector3 worldPos = transform.TransformPoint(point);
			Gizmos.DrawSphere(worldPos, debugSphereSize);
		}
	}
#endif

	protected override void OnPopulateMesh(VertexHelper vh)
	{
		vh.Clear();
		Debug.Log($"[Arrow] pathPoints: {pathPoints?.Count}, curvePts: {GenerateCatmullRomSpline(pathPoints, segmentPerCurve).Count}");
		if (pathPoints == null || pathPoints.Count < 2) return;

		List<Vector2> curve = GenerateCatmullRomSpline(pathPoints, segmentPerCurve);
		if (curve.Count < 2) return;

		UIVertex vertex = UIVertex.simpleVert;
		vertex.color = color;
		void Add(Vector2 pos) => vh.AddVert(new UIVertex { position = pos, color = color });

		// 누적 길이 측정
		float totalLength = 0f;
		for (int i = 1; i < curve.Count; i++)
			totalLength += Vector2.Distance(curve[i - 1], curve[i]);

		float cutoffLength = Mathf.Max(0f, totalLength - arrowHeadLength);

		int vertIndex = 0;
		float currentLength = 0f;
		bool done = false;

		for (int i = 1; i < curve.Count; i++)
		{
			Vector2 p0 = curve[i - 1];
			Vector2 p1 = curve[i];
			float segLen = Vector2.Distance(p0, p1);

			if (!done && currentLength + segLen >= cutoffLength)
			{
				float remain = cutoffLength - currentLength;
				float t = Mathf.Clamp01(remain / segLen);
				p1 = Vector2.Lerp(p0, p1, t);
				done = true;
			}

			Vector2 dir = (p1 - p0).normalized;
			if (dir == Vector2.zero) continue;

			Vector2 normal = new Vector2(-dir.y, dir.x) * (lineThickness / 2f);
			Vector2 v0 = p0 - normal;
			Vector2 v1 = p0 + normal;
			Vector2 v2 = p1 - normal;
			Vector2 v3 = p1 + normal;

			Add(v0); Add(v1); Add(v2); Add(v3);
			vh.AddTriangle(vertIndex, vertIndex + 1, vertIndex + 2);
			vh.AddTriangle(vertIndex + 1, vertIndex + 3, vertIndex + 2);
			vertIndex += 4;

			currentLength += segLen;

			if (done)
				break; // 선을 끊지 않고 마지막 잘린 세그먼트까지 그리고 나면 멈춤
		}

		// 화살촉
		Vector2 tip = pathPoints[pathPoints.Count - 1];
		Vector2 back = curve[Mathf.Max(0, curve.Count - 2)];
		Vector2 direction = tip - back;

		if (direction.sqrMagnitude > 1e-5f)
		{
			direction.Normalize();
			Vector2 arrowBase = tip - direction * arrowHeadLength;
			float angleRad = Mathf.Deg2Rad * arrowHeadAngle;
			float halfBase = Mathf.Tan(angleRad / 2f) * arrowHeadLength;
			Vector2 perp = new Vector2(-direction.y, direction.x);

			Vector2 left = arrowBase + perp * halfBase;
			Vector2 right = arrowBase - perp * halfBase;

			Add(tip);
			Add(left);
			Add(right);
			vh.AddTriangle(vertIndex, vertIndex + 1, vertIndex + 2);
		}
	}


	private List<Vector2> GenerateCatmullRomSpline(List<Vector2> points, int resolution)
	{
		List<Vector2> result = new List<Vector2>();
		int count = points.Count;
		for (int i = 0; i < count - 1; i++)
		{
			Vector2 p0 = (i == 0) ? points[i] : points[i - 1];
			Vector2 p1 = points[i];
			Vector2 p2 = points[i + 1];
			Vector2 p3 = (i + 2 < count) ? points[i + 2] : p2;

			for (int j = 0; j < resolution; j++)
			{
				float t = j / (float)resolution;
				Vector2 pos = 0.5f * (
					2f * p1 +
					(-p0 + p2) * t +
					(2f * p0 - 5f * p1 + 4f * p2 - p3) * t * t +
					(-p0 + 3f * p1 - 3f * p2 + p3) * t * t * t
				);
				result.Add(pos);
			}
		}
		result.Add(points[count - 1]);
		return result;
	}

	public Vector2 GetPointOnPath(float time)
	{
		if (pathPoints == null || pathPoints.Count < 2) return Vector2.zero;

		List<Vector2> curve = GenerateCatmullRomSpline(pathPoints, segmentPerCurve);
		if (curve.Count < 2) return Vector2.zero;

		// 전체 길이 계산
		float totalLength = 0f;
		List<float> segmentLengths = new List<float>();
		for (int i = 1; i < curve.Count; i++)
		{
			float d = Vector2.Distance(curve[i - 1], curve[i]);
			segmentLengths.Add(d);
			totalLength += d;
		}

		float targetLength = Mathf.Clamp01(time) * totalLength;

		// 누적 거리 따라 보간 위치 찾기
		float currentLength = 0f;
		for (int i = 1; i < curve.Count; i++)
		{
			float segLen = segmentLengths[i - 1];
			if (currentLength + segLen >= targetLength)
			{
				float remain = targetLength - currentLength;
				float t = segLen > 0f ? remain / segLen : 0f;
				return Vector2.Lerp(curve[i - 1], curve[i], t);
			}
			currentLength += segLen;
		}

		return curve[curve.Count - 1]; // fallback
	}

}

 

★★★★

 

반응형

댓글