크레이지게임즈 개발자 제출: RAYFRAME을 Basic Launch에 올리며 고친 다섯 가지
크레이지게임즈 개발자 포털에 3대3 메카 슈팅 RAYFRAME을 10월 19일에 제출했고, 지금은 Basic Launch로 일부 이용자에게만 공개돼 있습니다. SDK v3 신호, 전체 화면에서 막히는 Esc 키, 작은 iframe의 글자 크기, 초대 링크와 즉시 멀티, 로그인 감지 순서를 고친 과정과 Basic Launch 판정 조건을 적었습니다.
목차
RAYFRAME은 제가 만드는 웹용 3대3 무중력 메카 슈팅입니다. 2026년 10월 19일 17시 18분에 이 게임을 CrazyGames(크레이지게임즈) 개발자 포털에 제출했습니다.
지금은 Basic Launch 단계입니다. 게임 페이지는 열리지만 검색에 나오지 않고, 일부 이용자에게만 보입니다. 판정은 아직 진행 중이라서 이 글은 제출까지 고친 것과 앞으로 무엇으로 판정받는지까지만 다룹니다.

제출 폼에는 가로 16:9(1920×1080), 세로 2:3(800×1200), 정사각 1:1(800×800) 커버 세 장과 소리 없는 미리보기 영상 두 개를 넣었습니다. 커버에 들어가는 글자는 게임 제목뿐이어야 해서 부제나 'Play' 같은 문구는 넣지 않았습니다.
포털은 독점이 없는 CrazyGames로 골랐습니다#
포털 여섯 곳의 조건은 웹게임 포털 6곳 비교 글에 정리했습니다. 그 표를 RAYFRAME에 대 보니 CrazyGames가 맞았습니다. 이유는 셋입니다.
- 독점 조건이 없어서 rayframe.flowoodz.com을 그대로 운영할 수 있습니다.
- 친구 초대와 방 참가를 SDK로 지원합니다. 문서에는 온라인 멀티플레이 게임의 장기 리텐션이 싱글 게임의 2배라고 적혀 있습니다.
- 판이 끝나면 같은 사람들끼리 다음 판을 바로 시작할 수 있어야 한다는 요구사항이 있는데, RAYFRAME의 사용자 방은 원래 그렇게 동작합니다.
대가도 있습니다. CrazyGames에 먼저 올리면 Poki와는 독점 계약을 맺을 수 없고, 한 번에 받는 라이선스비 계약만 남습니다. 저는 그 점을 감수했습니다.
이용자 대부분이 영어권이고, 기본 심사 항목에도 영어 지원이 들어 있습니다. 그래서 한국어만 있던 로비와 경기 화면에 영어를 더하고, 브라우저 언어로 기본값을 고르게 했습니다.
제출 폼에는 외부 게임 서버에 연결한다는 사실도 적었습니다. 게임 파일은 모두 업로드한 빌드 안에 있고, 경기 연결만 flowoodz가 운영하는 게임 서버로 나갑니다.
SDK v3 신호는 프레임 루프 한 곳에서 냅니다#
CrazyGames용 빌드(npm run build:crazygames)만 HTML5 SDK v3 스크립트를 싣습니다. 스크립트를 못 받거나 초기화에 실패하면 웹 빌드와 똑같이 돌아갑니다. SDK 호출이 예외를 던져도 게임이 멈추지 않게 호출마다 감쌌습니다.
포털이 보는 신호는 네 개입니다. loadingStart는 부팅 처음에, loadingStop은 타이틀을 띄운 직후에 한 번씩 냅니다. gameplayStart와 gameplayStop은 플레이어가 실제로 경기를 하고 있는지가 바뀔 때마다 냅니다.
이 두 신호를 내는 곳은 main.ts의 프레임 루프 한 곳뿐입니다. 매 프레임 지금 경기 중인지 계산하고, 값이 바뀔 때만 SDK를 부릅니다. 경기 시작과 끝, 메뉴 열고 닫기가 모두 시작과 멈춤 한 쌍으로 나옵니다.
// platform gameplay signal: on while a match is being played (not on the loading screen, the 메뉴, 설정 or a result)
const play = !!session && session.running && !session.ended && !!hud && !hud.menu.open && screen === 'none';
if (play !== playing) { playing = play; if (play) platform.gameplay.start(); else platform.gameplay.stop(); }Basic Launch에서는 광고가 꺼져 있습니다. 그래서 보상형 광고 함수는 SDK를 부르지 않고 false를 돌려줍니다. 지금 RAYFRAME이 CrazyGames에서 버는 돈은 0원입니다.
Esc는 게임이 받지 못해서 P 키를 더했습니다#
RAYFRAME의 경기 메뉴는 Esc로 열었습니다. 그런데 CrazyGames는 전체 화면을 포털이 직접 제공하고, 게임 안에 따로 전체 화면 버튼을 두는 것을 금지합니다. 전체 화면에서 Esc를 누르면 브라우저가 전체 화면을 끝내는 데 쓰고 페이지로 보내지 않습니다.
품질 가이드의 Restricted Keys 항목도 같은 이야기를 합니다.
Avoid common keys that have other behaviour on web:
Escapecloses fullscreen
그래서 P 키로도 메뉴를 열고 닫게 했습니다. 사용자가 P를 행동 키로 묶어 두었으면 P는 메뉴를 열지 않습니다.
const p = e.code === 'KeyP' && !e.repeat && !Object.values(this.settings().binds).includes('KeyP');제출한 조작법 설명에도 메뉴 키를 P or Esc로 적었습니다.
작은 iframe에서 글자가 5~8px까지 줄었습니다#
CrazyGames는 게임을 전체 화면이 아닌 iframe으로도 띄웁니다. 게임플레이 요구사항에는 기기 픽셀 비율 1에서 글자를 읽을 수 있어야 한다는 조건이 있고, 확인할 크기로 907 x 510, 821 x 462 같은 화면 크기 열 가지를 적어 둡니다.
RAYFRAME의 UI는 1920×1080을 기준으로 비율에 맞춰 줄어듭니다. 821×462와 907×510, 픽셀 비율 1로 열어 보니 HUD와 타이틀, 기체 선택의 작은 글자가 5~8px까지 내려갔습니다. 읽을 수 없는 크기였습니다.
글자 크기 변수마다 11px 하한을 걸었습니다.
--fs-xs: max(11px, calc(16 * var(--u)));
--fs-sm: max(11px, calc(18 * var(--u)));
--fs-md: max(11px, calc(22 * var(--u)));무기 슬롯도 88×52px 아래로는 줄지 않게 했습니다. 1920×1080 화면에서는 계산값이 하한보다 커서 이전과 똑같이 보입니다. 이 수정은 제출 20분 전인 16시 59분 커밋으로 들어갔고, 제출한 빌드에 들어 있습니다.
초대 링크는 SDK가 만든 주소를 씁니다#
웹판에서 방 화면의 「초대 링크」는 지금 페이지 주소 뒤에 ?room=방 코드를 붙여 만듭니다. CrazyGames 안에서는 이 방법이 통하지 않았습니다.
CrazyGames는 게임 파일을 자기 도메인에서 제공해서, 게임이 읽는 location 주소가 플레이어가 보는 게임 페이지 주소와 다릅니다. 그래서 CrazyGames 빌드는 SDK의 inviteLink가 돌려주는 주소를 씁니다.
멀티플레이 요구사항은 이 밖에도 몇 가지를 더 요구합니다. RAYFRAME은 이렇게 맞췄습니다.
- 사용자 방에 있으면
updateRoom으로 방 코드와 참가 가능 여부를 알립니다. 빈자리가 있고 경기나 기체 선택 중이 아닐 때만 참가할 수 있다고 보냅니다. - 포털의 멀티플레이 화면에서 들어와
isInstantMultiplayer가 켜져 있으면 기본 설정의 비공개 방을 바로 만듭니다. 친구가 곧바로 들어올 수 있게 훈련 화면도 건너뜁니다. - 게임을 하는 중에 친구 방에 참가하면 그 방으로 옮깁니다. 온라인 경기나 기체 선택 중에는 나가는 순간 패배가 기록되므로, 옮기지 않고 경기가 끝난 뒤 참가하라고 알립니다.
- CrazyGames 계정으로 로그인한 사람은 CrazyGames 사용자명이 닉네임이 되고, 게임 안에서는 바꿀 수 없습니다. 채팅은 없고 팀원에게는 글자 없는 아이콘 핑만 보냅니다.
포털의 QA 도구에서는 알아 둘 점이 두 가지 있었습니다. 초대 매개변수를 시험하는 칸의 키는 roomName이 아니라 게임이 읽는 room이어야 합니다. 그리고 QA 페이지 주소에 초대 매개변수가 남아 있으면, 다시 실행(Relaunch)할 때마다 계속 그 방으로 들어갑니다.
로그인 감지를 SDK보다 먼저 걸었습니다#
제출 한 시간 전에 하나를 더 고쳤습니다. CrazyGames에서 게스트로 시작한 뒤 로그인하면, 게임이 그 계정으로 다시 접속하지 않았습니다.
원인은 순서였습니다. 로그인 변화를 듣는 addAuthListener를 SDK 초기화가 끝나기 전에 걸고 있었습니다. 그 시점에는 SDK의 user 모듈이 없어서 리스너가 아무 데도 걸리지 않았습니다. 16시 20분 커밋에서 리스너를 SDK 초기화 뒤로 옮겼습니다.
제출 직전 마지막 QA에서는 포털 QA 도구의 SDK 감지 항목 10개가 로그인 리스너까지 모두 초록이었습니다. 로드는 6.8초, 받는 용량은 6.3MB였습니다.
같은 날 실제 폰에서 포털 QR 코드로 열어 본 화면도 정상이었습니다.
Basic Launch는 7일과 500플레이로 판정합니다#
7월에 포털을 비교할 때는 Basic Launch를 2주 테스트로 적었습니다. 지금 Basic Launch 가이드는 기간을 7일에서 21일로 적습니다.
공개된 지 7일이 지나고 500플레이를 넘으면 끝나고, 500플레이를 못 채우면 21일째에 자동으로 끝납니다. RAYFRAME은 10월 19일에 시작했으니 늦어도 11월 9일에 판정이 납니다.
가이드가 성공 기준으로 드는 지표는 세 개입니다.
| 지표 | 가이드의 기준 |
|---|---|
| 평균 플레이 시간 | 10분 이상 |
| 1일차 리텐션 | 10~15% |
| 1분 넘게 플레이한 비율 | 80% 이상 (로드 10초 안, 빌드 20MB 아래 권장) |
지표가 좋으면 Full Launch로 넘어가고, 그때부터 SDK 광고로 수익을 나눕니다. RAYFRAME은 Full Launch 뒤에 결과 화면에서 보상형 광고를 넣을 계획입니다.
걱정되는 것도 있습니다. 3대3은 사람이 모여야 돌아가는데, Basic Launch는 일부 이용자에게만 보입니다.
멀티플레이 요구사항에 따르면 많은 인원이 필요한 멀티플레이 게임은 Basic Launch를 건너뛸 수 있고, 혼자 하는 모드가 있는 게임은 Basic Launch를 거칩니다. RAYFRAME에는 봇과 하는 연습전과 생존전이 있고, Basic Launch로 시작했습니다.
빠른 대전은 20초 뒤 빈자리를 봇으로 채우고, 10월 20일에는 기다리지 않고 봇과 바로 시작하는 버튼도 더했습니다. 이 변화가 1분 넘게 플레이한 비율에 어떻게 나올지는 아직 모릅니다.
아직 남은 일#
- 판정 결과에 대응해야 합니다. 결과가 나오면 이 글에 덧붙이겠습니다.
- 포털에는 업로드 API가 없습니다. 클라이언트를 바꾸면 「Submit new version」으로 빌드 폴더를 다시 올려야 합니다. 가이드에는 Basic Launch 중 업데이트가 자동으로 승인된다고 적혀 있습니다.
- 서버만 바뀌면 다시 올리지 않아도 됩니다. 그래서 10월 20일에 운영 대시보드를 만들 때 CrazyGames 이용자와 웹 이용자를 클라이언트가 아니라 서버가 접속 Origin으로 구분하게 했습니다.
- 프로토콜 버전을 올릴 때는 아직 새 빌드를 받지 못한 CrazyGames 클라이언트가 막히지 않도록, 서버가 이전 버전도 한동안 받아야 합니다.
- 같은 플랫폼 구조로 국내 토스 미니앱 빌드도 만들었습니다. 바꾼 것과 출시를 막은 등급분류는 앱인토스 게임 출시 글에 있습니다.