게임 디바이스 해상도 대응: 반응형 UI 자동 테스팅과 스크린샷 캡처 시스템 구축법

게임 디바이스 해상도 대응: 반응형 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 발생 기기를 우선한다.

테스트 프로필논리 해상도 예시화면비중점 확인 항목
기준 Android1080×192016:9기준 레이아웃
긴 화면 Android1080×234019.5:9상하 여백, Safe Area
소형 Android720×128016:9최소 폭, 글자 잘림
태블릿 세로1536×20483:4넓은 여백, 패널 확장
태블릿 가로2048×15364:3가로 HUD, 메뉴 배치
iPhone 노치 계열1170×2532약 19.5:9노치·홈 인디케이터 영역

해상도만이 아니라 방향도 별도 케이스다. 가로 모드를 지원한다면 같은 기기를 세로·가로 각각 캡처해야 한다.

서로 다른 스마트폰과 태블릿 화면비에서 게임 HUD가 배치된 반응형 UI 비교

Unity 반응형 UI를 자동 검증 가능한 상태로 만드는 방법

자동화의 첫 단계는 테스트 대상 UI를 결정적인 상태로 만드는 일이다. 서버 응답, 애니메이션, 랜덤 보상, 현재 시간처럼 실행마다 달라지는 요소가 스크린샷에 섞이면 비교 결과가 불안정해진다.

1. 기준 해상도와 Canvas Scaler 정책을 고정한다

Canvas ScalerUI Scale Mode는 보통 Scale With Screen Size를 사용하고 Reference Resolution을 디자인 기준값으로 둔다. 예를 들어 세로 게임의 기준이 1080×1920이라면 해당 값을 명시한다.

Match Width Or Height의 값은 UI 스케일에 직접 영향을 준다. Unity의 로그 보간 스케일 개념은 다음처럼 표현할 수 있다.

scale=2(1m)log2(w/wr)+mlog2(h/hr)scale = 2^{(1-m)\log_2(w/w_r)+m\log_2(h/h_r)}

여기서 w, h는 실제 화면 폭과 높이, w_r, h_r은 기준 해상도, mMatch 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), 픽셀 허용 오차, 유사도 임계값을 함께 사용한다.

가장 단순한 픽셀 차이 비율은 다음과 같다.

difference ratio=임계값을 초과한 픽셀 수비교 대상 전체 픽셀 수difference\ ratio = \frac{\text{임계값을 초과한 픽셀 수}}{\text{비교 대상 전체 픽셀 수}}

SSIM은 밝기·대비·구조의 유사도를 함께 평가한다. UI 회귀 테스트에서는 픽셀 차이 비율로 위치 변화에 민감하게 반응하고 SSIM으로 미세한 렌더링 차이에 과민 반응하지 않도록 보조 판단하는 구성이 실용적이다.

비교 방식장점주의점적합한 대상
절대 픽셀 차이구현이 단순하고 위치 이동에 민감함폰트·안티앨리어싱에 취약아이콘, 고정 HUD
허용 오차 픽셀 차이소량의 색상 흔들림을 흡수큰 흐림도 놓칠 수 있음일반 UI 화면
SSIM시각적 구조 차이를 반영위치가 조금만 바뀌어도 해석이 어려울 수 있음전체 화면 보조 지표
영역별 비교변경 의도를 좁게 관리마스크 관리 비용 발생광고 슬롯, 동적 배너 제외

동적으로 바뀌는 플레이어 이름, 서버 시간, 광고 배너는 마스크 처리한다. 단, 중요한 영역을 광범위하게 가리면 테스트 가치가 사라지므로 마스크는 최소 영역으로 제한한다.

기준 이미지와 실제 캡처 이미지, 빨간색 차이 오버레이가 나란히 표시된 UI 회귀 테스트 결과

Step 3. CI에서 실패 결과를 사람이 바로 판단할 수 있게 만든다

CI 작업은 각 디바이스 프로필과 화면 ID의 조합으로 실행한다. 실패 시 이미지 세 장을 아티팩트로 보관한다.

  1. baseline.png: 리뷰를 통과한 기준 화면
  2. actual.png: 현재 빌드의 캡처 화면
  3. 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 자동화 시스템의 출발점이다.

#게임 UI 테스트#반응형 UI#스크린샷 테스트#Unity Test Framework#이미지 비교#CI 자동화

계속 읽어보기

이런 글은 어떠세요?

< Back to Logs