한 줄 요약
해상도가 달라질 때마다 흐트러지던 Unity UI 레이아웃을 Layout Group과 Layout Element를 활용해 화면 크기에 맞게 구성한 과정을 정리했어요.
1. 들어가며
안녕하세요, 쿼리도슈팅에서 PO와 개발을 맡고 있는 정회륜이에요.
게임 UI를 만들다 보면 한 화면에서는 제대로 보이던 레이아웃이 다른 해상도에서는 간격이 벌어지거나 요소의 크기가 어색해지는 문제를 만나게 됩니다.
<쿼리도 슈팅>에서도 여러 화면 크기에 대응하는 UI를 구현하면서 비슷한 문제가 생겼어요.
UI 요소의 위치와 크기를 하나씩 직접 맞추는 방식으로는 해상도가 달라질 때마다 계속 수정해야 했습니다.
이번 작업에서는 이 문제를 해결하기 위해 Unity의 Layout Group과 Layout Element를 활용했어요.
부모 오브젝트에서 UI 요소의 배치를 관리하고, 각 요소가 필요한 크기를 따로 지정하는 방식으로 레이아웃을 구성했습니다.
이번 글에서는 해상도에 따라 UI가 달라지면서 어떤 문제가 생겼는지, 그리고 Layout Group과 Layout Element를 어떤 역할로 나누어 사용했는지 정리해볼게요.
이번 글에서는 유니티에서 UI를 제작하며 사용한 Layout Group과 Layout Element에 관해 이야기해 보려고 해요.
2. 배경
쿼리도슈팅은 안드로이드 출시를 목표로 개발하고 있는 턴제 전략 로그라이크 게임이에요.
모바일 게임은 기기마다 화면의 크기와 비율이 다르기 때문에, 기준 해상도에서 UI를 잘 배치하는 것만으로는 부족해요.
특히 최근에는 다음과 같은 UI를 집중적으로 제작했어요.
- 인게임 상점의 유물 목록
- 캐릭터 도감의 카드 목록
- 휴식 화면의 스쿼드 캐릭터 정보
- 옵션과 알림 팝업
- 구매 목록과 유물 인벤토리
- 로딩 화면의 텍스트와 이미지
이 화면들의 공통점은 콘텐츠의 개수나 텍스트 길이가 항상 같지 않다는 점이에요.
상점에 표시할 유물은 실행할 때 생성되고, 스쿼드 캐릭터 수도 현재 편성 상태에 따라 달라져요. 캐릭터 도감 역시 콘텐츠가 추가될수록 카드의 개수가 늘어나요.
처음에는 피그마 기획안과 기준 해상도를 보면서 각각의 UI 위치를 직접 맞췄어요.
하지만 화면 하나를 수정할 때마다 주변 UI의 위치도 함께 조정해야 했고, 해상도를 바꾸면 다시 확인해야 하는 일이 반복됐어요.
3. 문제 정의
가장 큰 문제는 UI의 위치와 크기를 각각의 RectTransform에 직접 저장하고 있었다는 점이에요.
예를 들어 상점에 유물 카드 네 개를 배치한다면, 처음에는 각 카드의 위치를 직접 맞출 수 있어요.
하지만 카드의 크기나 간격이 변경되면 네 개의 위치를 모두 다시 수정해야 해요. 다섯 번째 상품이 추가되거나 화면 폭이 달라지면 배치 기준도 새로 계산해야 하고요.
이런 방식에서는 다음과 같은 문제가 발생했어요.
- 화면이 좁아지면 버튼과 카드가 겹친다.
- 화면이 넓어지면 필요 이상으로 빈 공간이 생긴다.
- UI 하나의 크기를 바꾸면 주변 요소를 모두 다시 배치해야 한다.
- 실행 중 생성한 아이템의 위치를 코드에서 계산해야 한다.
- 상점과 도감에서 비슷한 배치 코드를 반복해서 작성하게 된다.
- 팝업 애니메이션을 적용하면 자동 레이아웃과 위치가 충돌한다.
처음에는 Anchor 설정만 잘하면 해결될 것이라고 생각했어요.
하지만 Anchor는 UI가 화면의 어느 영역을 기준으로 움직일지 정해 줄 뿐, 여러 자식의 간격이나 크기 우선순위까지 결정하지는 못했어요.
그래서 Anchor로 전체 영역을 잡고, 그 안의 실제 배치는 Layout Group과 Layout Element가 담당하도록 역할을 나눴어요.
4. 해결 과정
처음에는 Layout Group을 추가하면 모든 UI가 자동으로 정렬될 것이라고 생각했어요.
실제로 자식 UI가 일정한 간격으로 배치되기는 했지만, 모든 요소가 같은 크기로 늘어나거나 의도하지 않은 공간을 차지하는 문제가 생겼어요.
이 문제를 해결하기 위해 Layout Group과 Layout Element의 역할을 먼저 구분했어요.
Layout Group은 부모 오브젝트에 추가하고, 자식 UI를 어떤 방향과 간격으로 배치할지 결정하는 컴포넌트예요.
반면 Layout Element는 자식 오브젝트에 추가하고, 자신이 어느 정도의 크기를 필요로 하는지 부모에게 전달하는 컴포넌트예요.
쉽게 표현하면 다음과 같아요.
Layout Group은 “어떻게 배치할 것인가”를 결정하고, Layout Element는 “나는 얼마만큼의 공간이 필요한가”를 전달해요.
쿼리도슈팅에서는 UI의 성격에 따라 Layout Group을 나눠 사용했어요.
상점과 도감처럼 카드가 반복되는 화면에는 Grid Layout Group을 적용했어요. 옵션 버튼이나 하단 행동 버튼처럼 한 방향으로 배치되는 UI에는 Horizontal Layout Group 또는 Vertical Layout Group을 사용했고요.


이후 각 영역에는 Layout Element를 적용해 필요한 크기를 구분했어요.
버튼처럼 터치 영역을 유지해야 하는 UI에는 최소 크기와 선호 크기를 지정했어요. 반대로 화면이 넓어졌을 때 남은 공간을 채워야 하는 목록이나 상세 정보 패널에는 Flexible 값을 사용했어요.
여기서 중요했던 것은 모든 UI의 Flexible 값을 동일하게 설정하지 않는 것이었어요.
모든 자식이 같은 Flexible 값을 가지면 남은 공간을 동일하게 나눠 갖게 돼요. 그러면 고정된 크기를 유지해야 하는 버튼까지 불필요하게 늘어날 수 있어요.
그래서 UI를 다음 세 종류로 구분했어요.
첫 번째는 버튼이나 아이콘처럼 크기를 유지해야 하는 UI예요. 이런 요소는 최소 크기와 선호 크기를 중심으로 설정했어요.
두 번째는 상점 목록이나 캐릭터 상세 패널처럼 남은 공간을 활용해야 하는 UI예요. 이런 영역에는 Flexible 값을 적용했어요.
세 번째는 UI 사이의 빈 공간이에요. 빈 오브젝트에 Layout Element를 추가해 Spacer로 사용했어요.
예를 들어 상단에 재화 정보는 왼쪽, 옵션 버튼은 오른쪽에 배치하고 싶다면 두 요소 사이에 Flexible Spacer를 두는 방식이에요.
상점과 캐릭터 도감에는 Grid Layout Group을 사용했지만, 여기에도 문제가 있었어요.
기본 Grid Layout Group은 카드 크기와 열 개수가 고정돼 있기 때문에 화면 폭이 바뀌면 카드가 잘리거나 한쪽에 빈 공간이 남을 수 있었어요.
이를 해결하기 위해 ResponsiveGridLayout이라는 보조 컴포넌트를 제작했어요.
이 컴포넌트는 현재 콘텐츠 영역의 폭을 확인하고, 최소 카드 크기를 기준으로 한 줄에 들어갈 수 있는 열 개수를 계산해요.
계산된 열 개수는 최소 2열, 최대 5열 범위 안에서 조절하고, 남은 공간에 맞춰 실제 카드 크기를 다시 정해요.
columnCount = Mathf.Clamp(
columnCount,
minColumns,
maxColumns);
코드 자체는 단순하지만, 이 방식을 적용하면서 화면 크기가 달라져도 카드 목록을 다시 손으로 배치하지 않아도 됐어요.
Layout을 적용하면서 예상하지 못했던 문제도 있었어요.
바로 DOTween으로 UI 등장 애니메이션을 적용했을 때였어요.
Layout Group이 관리하는 자식의 위치를 Tween으로 변경하면, 다음 레이아웃 계산에서 원래 위치로 돌아가는 현상이 발생했어요. Layout Group과 Tween이 같은 RectTransform을 동시에 수정하고 있었기 때문이에요.
이를 해결하기 위해 레이아웃이 관리하는 오브젝트와 애니메이션이 관리하는 오브젝트를 분리했어요.
Layout Group
└─ Layout Item
└─ Animation Root
└─ UI Content
부모의 Layout Group은 Layout Item의 위치와 크기를 관리하고, 실제 이동과 페이드 애니메이션은 그 아래의 Animation Root에 적용했어요.
이렇게 구성하니 자동 배치와 등장 애니메이션을 함께 사용할 수 있었어요.

모든 자식이 Layout 계산에 참여해야 하는 것도 아니었어요.
상점 카드의 품절 표시나 선택 테두리, 팝업의 투명한 입력 차단 이미지는 원래 UI 위에 겹쳐져야 해요.
이 요소들이 Layout 계산에 포함되면 실제 카드나 버튼이 옆으로 밀려나게 돼요.
이때는 Layout Element의 Ignore Layout을 사용했어요.
layoutElement.ignoreLayout = true;
이 설정을 사용하면 오브젝트는 부모 안에 그대로 존재하지만, Layout Group이 공간을 계산할 때는 제외돼요.
품절 이미지처럼 화면에는 보여야 하지만 별도의 공간은 차지하면 안 되는 UI에 유용했어요.
마지막으로 동적으로 UI를 생성한 직후에는 레이아웃 계산 시점도 고려해야 했어요.
유니티는 자식 오브젝트가 생성되는 즉시 모든 레이아웃을 계산하지 않아요. 그래서 UI를 생성한 직후 위치나 높이를 읽으면 아직 갱신 전 값이 들어 있을 수 있어요.
특히 생성된 UI의 위치를 기준으로 애니메이션을 실행하면 잘못된 위치에서 시작하는 문제가 발생했어요.
필요한 경우 아이템 생성을 모두 끝낸 뒤 부모 레이아웃을 한 번 갱신하도록 처리했어요.
중요한 점은 아이템 하나를 생성할 때마다 갱신하지 않는 것이었어요. 여러 개를 생성한 뒤 마지막에 한 번만 다시 계산해야 불필요한 연산을 줄일 수 있었어요.
5. 결과
Layout Group과 Layout Element를 적용한 뒤 가장 크게 달라진 점은 UI를 수정하는 기준이 바뀌었다는 것이에요.
이전에는 UI가 어긋나면 각 오브젝트의 좌표와 크기를 하나씩 확인했어요.
지금은 먼저 부모의 배치 방향과 간격을 확인하고, 그다음 자식의 최소·선호·Flexible 크기가 적절한지 살펴봐요.

동적 UI를 생성하는 코드도 단순해졌어요.
이전에는 생성한 카드의 위치까지 코드에서 계산해야 했지만, 지금은 데이터를 기준으로 프리팹을 생성하고 부모에 넣는 작업까지만 담당해요. 실제 배치는 Grid Layout Group이 처리하고요.
프로젝트에 적용한 결과는 다음과 같아요.
- 상점 유물과 도감 카드가 생성 순서에 따라 자동으로 배치됐어요.
- 화면 폭에 따라 Grid의 열 개수와 카드 크기가 달라졌어요.
- Spacer를 이용해 남는 공간을 자연스럽게 분배할 수 있었어요.
- 품절 표시와 선택 효과는 Ignore Layout으로 배치에서 제외했어요.
- Layout과 애니메이션의 RectTransform을 분리해 위치 충돌을 해결했어요.
- UI 크기를 변경할 때 주변 요소의 좌표를 다시 조정하는 작업이 줄었어요.
특히 상점, 도감, 휴식 화면처럼 비슷한 형태의 콘텐츠가 반복되는 UI를 제작할 때 효과를 크게 느낄 수 있었어요.
6. 배운 점
이번 작업을 통해 Layout Group을 추가한다고 바로 반응형 UI가 완성되는 것은 아니라는 점을 배웠어요.
부모가 자식을 자동으로 정렬하더라도 각 자식이 어느 정도의 크기를 유지하고, 어떤 영역이 남은 공간을 가져갈지는 따로 정해야 해요.
처음에는 UI가 예상과 다르게 늘어나면 Layout Group 설정만 계속 수정했어요.
하지만 실제 원인은 자식의 Layout Element에 모든 Flexible 값이 설정돼 있거나, 장식용 이미지까지 레이아웃 계산에 포함돼 있는 경우가 많았어요.
또한 Layout Group과 Tween처럼 서로 같은 RectTransform을 수정하는 기능을 함께 사용하면 역할을 분리해야 한다는 점도 알게 됐어요.
이번 작업에서 정리한 기준은 다음과 같아요.
- Anchor는 전체 기준 영역을 정한다.
- Layout Group은 자식의 배치 규칙을 정한다.
- Layout Element는 자식이 필요한 크기를 전달한다.
- 장식과 오버레이는 Ignore Layout으로 제외한다.
- 애니메이션은 별도의 자식 오브젝트에 적용한다.
- 동적 UI를 모두 생성한 뒤 필요한 레이아웃만 한 번 갱신한다.
이 기준을 세운 뒤에는 UI가 깨졌을 때 어느 부분을 확인해야 하는지도 이전보다 명확해졌어요.
여러분도 모바일 UI를 제작하면서 좌표를 계속 직접 수정하고 있다면, 먼저 화면을 역할별 영역으로 나누고 부모와 자식의 크기 책임을 구분해 보시면 좋을 것 같아요.
결국 반응형 UI는 모든 위치를 자동으로 계산하는 것이 아니라, 각 오브젝트가 지켜야 할 배치 규칙을 정리하는 과정이라고 느꼈어요.
7. 참고 자료
- Unity Manual — Auto Layout
- Unity Manual — Layout Element
- 이전 글: 해상도마다 깨지는 모바일 게임 UI, 레터박스 + Unity Canvas로 잡은 과정

'Project > VESTIGIA' 카테고리의 다른 글
| AI 웹 프로토타입으로 턴제 전투 검증 - 벽 건설을 시간대·콤보 시스템으로 바꾼 과정 | 쿼리도 슈팅 개발일지 #06 (0) | 2026.09.21 |
|---|---|
| 게임 개발 마일스톤 - 기능 목록을 ‘검증 질문’으로 바꾼 이유 | 쿼리도 슈팅 개발일지 #07 (0) | 2026.09.18 |
| [쿼리도 슈팅 #05] 유니티 턴제 전투 타격감 — "밋밋하다"를 애니메이션·이펙트·UI 역할 분담으로 푼 방법 (0) | 2026.07.24 |
| [쿼리도 슈팅 #04] 유니티 턴제 게임 동적 경로 재계산 - 매번 다시 계산하지 않고 맵 버전으로 푼 방법 (0) | 2026.07.17 |
| 벽 건설로 전략을 짜는 로그라이크 SRPG 〈쿼리도 슈팅〉, 7월 출시 예정 (0) | 2026.06.21 |