스토어 페이지에 「광고 포함」이라고 적혀 있었다.

프로덕션에 올라간 「오늘 누가 쏘냐?」를 Play 스토어에서 열어 봤을 때다. 앱 정보 한쪽에 작게 붙는 그 표시였다. 이 앱에는 광고가 없다. 광고 SDK 를 넣은 적도 없고, 매니페스트에 광고 ID 권한도 없다. 그런데 스토어는 이 앱이 광고를 보여 준다고 말하고 있었다.

코드에서는 답이 나오지 않는 버그였다. 원인은 Play Console 의 설문 하나였다.

계획이 답이 되어 있었다

Play Console 의 「앱 콘텐츠」에는 광고, 콘텐츠 등급, 대상 연령, 데이터 보안, 광고 ID 선언 같은 정책 설문이 열 개쯤 있다. 스토어 페이지의 「광고 포함」 표시는 이 중 「광고」 설문의 답을 그대로 보여 준다.

이 앱의 광고 계획은 한 번 바뀌었다. 처음 Play 에 올릴 준비를 할 때는 AdMob 을 붙이기로 했다. 그래서 개인정보처리방침에 광고 서비스가 가져갈 수 있는 정보와 광고 ID 를 재설정하는 방법을 미리 넣었다. 스토어 가이드 문서의 설문 답도 「광고 포함」 기준으로 바꿔 뒀다.

그 뒤 광고는 나중에 넣기로 했다. 가이드 문서의 답은 전부 「지금은 없음」으로 되돌렸다. 처리방침은 광고 도입에 대비해 넓게 적힌 채 두었다.

문서는 되돌렸지만 콘솔은 확인하지 않았다. 계획이 바뀌던 사이 어디쯤에서 「광고 있음」에 체크가 들어간 채 남아 있었던 것으로 보인다. 언제였는지는 기록이 없다. 확실한 건 내가 정답이라고 믿던 문서와 실제로 제출된 답이 달랐다는 것이다.

판단 기준은 단순했다. 이 설문은 앞으로의 계획을 묻지 않는다. 지금 올라가 있는 빌드가 광고를 보여 주느냐를 묻는다. 광고 ID 선언도 같은 원리다. 매니페스트에 AD_ID 권한이 없는 빌드를 올려 놓고 「예」라고 답하면 버전이 차단된다. 「광고」 설문은 차단까지는 안 하지만, 틀린 답이 고스란히 사용자 눈앞에 표시된다.

콘솔에서 「없음」으로 바꾸고, 가이드 문서에 「광고를 실제로 넣기 전까지 없음 유지」라고 이유를 적었다. 광고를 넣는 날 광고 ID 선언, 광고 설문, 데이터 보안, 스토어 설명 문구를 함께 바꾸는 목록도 다시 확인했다. 하나라도 빠지면 이번과 반대 방향으로 어긋난다. 바꾼 뒤 열 개의 선언을 문서와 하나씩 대조했다. 이번에는 전부 맞았다.

전체이용가인데 13세 이상

광고를 고치면서 옆 칸인 대상 연령도 다시 봤다. 처음에는 「18세 이상」 하나만 골라 두었다. 아동이 대상에 들어가면 Play 의 가족 정책이 붙으니, 성인용이라고 해 두면 절차가 단순했다.

그런데 이 앱의 실제 쓰임과 맞지 않았다. 커피 내기라고 이름 붙였지만 사다리, 눈치 게임, 369, 초성 퀴즈는 중학생 교실에서도 그대로 통하는 놀이다. 그래서 1315세, 1617세, 18세 이상 세 칸을 골랐다. 13세 미만은 넣지 않았다. 그 칸을 넣는 순간 가족 정책 대상이 되어, 아동용 개인정보 기준과 광고 SDK 인증부터 검토해야 한다.

헷갈리기 쉬운 건 콘텐츠 등급과의 관계다. 이 앱의 IARC 등급은 전체이용가다. 3세부터 쓸 수 있는 앱을 13세 이상 대상이라고 하는 게 모순처럼 보이지만 그렇지 않다. 콘텐츠 등급은 「이 내용이 몇 살부터 괜찮은가」를 묻고, 대상 연령은 「누구를 위해 만든 앱인가」를 묻는다. 어린아이가 봐도 문제없지만, 어린아이를 위해 만든 앱은 아니다.

이 결정을 개인정보처리방침에도 옮겼다. 예전 8항은 「만 14세 미만 아동을 대상으로 하지 않는다」 한 줄이었다. 이걸 고쳐 썼다.

<h2>8. 아동·청소년의 개인정보</h2>
<li>앱의 대상 연령은 만 13세 이상(중학생부터)입니다. 콘텐츠 등급은 전체이용가(IARC 3+)이지만,
    만 13세 미만 아동을 대상으로 만든 앱은 아닙니다.</li>
<li>앱은 나이와 관계없이 이용자의 개인정보를 개발자에게 전송하거나 수집하지 않습니다(1항).
    따라서 만 14세 미만 이용자의 개인정보를 수집할 때 필요한 법정대리인 동의 절차도 없습니다.</li>

13과 14가 섞여 있는 데는 이유가 있다. 13은 Play 의 연령 구간이고, 14는 우리 법에서 법정대리인 동의가 필요해지는 기준이다. 중학교 1학년은 둘 사이에 걸쳐 있어서, 개인정보를 가져가지 않으니 동의 받을 일도 없다는 두 번째 줄이 필요했다. 정산 기능의 계좌 정보 때문에 만 14세 미만은 보호자와 함께 넣어 달라는 줄도 더했다.

서른 자와 여든 자

설문을 바로잡고 나니 스토어 페이지의 나머지가 눈에 들어왔다. 앱 이름은 「오늘 누가 쏘냐? - 커피 내기 파티게임」이었다. 그사이 게임은 열다섯 개가 됐고, 누가 누구에게 얼마를 보낼지까지 계산하는 정산 기능이 붙었다. 이름에는 그 얘기가 없었다.

경쟁 앱 분석 자료를 옆에 펴 놓고 이름, 간단한 설명, 자세한 설명을 다시 썼다. 앱 이름 후보는 「오늘 누가 쏘냐? 커피 내기·정산 게임」이다. 「파티게임」을 빼고 「정산」을 넣었다. 정산 앱을 찾는 검색에도 걸리게 하려는 것이다. 「게임」은 이름만 보고 무엇인지 알도록 남겼다.

Play 에는 키워드를 넣는 칸이 없다. 검색은 이름과 설명 문장에서 읽힌다. 그렇다고 설명에 키워드를 나열하거나 반복하면 메타데이터 정책 위반이다. 그래서 자세한 설명 앞쪽에 「이럴 때 써요」라는 단락을 새로 넣고, 검색될 만한 말을 문장 안에 녹였다.

■ 이럴 때 써요
· 점심 커피 내기, 회식 2차 계산, 모임 벌칙 정하기까지
· 사다리 타기·룰렛·제비뽑기 같은 복불복부터 눈치게임·369 같은 순발력 내기 게임까지
· 더치페이·N빵 계산기를 따로 켤 필요 없이 게임 결과로 바로 정산

검색될 말이 다 들어 있지만 읽으면 그냥 쓰임새 설명이다. 자세한 설명의 첫 세 줄도 바꿨다. 「더보기」 전에 보이는 건 거기까지라, 분위기 한 줄로 시작하면 앱이 하는 일은 접힌 쪽에 숨는다. 새 세 줄은 게임 열다섯 가지, 2~8명, 각자 폰으로 같이 하기, 정산, 회원가입 없음을 다 말한다.

넣지 않기로 한 문장들

다시 쓰는 일의 절반은 빼는 일이었다. 그럴듯한 제안도 앱의 실제 동작이나 정책과 맞춰 보면 쓸 수 없는 것이 꽤 있었다.

「광고 없음」을 이름이나 간단한 설명에 넣자는 제안이 먼저 걸렸다. 이름에는 원래 못 쓴다. Play 메타데이터 정책은 앱 이름, 아이콘, 개발자 이름에 「무료」「1위」 같은 홍보 문구를 금지한다. 간단한 설명에는 쓸 수 있지만 쓰지 않았다. 광고는 나중에 넣기로 했다. 지금 내세우면 그날 거둬야 하고, 그 문구를 보고 설치한 사람은 배신감을 느낀다. 반대 방향으로 같은 실수를 할 뻔했다. 대신 「회원가입 없이」를 썼다. 이건 앞으로도 바뀌지 않을 사실이다.

「모든 결과는 무작위 공정 추첨」이라는 문구도 뺐다. 경쟁 앱 낮은 평점 리뷰에 「한 명만 자꾸 걸린다」는 조작 의심이 가장 많았으니 취지는 맞다. 하지만 열다섯 게임 중 절반 넘게는 연타 대결, 딱 멈춰, 순서 외우기, 초성 퀴즈 같은 실력 게임이다. 「모든 결과가 무작위」는 틀린 말이 된다. 범위를 좁혔다. 사다리는 판마다 가로줄을 새로 깔고, 룰렛은 판마다 걸릴 사람과 바늘이 멈출 자리를 새로 뽑는다. 코드에서 그걸 확인한 다음에야 「사다리·룰렛·제비뽑기는 판마다 새로 섞어요. 폰 주인이라고 유리한 것 없음」 한 줄을 넣었다.

송금 표현도 정리했다. 정산 화면에는 계좌 복사와 카카오페이 송금 링크 열기가 있다. 「카카오페이 송금」이라고 쓰면 앱이 송금을 하는 것처럼 읽히는데, 「금융 기능: 없음」이라고 답한 앱이다. 그래서 늘 「송금 링크 열기」라고 쓰고, 「카카오페이」라는 이름도 기능을 설명하는 문장 안에서만 쓴다. 「3초 만에 결정」도 뺐다. 사다리 애니메이션만 해도 몇 초가 넘는다.

여덟 장에 하나를 넣으려면

스크린샷이 남았다. 휴대전화 칸에는 최대 여덟 장이 들어가고, 이미 여덟 장이 차 있었다. 그런데 비슷한 앱에 없는 같이 하기가 스크린샷에는 첫 장 부제에 「각자 폰으로 같이 하기도」라는 말로만 있었다.

하나를 넣으려면 하나를 빼야 했다. 룰렛을 뺐다. 세 번째 장인 게임 목록의 부제에 이미 「사다리 · 눈치 · 폭탄 · 룰렛 · 초성 퀴즈 …」로 룰렛이 들어 있었다. 새 순서는 타이틀, 같이 하기 로비, 게임 목록, 사다리, 폭탄 돌리기, 결과, 정산, 이번 달 기록이다. 결과 화면 부제도 「독박? N빵? 분담 방식 5가지 중 선택」으로 구체적으로 바꿨다. 순서와 문구는 합성 스크립트의 목록 하나에 있고, 부제는 한 줄에 들어가는 스무 자 남짓으로 맞췄다. 문제는 02_lobby 원본이 없다는 것이었다.

스토어 스크린샷 2번. 「각자 폰으로 같이 하기」 제목 아래 철수의 방 로비가 있고, 방 번호 23과 참가자 다섯 명, 방장 표시, 게임 고르기 버튼이 보인다

소켓 없는 방

스크린샷 원본은 손으로 찍지 않는다. Windows 테스트 빌드를 -storeshots 폴더 인자로 실행하면, 저장된 데이터를 지우고 예시 참가자 다섯 명(철수, 영희, 민수, 지영, 현우)과 지난 판 기록 몇 개를 넣은 뒤 화면을 차례로 띄우며 찍고 종료한다. 캔버스를 1080x1920 렌더 텍스처에 그리니 창 크기와 상관없다. 게임은 「타기 시작!」 같은 글자의 버튼을 찾아 눌러 진행시킨다. 그다음 파이썬 스크립트가 원본 위에 제목과 부제를 얹어 스토어용 여덟 장을 만든다.

로비만은 이 방식으로 찍을 수 없었다. 로비 화면은 실제 방에서만 그려진다. 방장이 되려면 소켓을 열고 같은 네트워크에 방을 알려야 하고, 참가자 목록은 다른 폰이 접속해야 채워진다. 스크린샷 하나에 폰 네 대를 붙일 수는 없었다. 게다가 방 번호는 방장 기기 IP 의 마지막 자리라, 촬영하는 PC 의 주소가 그대로 찍힌다.

그래서 방을 흉내 내는 함수를 만들었다.

public static string HostAddress => _previewAddress ?? NetProtocol.PrimaryAddress();

public static void StartPreview(string address, List<NetPlayer> players)
{
    Leave();
    Mode = RoomMode.Host;
    MyId = 0;
    HostName = players[0].name;
    _hostScreen = "LobbyScreen";
    _previewAddress = address;
    Players.Clear();
    Players.AddRange(players);
    RaiseChanged();
}

소켓은 열지 않는다. 방의 상태만 방장 모드로 만들고, 참가자 목록을 직접 채우고, 화면에 변경을 알린다. 로비 화면은 진짜 방과 구분하지 못하고 평소처럼 그린다.

이걸 위해 기존 코드 두 군데를 고쳤다. 로비 화면은 방 번호를 내 실제 주소에서 바로 계산하던 것을 Room.HostAddress 를 거치게 바꿨다. 평소에는 같은 값이고, 촬영 때만 사설 대역의 예시 주소가 들어가 방 번호 23 으로 찍힌다. 다른 하나는 방장이 화면을 바꿀 때마다 참가자들에게 알리는 부분이다. 미리 보기 방에는 소켓이 없으니 그대로 두면 비어 있는 호스트 객체를 건드려 널 참조 오류가 난다. 호스트가 없으면 보내지 않도록 조건 하나를 넣었다. 촬영이 끝나면 Leave() 로 방을 닫고, 그때 예시 주소도 지운다. 다음 화면부터는 다시 진짜 상태다. 이 함수는 -storeshots 인자로 시작한 촬영 코드만 부르니 폰의 앱에는 영향이 없다.

다시 찍은 여덟 장을 하나씩 열어 봤다. 둘째 장에 철수의 방과 네 명의 참가자가, 여섯째 장에 민수가 낼 13,900원이 있었다.

스토어 스크린샷 6번. 「누가 얼마 낼지 한눈에」 제목 아래 결과 화면에서 민수가 13,900원 벌칙이고, 1~5등과 사람별 금액, 정산하기 버튼이 보인다

이름, 설명, 스크린샷을 한 번에 다 바꾸지는 않기로 했다. 다운로드가 아직 적고, 한꺼번에 바꾸면 무엇이 효과였는지 모른다. 이름과 간단한 설명을 먼저 바꾸고, Play Console 의 획득 보고서에서 스토어 방문자와 설치 전환율, 검색어별 유입을 본 다음 스크린샷으로 넘어간다.

코드 한 줄 안 바꾸고도 스토어는 앱에 대해 틀린 말을 할 수 있었다. 스토어에 적는 말도 결국 앱의 일부였다. 다음 편은 이 연재의 마지막, 1.8 에 함께 실린 초성 퀴즈 「말하기」 버튼 이야기다.