Unity
게임 엔진으로 키오스크를 만들었다고? — Unity로 상용 포토부스를 출시하기까지 삽질의 기록
김*겸··수정됨 2026.07.22

"Unity로 키오스크요? 미쳤..."
포토 키오스크. 네컷사진, 인생네컷 — 다들 한 번쯤은 찍어봤을 것이다. 그런데 그 안에 돌아가는 소프트웨어를 Unity 게임 엔진으로 만들었다고 하면?
"차라리 WPF나 Electron 쓰지 왜 Unity를?"이라는 반응이 정상이다. 하지만 이 프로젝트는 실시간 웹캠 프리뷰, ML 기반 배경 제거, 드래그 앤 드롭 스티커 편집, 애니메이션 UI 전환을 전부 하나의 앱에서 돌려야 했다. 그 순간 Unity가 "미친 선택"에서 "유일한 선택"이 됐다.
프로젝트 구조: 12개 화면, 하나의 씬
Ready → Mode → Chroma → Select → Quantity → Payment → WaitingForPayment
→ Guide → Filming → CutWindow → Sticker → Printing
키오스크의 전체 플로우는 12단계 상태 머신으로 돌아간다. 그런데 Unity 씬은 딱 하나. 씬 전환 없이 GameManager 싱글톤이 상태를 바꾸면, FadeAnimationCtrl이 23개의 페이드 상태를 라우팅하면서 패널을 전환한다.
왜 단일 씬인가? DontDestroyOnLoad 지옥을 피하기 위해서다. 리셋이 필요하면 그냥 SceneManager.LoadScene(현재씬)으로 씬을 통째로 다시 로드한다. 거대한 리셋 함수 대신, Unity의 씬 로딩 자체가 리셋 메커니즘이 된 셈이다.
함정 1: "1500줄짜리 인쇄 컨트롤러" — 사진 한 장 뽑는 게 이렇게 어렵다고?
PrintController.cs — 이 파일 하나가 1,500줄이다. "사진 찍어서 프린터로 보내면 끝 아닌가?"라고 생각했다면, 현실은 이렇다:
캡처 전략: 3단계 폴백
순서방식설명1순위HiRes ScreenCapture (8x)1920×1080 화면을 15,360×8,640으로 캡처 후 크롭2순위RenderTexture 직접 합성Linear 컬러 스페이스에서 색이 날아가는 문제로 기본 비활성화3순위ReadPixels 폴백최후의 수단
"임시 스케일업" 트릭
캡처 직전에 대상 UI를 루트 캔버스로 이동시키고 2배로 확대한 뒤 스크린샷을 찍고, 즉시 원래 자리로 되돌린다. 화면 레이아웃은 안 바뀌는데 픽셀 밀도는 2배가 되는 마법. 화면 밖으로 삐져나가는 걸 방지하는 가드까지 구현했다.
이미지 처리 파이프라인
캡처 후에도 끝이 아니다:
미러링 → 회전 보정 → 방향 자동 감지 → 리샘플(3200×4800) → 센터 크롭
→ CPU 샤프닝(Unsharp Mask) → 프린터용 변환 → QR용 리사이즈
DS-RX1 프린터는 세로 방향만 받는다. 가로 사진이면? 90도 돌려서 보내야 한다. 여기서 MirrorX, MirrorY, Rotate90, Rotate180 조합이 모드(세로/가로)에 따라 다르게 적용된다. 이거 하나 잘못되면 사진이 거울상으로 나오거나 뒤집혀서 출력된다. 전부 CPU 픽셀 연산(GetPixels32/SetPixels32)으로 처리한다.
함정 2: "프린터 다이얼로그가 뜨는데 어떻게 닫지?" — Win32 API 해킹
프린터에 인쇄 명령을 보내면 Windows가 "사진 인쇄" 다이얼로그를 띄운다. 키오스크에서 이건 치명적이다 — 고객이 마우스로 "확인"을 눌러야 한다고?
해결책: Win32 API P/Invoke로 다이얼로그를 자동으로 닫는다.
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
FindWindow로 다이얼로그 창을 찾고 → SetForegroundWindow로 포커스를 가져온 뒤 → keybd_event로 Enter 키를 프로그래밍적으로 눌러버린다. 게임 엔진에서 Windows 키보드 이벤트를 시뮬레이션하는 날이 올 줄은 몰랐을 것이다.
최종적으로는 전용 PhotoPrinterBridge.exe를 외부 프로세스로 실행하는 방식으로 진화했다. Unity 코루틴이 매 프레임 Process.HasExited를 폴링하면서 타임아웃 시 Process.Kill()로 안전하게 종료한다.
함정 3: "Linear 컬러 스페이스에서 사진이 하얗게 빠진다"
Unity를 Linear 컬러 스페이스로 설정하면 렌더링 품질이 올라가지만, RenderTexture로 캡처한 이미지가 감마 보정 없이 저장되면서 사진이 씻겨나간 것처럼 하얗게 나온다.
// linear = false를 명시적으로 설정해서 sRGB로 강제
new Texture2D(2, 2, TextureFormat.RGBA32, false, /* linear: */ false)
이 false 하나가 정상적인 출력물과 폐지의 차이다. RenderTexture 방식은 아예 비활성화하고 HiRes ScreenCapture 경로를 기본으로 전환했다.
함정 4: "AWS SDK 없이 클라우드에 올려야 한다" — SigV4 서명 직접 구현
촬영한 사진을 QR 코드로 다운로드받을 수 있게 하려면 클라우드에 업로드해야 한다. 네이버 클라우드(NCP) Object Storage를 사용하는데, Unity에는 AWS SDK가 없다.
AWS Signature V4 서명 체인을 처음부터 직접 구현했다:
HMAC-SHA256(kDate) → HMAC-SHA256(kRegion) → HMAC-SHA256(kService) → HMAC-SHA256(kSigning)
→ Canonical Request → String to Sign → Authorization Header
Credential은 StreamingAssets/ncp_secrets.json에서 런타임에 로드한다. SDK 없이 HTTP 서명 프로토콜을 날것으로 구현하는 건... 다시는 하고 싶지 않은 경험일 것이다.
함정 5: "ML 배경 제거가 GPU마다 다르게 동작한다"
VirtualBackgroundController는 Unity Barracuda + MediaPipe Selfie Segmentation 모델로 실시간 인물 세그멘테이션을 수행한다.
문제는 — GPU/드라이버에 따라 마스크가 반전되는 경우가 있다는 것. NVIDIA에서는 인물이 흰색, AMD에서는 인물이 검정색. 같은 모델, 같은 코드인데!
해결책: 자동 반전 감지 시스템.
첫 30프레임 동안 10×10 그리드(100개 포인트)를 샘플링
→ 평균값 > 0.6이면 마스크 반전 적용
추론 백엔드도 ComputePrecompiled 실패 시 CSharpBurst로 자동 폴백한다. GPU 컴퓨트 셰이더가 없는 장비에서도 (느리지만) 동작한다.
함정 6: "웹캠이 죽어도 키오스크는 살아야 한다"
Logitech C922 웹캠을 사용하는데, 장시간 운영하면 웹캠이 먹통이 되는 경우가 있다. USB 연결이 불안정하거나, 드라이버가 응답을 멈추거나.
대응 체계:
4K 자동 협상: 3840×2160으로 요청 → 0.5초 후 실제 해상도 확인 → 4K 실패 시 1920×1080으로 재시작
5초 간격 헬스 체크:
isPlaying == false또는width <= 16(Unity의 미초기화 센티널) 감지 시 자동 재시작사전 초기화:
Start()에서 미리 켜두고CanvasGroup.alpha로 보이기/숨기기 — 촬영 시작할 때 수 초간 기다리는 문제 제거UV 미러링: 전면 카메라 좌우반전을
uvRect.width = -1f로 GPU에서 처리 — 픽셀 복사 0
함정 7: "결제 승인은 됐는데 서버가 죽었다" — 결제 데이터 유실 방지
VAN 단말기(tPayDaemon, 127.0.0.1:6444)와 HTTP 통신으로 카드 결제를 처리한다. 문제는 카드 승인은 성공했는데 백엔드 서버 전송이 실패하는 경우.
카드 승인 → 즉시 로컬 JSON 백업 저장 → 백엔드 전송 시도
→ 실패 시 재시도(설정 가능 횟수/간격) → 앱 재시작 시 미전송 건 일괄 재전송
승인 즉시 로컬에 백업하기 때문에, 네트워크가 끊겨도 결제 데이터는 절대 유실되지 않는다. 고객은 즉시 다음 단계로 진행할 수 있다.
현장 운영을 위한 숨겨진 장치들
키오스크는 개발자 없이 현장 스태프가 운영해야 한다. 그래서 곳곳에 비밀 인터랙션이 숨어있다:
조작기능숨겨진 버튼 10번 탭앱 최소화 (Win32 ShowWindow P/Invoke)결제 영역 10번 탭 (1회)관리자 모드 ON — 결제 10원으로 변경결제 영역 10번 탭 (2회)관리자 모드 OFF — 정상 금액 복구용지 카운터 버튼 10번 탭용지 잔량 690매로 리셋씬 리로드전체 키오스크 초기화
네트워크 핑 체커, 디스크 용량 체크, 콘솔 로그 내보내기(바탕화면에 .txt로 저장)까지 — 원격 접속 없이도 현장에서 문제를 진단할 수 있는 도구들이 내장되어 있다.
스티커 시스템: "게임 엔진이라 가능한 것"
드래그 앤 드롭 스티커 편집이 Unity라서 자연스럽게 구현됐다:
팔레트의 스티커를 드래그하면 원본은 그대로, 클론이 생성돼서 따라온다
PointerEventData.pointerDrag를 클론으로 리다이렉트하는 트릭드롭 존, 삭제 존, 실시간 크기 조절 슬라이더
인쇄 시: 스티커 위치/크기를 프레임 비율에 맞게 재매핑하여 인쇄용 프레임에 복제
결론: "안 되는 걸 되게 하는 기술"
이 프로젝트에서 마주한 진짜 적은 "Unity는 키오스크용이 아니다"라는 전제 그 자체였다.
게임 엔진에서 Win32 API를 호출해서 프린터 다이얼로그를 자동으로 닫고
AWS SDK 없이 클라우드 서명 프로토콜을 날것으로 구현하고
ML 추론 결과가 GPU마다 달라지는 버그를 자동 감지 시스템으로 우회하고
Linear 컬러 스페이스의 함정을
false하나로 극복하고1,500줄짜리 이미지 파이프라인으로 캡처부터 인쇄까지 모든 변환을 처리했다
게임 엔진은 게임만 만드는 도구가 아니다. 현실 세계의 하드웨어와 싸우는 무기가 될 수도 있다.
다만, 다시 하라면... 좀 고민될 것 같다. 🙃
읽기 도구
약 10분 읽기
이 글이 도움이 되었나요?