
게임 디바이스 해상도 대응: 반응형 UI 자동 테스팅과 스크린샷 캡처 시스템 구축법
Unity 게임의 반응형 UI를 여러 해상도와 화면비에서 자동 검증하는 방법을 정리한다. 디바이스 매트릭스, 스크린샷 캡처, 이미지 비교, CI 실패 보고까지 실무형으로 설명한다.
TL;DR
게임 UI는 해상도 하나만 확인해서는 안전하지 않다. 기준 해상도와 화면비가 다른 디바이스에서 자동으로 화면을 캡처하고 승인된 기준 이미지(Baseline)와 비교하면 잘림·겹침·안전 영역 누락을 빌드 단계에서 발견할 수 있다.
이 글은 Unity 2022 LTS 계열 프로젝트를 예시로 Canvas Scaler, Unity Test Framework, adb 기반 Android 캡처, 픽셀 차이와 SSIM(Structural Similarity) 비교를 묶어 반응형 UI 회귀 테스트 파이프라인을 만드는 방법을 다룬다.
반응형 게임 UI 버그는 왜 실제 디바이스에서 발생하는가?
에디터의 Game 뷰에서 16:9 해상도만 확인하면 문제가 없어 보이기 쉽다. 하지만 실제 서비스 환경에는 19.5:9 세로형 스마트폰, 4:3 태블릿, 노치와 펀치홀을 가진 기기, 폴더블 기기처럼 서로 다른 화면 조건이 공존한다.
대표적인 원인은 다음과 같다.
| 원인 | 증상 | 자동 테스트 관점의 검출 방법 |
|---|---|---|
| 고정 좌표 배치 | 버튼이 화면 밖으로 밀림 | 화면 경계 픽셀과 UI 요소 위치 검사 |
| 잘못된 Anchor | 가로가 넓을 때 패널 간격 붕괴 | 화면비별 스크린샷 비교 |
Canvas Scaler 설정 불일치 | 텍스트 또는 아이콘 크기 과도 변화 | 기준 이미지 대비 영역별 차이 측정 |
| Safe Area 미반영 | 상단 HUD가 노치와 겹침 | 노치 기기 프로필에서 영역 검사 |
| 현지화 문자열 증가 | 버튼 라벨 줄바꿈 또는 잘림 | 언어·해상도 조합 테스트 |
특히 UI 스크린샷 회귀 테스트는 “버튼이 존재하는가”뿐 아니라 “의도한 위치와 크기로 보이는가”를 검증한다는 점에서 일반적인 단위 테스트를 보완한다.
어떤 디바이스 해상도 매트릭스를 선택해야 할까?
모든 실기기를 테스트 대상으로 만들 필요는 없다. 먼저 화면비, 밀도, 안전 영역이라는 변동 축을 대표하는 프로필을 고른다. 운영 데이터가 있다면 실제 활성 기기 비중과 크래시·CS 발생 기기를 우선한다.
| 테스트 프로필 | 논리 해상도 예시 | 화면비 | 중점 확인 항목 |
|---|---|---|---|
| 기준 Android | 1080×1920 | 16:9 | 기준 레이아웃 |
| 긴 화면 Android | 1080×2340 | 19.5:9 | 상하 여백, Safe Area |
| 소형 Android | 720×1280 | 16:9 | 최소 폭, 글자 잘림 |
| 태블릿 세로 | 1536×2048 | 3:4 | 넓은 여백, 패널 확장 |
| 태블릿 가로 | 2048×1536 | 4:3 | 가로 HUD, 메뉴 배치 |
| iPhone 노치 계열 | 1170×2532 | 약 19.5:9 | 노치·홈 인디케이터 영역 |
해상도만이 아니라 방향도 별도 케이스다. 가로 모드를 지원한다면 같은 기기를 세로·가로 각각 캡처해야 한다.

Unity 반응형 UI를 자동 검증 가능한 상태로 만드는 방법
자동화의 첫 단계는 테스트 대상 UI를 결정적인 상태로 만드는 일이다. 서버 응답, 애니메이션, 랜덤 보상, 현재 시간처럼 실행마다 달라지는 요소가 스크린샷에 섞이면 비교 결과가 불안정해진다.
1. 기준 해상도와 Canvas Scaler 정책을 고정한다
Canvas Scaler의 UI Scale Mode는 보통 Scale With Screen Size를 사용하고 Reference Resolution을 디자인 기준값으로 둔다. 예를 들어 세로 게임의 기준이 1080×1920이라면 해당 값을 명시한다.
Match Width Or Height의 값은 UI 스케일에 직접 영향을 준다. Unity의 로그 보간 스케일 개념은 다음처럼 표현할 수 있다.
여기서 w, h는 실제 화면 폭과 높이, w_r, h_r은 기준 해상도, m은 Match Width Or Height 값이다. 값이 0이면 폭을 1이면 높이를 더 우선한다. 이 값은 감으로 정하지 말고 목표 화면비에서 HUD와 콘텐츠 영역이 어떻게 변하는지 캡처로 검증한다.
2. Safe Area 적용을 재사용 가능한 컴포넌트로 분리한다
상단 HUD나 하단 조작 버튼처럼 시스템 UI와 충돌할 수 있는 요소는 Screen.safeArea에 맞춰 별도 컨테이너를 조정한다.
using UnityEngine;
[RequireComponent(typeof(RectTransform))]
public sealed class SafeAreaFitter : MonoBehaviour
{
private RectTransform rectTransform;
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
Apply();
}
public void Apply()
{
Rect safeArea = Screen.safeArea;
Vector2 min = safeArea.position;
Vector2 max = safeArea.position + safeArea.size;
min.x /= Screen.width;
min.y /= Screen.height;
max.x /= Screen.width;
max.y /= Screen.height;
rectTransform.anchorMin = min;
rectTransform.anchorMax = max;
}
}
에디터와 일부 에뮬레이터에서는 실제 노치 영역이 재현되지 않을 수 있다. 따라서 Safe Area 로직은 별도 테스트 훅으로 Rect 값을 주입하거나 노치가 있는 실기기·클라우드 디바이스 팜에서 최종 확인하는 편이 안전하다.
3. 테스트 전용 화면 상태를 만든다
스크린샷 테스트는 로그인, 튜토리얼, 매치메이킹을 거치지 않고 특정 화면을 즉시 열어야 유지보수하기 쉽다. 개발 빌드에서만 동작하는 딥링크 또는 명령행 인자를 두고 예를 들어 --ui-test=settings로 설정 화면을 진입시키는 방식을 권장한다.
public interface IUiTestRoute
{
void Open(string screenId);
}
public sealed class UiTestBootstrap : MonoBehaviour
{
[SerializeField] private MonoBehaviour routeProvider;
private void Start()
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
string[] args = System.Environment.GetCommandLineArgs();
foreach (string arg in args)
{
if (!arg.StartsWith("--ui-test=")) continue;
(routeProvider as IUiTestRoute)?.Open(arg[10..]);
}
#endif
}
}
외부 API 호출은 mock 데이터로 대체하고 로딩 스피너와 카운트다운은 멈춘 상태에서 캡처한다. 목적은 게임 플레이 검증이 아니라 재현 가능한 UI 렌더링 검증이다.
스크린샷 캡처와 이미지 비교는 어떻게 구현할까?
권장 구조는 “테스트 실행 → 안정화 대기 → 캡처 → 기준 이미지 비교 → 결과물 업로드”다. 테스트가 실패했을 때에는 기준 이미지, 실제 이미지, 차이 이미지를 함께 남겨야 원인 판단이 빨라진다.
flowchart LR
A[CI 빌드] --> B[디바이스 또는 에뮬레이터 실행]
B --> C[테스트 화면 진입]
C --> D[애니메이션 및 비동기 로딩 안정화]
D --> E[스크린샷 캡처]
E --> F[Baseline과 이미지 비교]
F -->|통과| G[테스트 성공]
F -->|실패| H[Actual Diff 첨부 및 빌드 실패]
Step 1. 화면이 안정화된 뒤 캡처한다
Unity 플레이 모드 테스트에서는 최소 한 프레임 이상 기다린 뒤 캡처한다. 폰트 아틀라스 생성, 주소 기반 에셋 로딩, 레이아웃 재계산이 끝나기 전에 찍으면 간헐 실패가 발생할 수 있다.
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
public sealed class SettingsScreenVisualTests
{
[UnityTest]
public IEnumerator SettingsScreen_CapturesAtReadyState()
{
UiTestRouter.Open("settings");
yield return new WaitForEndOfFrame();
yield return new WaitForSecondsRealtime(0.2f);
ScreenCapture.CaptureScreenshot("Artifacts/settings-actual.png");
Assert.Pass("스크린샷은 후처리 비교 단계에서 검증한다.");
}
}
ScreenCapture.CaptureScreenshot는 파일 쓰기가 비동기적으로 끝날 수 있다. CI에서 즉시 파일을 읽어야 한다면 파일 생성 완료를 폴링하거나 플랫폼별 캡처 명령을 사용해 완료 시점을 보장해야 한다.
Android에서는 다음과 같이 adb exec-out screencap -p로 PNG를 표준 출력에 받을 수 있다.
adb -s emulator-5554 exec-out screencap -p > Artifacts/settings-actual.png
Step 2. Baseline 이미지와 비교 기준을 정한다
완전한 픽셀 일치는 GPU, 폰트 래스터라이징, 색상 처리 차이 때문에 너무 엄격할 수 있다. 그래서 보통은 무시 영역(mask), 픽셀 허용 오차, 유사도 임계값을 함께 사용한다.
가장 단순한 픽셀 차이 비율은 다음과 같다.
SSIM은 밝기·대비·구조의 유사도를 함께 평가한다. UI 회귀 테스트에서는 픽셀 차이 비율로 위치 변화에 민감하게 반응하고 SSIM으로 미세한 렌더링 차이에 과민 반응하지 않도록 보조 판단하는 구성이 실용적이다.
| 비교 방식 | 장점 | 주의점 | 적합한 대상 |
|---|---|---|---|
| 절대 픽셀 차이 | 구현이 단순하고 위치 이동에 민감함 | 폰트·안티앨리어싱에 취약 | 아이콘, 고정 HUD |
| 허용 오차 픽셀 차이 | 소량의 색상 흔들림을 흡수 | 큰 흐림도 놓칠 수 있음 | 일반 UI 화면 |
| SSIM | 시각적 구조 차이를 반영 | 위치가 조금만 바뀌어도 해석이 어려울 수 있음 | 전체 화면 보조 지표 |
| 영역별 비교 | 변경 의도를 좁게 관리 | 마스크 관리 비용 발생 | 광고 슬롯, 동적 배너 제외 |
동적으로 바뀌는 플레이어 이름, 서버 시간, 광고 배너는 마스크 처리한다. 단, 중요한 영역을 광범위하게 가리면 테스트 가치가 사라지므로 마스크는 최소 영역으로 제한한다.

Step 3. CI에서 실패 결과를 사람이 바로 판단할 수 있게 만든다
CI 작업은 각 디바이스 프로필과 화면 ID의 조합으로 실행한다. 실패 시 이미지 세 장을 아티팩트로 보관한다.
baseline.png: 리뷰를 통과한 기준 화면actual.png: 현재 빌드의 캡처 화면diff.png: 차이 픽셀을 강조한 결과
테스트 이름에는 화면과 프로필을 모두 포함한다. 예를 들어 Inventory_1080x2340_ko-KR처럼 기록하면 실패 로그만으로도 어떤 조건의 문제인지 알 수 있다. 변경이 의도된 경우에는 PR 리뷰에서 actual.png를 확인한 후에만 Baseline을 갱신한다. 자동으로 기준 이미지를 덮어쓰면 회귀 테스트가 버그를 승인하는 통로가 될 수 있다.
자동 스크린샷 테스트의 불안정성을 줄이는 체크리스트
- 테스트 시작 시 네트워크를 끄거나 모든 응답을 로컬 fixture로 고정한다.
- 무작위 보상, 실시간 시계, 원격 설정, A/B 실험 값을 테스트 모드에서 고정한다.
- 애니메이션은 완료 상태로 이동하거나 테스트 동안 비활성화한다.
- OS 글꼴 크기와 디스플레이 배율을 디바이스 프로필별로 고정한다.
- 색 공간, 렌더 파이프라인, 품질 등급을 CI와 로컬에서 동일하게 맞춘다.
- 실패 이미지와 비교 임계값을 빌드 로그에 남긴다.
자주 묻는 질문 (FAQ)
픽셀 비교만으로 반응형 UI 테스트를 해도 될까?
가능하지만 권장하지 않는다. 픽셀 비교는 위치와 크기 회귀에는 강하지만 렌더링 미세 차이에 취약하다. 허용 오차, 마스크, SSIM을 함께 두고 중요한 화면은 실제 디바이스 확인으로 보완하는 것이 좋다.
Baseline 이미지는 언제 갱신해야 할까?
UI 변경이 의도된 PR에서 실제 캡처와 변경 목적을 검토한 뒤 갱신한다. 테스트 실패를 없애기 위해 일괄 갱신하면 기존 품질 기준을 잃는다.
모든 화면과 모든 해상도를 매 빌드마다 실행해야 할까?
아니다. PR 단계에서는 핵심 화면과 대표 프로필만 실행하고 야간 빌드나 릴리스 후보에서는 전체 화면·언어·디바이스 매트릭스를 실행하는 계층형 전략이 비용 대비 효과적이다.
정리
반응형 UI 품질은 특정 해상도에서 눈으로 확인하는 과정만으로 유지하기 어렵다. 대표 디바이스 매트릭스, 결정적인 테스트 상태, Baseline 이미지 비교, 실패 아티팩트 보관을 연결하면 UI 회귀를 배포 전에 반복 가능하게 잡아낼 수 있다.
처음에는 로그인 화면, HUD, 인벤토리처럼 사용 빈도와 변경 빈도가 높은 화면 3개부터 시작하자. 테스트 범위를 작게 유지한 채 실패 결과를 빠르게 읽을 수 있도록 만드는 것이, 오래 유지되는 QA 자동화 시스템의 출발점이다.


