Unity UI: RectTransform의 심층 이해

Unity UI: RectTransform의 핵심 개념

Unity UI 시스템에서 사용자 인터페이스(UI) 요소의 위치 지정과 크기 조절은 RectTransform 컴포넌트를 통해 이루어집니다. 일반 Transform이 3D 공간에서의 위치, 회전, 크기를 다루는 것과 달리, RectTransform은 캔버스(Canvas) 내에서 UI 요소가 차지하는 사각형 영역을 정의하며 앵커(Anchor), 피벗(Pivot), 오프셋(Offset)과 같은 UI 특화 개념을 도입하여 다양한 화면 크기에 대응하는 반응형 UI를 구현할 수 있도록 지원합니다.

1. 앵커(Anchor) 영역 (`anchorMin`, `anchorMax`)

RectTransform의 앵커는 부모 RectTransform 공간 내에서 자식 RectTransform의 상대적인 기준점을 정의하는 사각형 영역입니다. anchorMinanchorMax는 이 앵커 영역의 최소(좌측 하단) 및 최대(우측 상단) 지점을 부모 RectTransform 크기의 비율(0~1 사이)로 표현합니다.

  • anchorMin (Vector2): 앵커 영역의 좌측 하단 코너가 부모의 좌측 하단으로부터 떨어진 비율입니다. (예: (0,0)은 부모의 좌측 하단, (0.5, 0.5)는 부모의 중앙)
  • anchorMax (Vector2): 앵커 영역의 우측 상단 코너가 부모의 좌측 하단으로부터 떨어진 비율입니다. (예: (1,1)은 부모의 우측 상단, (0.5, 0.5)는 부모의 중앙)

예를 들어, 부모 RectTransform의 크기가 (300, 200)이고, 자식 RectTransformanchorMin이 (0.2, 0.1), anchorMax가 (0.7, 0.9)로 설정되었다면, 앵커 영역은 부모의 좌측 하단에서 (0.2 * 300 = 60, 0.1 * 200 = 20) 떨어진 지점에서 시작하여 부모의 우측 상단에서 (0.3 * 300 = 90, 0.1 * 200 = 20) 떨어진 지점까지 (즉, 부모의 좌측 하단 기준 (0.7 * 300 = 210, 0.9 * 200 = 180) 지점까지) 확장됩니다.

2. 노드 오프셋 (`offsetMin`, `offsetMax`)

offsetMinoffsetMax는 현재 RectTransform의 사각형 영역이 앵커 영역의 좌측 하단 코너와 우측 상단 코너로부터 각각 얼마나 떨어져 있는지를 정의합니다. 이 값들은 노드의 크기와 위치를 앵커 영역에 상대적으로 조절하는 데 사용됩니다.

  • offsetMin (Vector2): 현재 RectTransform의 좌측 하단 코너가 앵커 영역의 좌측 하단으로부터의 상대적 거리입니다. 양수 값은 앵커 영역 안쪽으로 들어오게 합니다.
  • offsetMax (Vector2): 현재 RectTransform의 우측 상단 코너가 앵커 영역의 우측 상단으로부터의 상대적 거리입니다. 음수 값은 앵커 영역 안쪽으로 들어오게 합니다.

만약 offsetMin=(40, 30), offsetMax=(-10, -30)으로 설정되었다면, RectTransform은 앵커 영역보다 작으며, 좌측 및 하단에서 각각 40, 30 유닛만큼, 우측 및 상단에서 각각 10, 30 유닛만큼 안쪽으로 줄어든 형태를 가집니다.

3. 크기 차이: `sizeDelta`

sizeDelta는 현재 RectTransform의 크기와 앵커 영역의 크기 간의 차이를 나타냅니다. 다음 공식을 통해 계산할 수 있습니다.

sizeDelta = 현재RectTransform.rect.size - 앵커영역의크기;
// 또는
sizeDelta = offsetMax - offsetMin;

이전 예시에서 offsetMin=(40, 30), offsetMax=(-10, -30)일 경우, sizeDelta = (-10 - 40, -30 - 30) = (-50, -60)이 됩니다. 이는 RectTransform의 크기가 앵커 영역보다 가로로 50, 세로로 60만큼 작다는 것을 의미합니다.

4. 앵커링된 위치: `anchoredPosition`

anchoredPositionRectTransform의 피벗(pivot)이 앵커 영역 내의 가상 피벗(Virtual Pivot)에 대해 상대적으로 어디에 위치하는지를 나타냅니다. 가상 피벗은 anchorMin, anchorMax, 그리고 pivot 값에 의해 결정됩니다.

Vector2 CalculateVirtualAnchorPivot(Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot)
{
    float virtualX = Mathf.Lerp(anchorMin.x, anchorMax.x, pivot.x);
    float virtualY = Mathf.Lerp(anchorMin.y, anchorMax.y, pivot.y);
    return new Vector2(virtualX, virtualY);
}

anchoredPosition은 다음 공식들로도 유도될 수 있습니다.

  • anchoredPosition = offsetMin + sizeDelta * pivot
  • anchoredPosition = offsetMin * (Vector2.one - pivot) + offsetMax * pivot

만약 앵커가 가로 또는 세로 방향으로 늘어나지 않고(`anchorMin.x == anchorMax.x` 또는 `anchorMin.y == anchorMax.y`) 특정 지점에 고정될 때, sizeDeltaRectTransform 자체의 크기가 되며, anchoredPosition은 앵커 영역 내에서 RectTransform의 피벗이 위치한 좌표를 의미하게 됩니다.

5. `Rect` 구조체

RectTransformrect 속성은 Rect 구조체로, RectTransform이 차지하는 사각형의 크기와 로컬 피벗에 대한 상대적 위치를 정의합니다.

  • rect.position 또는 rect.min: RectTransform의 좌측 하단 코너가 피벗에 대해 상대적으로 위치한 좌표입니다.
  • rect.center: RectTransform의 중앙점이 피벗에 대해 상대적으로 위치한 좌표입니다. (0, 0)일 경우 피벗과 중앙점이 일치합니다.
  • rect.max: RectTransform의 우측 상단 코너가 피벗에 대해 상대적으로 위치한 좌표입니다.
  • rect.size: RectTransform의 너비와 높이입니다.

6. 로컬 위치: `localPosition`

localPosition은 현재 RectTransform의 피벗이 부모 RectTransform의 피벗에 대해 상대적으로 위치한 좌표입니다. 3D TransformlocalPosition과 유사하지만, UI 맥락에서 RectTransform의 피벗을 기준으로 합니다.

localPosition을 활용한 다른 좌표 계산:

  • 자신의 중앙점이 부모의 피벗에 대해 상대적인 좌표: rtf.localPosition + rtf.rect.center
  • 자신의 좌측 하단이 부모의 피벗에 대해 상대적인 좌표: rtf.localPosition + rtf.rect.min
  • 자신의 좌측 하단이 부모의 좌측 하단에 대해 상대적인 좌표: rtf.localPosition + rtf.rect.min + parentRtf.rect.size * parentRtf.pivot

UI 요소 중앙 정렬 메서드

특정 UI 요소의 중심을 다른 UI 요소의 중심에 맞추는 유틸리티 메서드는 다음과 같이 구현할 수 있습니다.

using UnityEngine;
using UnityEngine.UI; // RectTransformUtility를 위해 필요

public static class UiAlignmentHelper
{
    /// <summary>
    /// sourceTransform의 중심을 targetTransform의 중심에 맞춥니다.
    /// </summary>
    /// <param name="sourceTransform">위치를 조정할 RectTransform</param>
    /// <param name="targetTransform">중심을 맞출 기준 RectTransform</param>
    /// <param name="uiCamera">UI를 렌더링하는 카메라 (캔버스 설정에 따라 다름)</param>
    public static void AlignCenterToTarget(RectTransform sourceTransform, RectTransform targetTransform, Camera uiCamera)
    {
        // 1. 타겟 트랜스폼의 로컬 피벗 기준 중심 좌표를 계산합니다.
        Vector3 targetCenterLocalPos = targetTransform.localPosition + targetTransform.rect.center;
        
        // 2. 타겟 트랜스폼의 부모를 기준으로 월드 좌표로 변환된 중심 좌표를 얻습니다.
        Vector3 targetCenterWorldPos = targetTransform.parent.TransformPoint(targetCenterLocalPos);

        // 3. 월드 좌표를 UI 카메라를 통해 스크린 좌표로 변환합니다.
        Vector2 targetCenterScreenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, targetCenterWorldPos);
        
        // 4. 스크린 좌표를 소스 트랜스폼의 부모 기준 로컬 좌표로 변환합니다.
        //    이것은 소스 트랜스폼의 피벗이 위치해야 할 부모 기준 로컬 좌표입니다.
        Vector2 desiredSourcePivotLocalPos;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            (RectTransform)sourceTransform.parent,
            targetCenterScreenPos,
            uiCamera,
            out desiredSourcePivotLocalPos
        );
        
        // 5. 소스 트랜스폼의 최종 localPosition을 설정합니다.
        //    desiredSourcePivotLocalPos는 소스 트랜스폼의 중심이 위치해야 할 곳이므로,
        //    소스 트랜스폼 자체의 rect.center를 빼주어 피벗의 위치를 조정합니다.
        sourceTransform.localPosition = desiredSourcePivotLocalPos - sourceTransform.rect.center;
    }
}

부모와의 여백 계산

RectTransform이 부모 요소로부터 얼마나 떨어져 있는지(여백)를 계산하는 것은 UI 레이아웃에서 중요한 작업입니다.

1. 요소 자체 스케일(Scale) 미고려 시:

  • 왼쪽 여백: anchorMin.x * parentWidth + offsetMin.x
  • 아래쪽 여백: anchorMin.y * parentHeight + offsetMin.y
  • 오른쪽 여백: (1 - anchorMax.x) * parentWidth - offsetMax.x
  • 위쪽 여백: (1 - anchorMax.y) * parentHeight - offsetMax.y

예를 들어, 부모 너비 300, anchorMin.x = 0.2, offsetMin.x = 40이라면, 왼쪽 여백은 0.2 * 300 + 40 = 100이 됩니다.

2. 요소 자체 스케일(Scale) 고려 시:

RectTransformlocalScale이 변경되면, 실제 보이는 크기가 달라지므로 여백 계산에도 영향을 미칩니다. 피벗에 의해 발생하는 추가적인 여백 변화를 고려해야 합니다. 여기서 elementUnscaledWidthrtf.rect.width를 의미합니다.

  • 왼쪽 여백: anchorMin.x * parentWidth + offsetMin.x + pivot.x * (elementUnscaledWidth * (1 - localScale.x))
  • 아래쪽 여백: anchorMin.y * parentHeight + offsetMin.y + pivot.y * (elementUnscaledHeight * (1 - localScale.y))
  • 오른쪽 여백: (1 - anchorMax.x) * parentWidth - offsetMax.x + (1 - pivot.x) * (elementUnscaledWidth * (1 - localScale.x))
  • 위쪽 여백: (1 - anchorMax.y) * parentHeight - offsetMax.y + (1 - pivot.y) * (elementUnscaledHeight * (1 - localScale.y))

여백을 이용한 UI 요소 위치 설정

주어진 여백 값을 기준으로 RectTransform의 위치를 설정하는 것은 특정 레이아웃을 구현하는 데 매우 유용합니다.

1. 커스텀 메서드 구현:

다음은 좌측 및 상단 여백을 기준으로 RectTransformoffsetMinoffsetMax를 계산하여 설정하는 커스텀 메서드입니다. 이 구현은 요소의 localScale을 고려합니다.

using UnityEngine;

public static class UiMarginAdjuster
{
    /// <summary>
    /// RectTransform의 좌측 및 상단 여백을 기준으로 위치를 설정합니다.
    /// </summary>
    /// <param name="targetRectTransform">위치를 조정할 RectTransform</param>
    /// <param name="leftMargin">부모의 좌측 가장자리로부터의 여백</param>
    /// <param name="topMargin">부모의 상단 가장자리로부터의 여백</param>
    /// <param name="parentRect">부모 RectTransform의 Rect 정보 (크기 계산용)</param>
    public static void SetPositionFromTopLeftMargins(RectTransform targetRectTransform, float leftMargin, float topMargin, Rect parentRect)
    {
        Rect elementUnscaledRect = targetRectTransform.rect; // 스케일 적용 전 요소의 Rect
        Vector2 elementPivot = targetRectTransform.pivot;
        Vector3 elementScale = targetRectTransform.localScale;

        Vector2 currentOffsetMin = targetRectTransform.offsetMin;
        Vector2 currentOffsetMax = targetRectTransform.offsetMax;

        Vector2 anchorMinValues = targetRectTransform.anchorMin;
        Vector2 anchorMaxValues = targetRectTransform.anchorMax;

        // X축 (좌측 여백 기반 계산)
        // offsetMin.x는 좌측 여백에서 anchorMin에 의한 거리와 스케일 변화에 의한 피벗 이동을 뺀 값입니다.
        currentOffsetMin.x = leftMargin - 
                             (anchorMinValues.x * parentRect.width) - 
                             (elementUnscaledRect.width * elementPivot.x * (1 - elementScale.x));
        
        // offsetMax.x는 (좌측 여백 + 실제 가시 너비)에서 anchorMax에 의한 거리를 뺀 값입니다.
        // 여기에서 elementUnscaledRect.width * elementScale.x는 요소의 실제 가시 너비(스케일 적용 후)를 의미합니다.
        currentOffsetMax.x = (leftMargin + (elementUnscaledRect.width * elementScale.x)) - 
                             (anchorMaxValues.x * parentRect.width);

        // Y축 (상단 여백 기반 계산)
        // offsetMin.y는 (부모 높이 - 상단 여백 - 실제 가시 높이)에서 anchorMin에 의한 거리를 뺀 값입니다.
        // 여기에서 elementUnscaledRect.height * elementScale.y는 요소의 실제 가시 높이(스케일 적용 후)를 의미합니다.
        currentOffsetMin.y = (parentRect.height - topMargin - (elementUnscaledRect.height * elementScale.y)) - 
                             (anchorMinValues.y * parentRect.height);
        
        // offsetMax.y는 (1 - anchorMax.y)에 의한 부모 거리 + 스케일 변화에 의한 피벗 이동 - 상단 여백을 계산합니다.
        currentOffsetMax.y = ((1 - anchorMaxValues.y) * parentRect.height) + 
                             (elementUnscaledRect.height * (1 - elementPivot.y) * (1 - elementScale.y)) - 
                             topMargin;
        
        targetRectTransform.offsetMin = currentOffsetMin;
        targetRectTransform.offsetMax = currentOffsetMax;
    }
}

2. Unity 내장 메서드: `SetInsetAndSizeFromParentEdge`

RectTransform은 부모의 가장자리로부터의 여백(inset)과 크기를 지정하여 위치를 설정하는 편리한 내장 메서드를 제공합니다. 이 메서드를 사용하면 anchorMinanchorMax 값이 자동으로 조정됩니다.

using UnityEngine;

public class UiElementEdgePositioner : MonoBehaviour
{
    void Start()
    {
        RectTransform elementRectTransform = GetComponent<RectTransform>();
        if (elementRectTransform == null) return;

        // UI 요소의 현재 스케일이 적용되지 않은 크기를 가져옵니다.
        Vector2 currentElementSize = elementRectTransform.rect.size;

        // 부모의 좌측 가장자리에서 100픽셀 떨어지게 설정하고, 요소의 너비는 currentElementSize.x로 유지합니다.
        elementRectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, 100, currentElementSize.x);
        
        // 부모의 상단 가장자리에서 50픽셀 떨어지게 설정하고, 요소의 높이는 currentElementSize.y로 유지합니다.
        elementRectTransform.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Top, 50, currentElementSize.y);

        // 참고: 이 메서드는 실행 후 RectTransform의 anchorMin 및 anchorMax 값을 변경하여 앵커가 해당 가장자리에 고정되도록 합니다.
    }
}

태그: Unity RectTransform UI uGUI Anchors

7월 9일 23:38에 게시됨