로블록스는 만들라고 하기 전에 공부부터 시켜라

모르는 판에서는 만들라고 시키기 전에 공부하라고 시키는 편이 빠르다. 지시 네 줄 중 세 줄을 공부에 썼고, 첫 코드가 나오기까지 14분이 걸렸고, 81분 뒤에 게임이 돌아갔다.

이 순서를 알게 된 건 앞에서 한 번 엎었기 때문이다. 아들이 브라우저에서 돌아가는 게임을 하나 만들고 나서 다음 걸 요구했다. 로블록스로 만들고 싶다고 했다. 웹게임 때와 같은 방식이면 될 거라고 봤다. AI에게 말로 설명하면 코드가 나오고, 그걸 열면 돌아가는 흐름 말이다. 로블록스에서는 안 됐다. 파일은 나왔는데 스튜디오에서 게임이 되지 않았고, 더 곤란했던 건 왜 안 되는지를 셋 중 아무도 몰랐다는 것이다. 아이도 모르고 나도 모르고 AI도 모르는 상태에서 안 되는 결과물을 붙들고 있었다.

밝은 작업대에 공구가 손도 안 댄 채 정리돼 있고 그 앞에 펼친 설명서만 손때가 묻은 장면

아이가 웹에서 로블록스로 올라갔다

앞선 두 편에서 아들이 AI와 대화해서 만든 건 브라우저 게임이었다. 1편에서 96분 만에 돌아갔고 2편에서 아이가 어떻게 요구를 넣었는지 봤다. 그게 끝나고 아이가 다음으로 지목한 게 로블록스였다. 이유는 단순하다. 친구들이 거기 있다. 브라우저 게임은 링크를 보내야 하고 받은 쪽이 열어야 하지만, 로블록스는 이미 다들 켜 놓고 있는 곳이다.

아이가 만들고 싶다고 한 건 「주츠」였다. 주술회전 세계관을 로블록스로 옮긴 대전 게임인데, 정확히는 그 게임의 아류작을 만들고 싶다는 얘기였다. 원작 게임을 아이는 이미 몇 십 시간 해본 상태였고, 뭐가 재밌는지에 대해서는 나보다 훨씬 정확한 감각을 갖고 있었다.

시도는 ChatGPT로 했다. 웹게임 때와 같은 도구였고 같은 방식이었다. 결과는 안 됐다. 이 부분은 대화 원문을 지금 갖고 있지 않다. 웹 쪽 대화라 내 PC에 남아 있지 않고, 로컬에 남은 유일한 흔적은 나중에 내가 다른 창에 친 한 줄이다. 그래서 여기서는 "안 됐다"까지만 쓴다. 어느 단계에서 어떻게 무너졌는지를 재구성하면 그건 기록이 아니라 창작이 된다.

다만 안 된 이유의 성격은 짐작이 간다. 로블록스 게임은 우리가 아는 의미의 파일이 아니다. 서버 한 대와 클라이언트 여러 대가 같은 인스턴스 트리를 각자 들고 있고, 엔진이 그 사이를 자동으로 복제한다. 스크립트를 어느 폴더에 두느냐에 따라 실행 여부가 갈린다. 이걸 모르면 "코드는 맞는데 아무 일도 안 일어나는" 상태에 정확히 도착한다. 문법 오류가 나면 차라리 낫다. 오류 메시지가 다음 행동을 알려주기 때문이다.

지시 네 줄 중 세 줄을 공부에 썼다

8월 4일 밤 9시 15분에 내가 직접 붙었다. Claude Code를 열고 처음 친 게 이거다. 오타를 포함해 원문 그대로다.

로블록스용 게임 그러니까 로블록스 스튜디오에서 개발해서 로블록스에서 돌아가는 게임을 만들고 싶어.
구체적으로 어떤 게임이냐면 주츠 라고 주술회선을 로블록스로 옮긴 게임이야. 이거랑 비슷한 아류작

  1. 로블록스 스튜디오에 대해서 공부해봐.
  2. 로블록스 게임 제작법을 공부해봐
  3. 주츠 게임에 대해서 분석해봐.
  4. 실제 플레이가 가능한 게임을 만들어.

네 줄인데 만들라는 건 마지막 한 줄뿐이다. 앞의 세 줄은 전부 공부하라는 말이다.

이렇게 쓴 건 전략이라기보다 체념에 가까웠다. 로블록스에 대해 내가 아는 게 없으니 뭘 어떻게 만들라고 지시할 수가 없었다. 웹게임 때는 "총은 빼자" 같은 말을 할 수 있었다. 화면에 뭐가 나와야 하는지 내가 알기 때문이다. 로블록스는 그게 안 됐다. 요구를 구체화하려면 그 판에서 뭐가 가능한지를 알아야 하는데 그걸 몰랐다. 그래서 요구를 구체화하는 대신 공부를 지시했다.

첫 코드가 나오기까지 14분이 걸렸다

작업 기록이 시각과 함께 남아 있다. 처음 14분에 무슨 일이 있었는지 그대로 옮기면 이렇다.

21시 15분에 위 네 줄을 입력했다. 21시 16분부터 21분까지 웹 검색 6회와 문서 8건 열람이 이어졌다. 21시 25분에 docs/01-로블록스-스튜디오-학습노트.md 가 나왔고, 26분에 docs/02-게임제작-워크플로우.md, 27분에 docs/03-주츠-게임-분석.md, 28분에 docs/04-게임-설계서.md 가 이어졌다. 첫 코드인 Config.lua 는 21시 29분이다.

착수하고 14분 동안 코드가 한 줄도 안 나왔다. 검색 여섯 번 하고 문서 여덟 개를 읽고 나서 마크다운 네 편을 쓴 다음에야 .lua 파일이 처음 생겼다.

읽은 여덟 개가 뭐였는지도 남아 있다. 로블록스 공식 문서, rbx-dom 저장소의 XML 포맷 명세, 그리고 DevForum 스레드 두 개다. DevForum 쪽 두 개는 검색어가 이렇다.

devforum Motor6D.Transform does nothing when no animation playing Animator must be stepped RunService.Stepped

이 검색어가 나왔다는 건 이미 첫 번째 함정을 예상하고 있었다는 뜻이다. 그 함정이 실제로 이 프로젝트의 최대 난관이 됐다.

학습노트에 뭐가 적혔나

21시 25분에 나온 219줄짜리 학습노트가 이 프로젝트에서 제일 중요한 파일이다. 코드보다 먼저 나왔고, 이후 모든 판단의 근거가 됐다. 몇 개만 옮긴다.

복제 경계. ServerScriptServiceServerStorage는 클라이언트에 존재조차 하지 않는다. 클라이언트 코드가 여기를 참조하면 nil이다. 오류가 아니라 nil이다. 그래서 조용히 아무 일도 안 일어난다.

ModuleScript 캐시. require()될 때 한 번 실행되고 결과가 캐시되는데, 서버와 클라이언트가 각각 따로 캐시한다. 모듈 안에 상태를 두면 서버와 클라이언트가 서로 다른 값을 보게 된다. 이것도 오류가 안 난다.

애니메이션. 커스텀 애니메이션은 로블록스에 업로드해야 AnimationId가 생긴다. 코드만으로는 못 만든다. 우회로는 Motor6D.Transform을 매 프레임 직접 쓰는 것인데, 여기 타이밍 함정이 있다. Animator가 PreAnimation 이후 PreSimulation 이전에 매 프레임 그 값을 덮어쓴다. 따라서 우리 포즈는 PreSimulation에서 써야 이긴다. Stepped에서 쓰면 어긋난다.

거기다 Transform은 C0 좌표계 기준이라 R6와 R15에서 축이 다르다. 오른팔을 앞으로 90도 드는 같은 동작이 R6에서는 Z축 회전이고 R15에서는 X축 회전이다. 이걸 모른 채로 짜면 한쪽 아바타에서만 팔이 제대로 올라가고 다른 쪽에서는 팔이 옆으로 꺾인다.

플레이스 파일 포맷. Rojo 없이 가기로 해서 .rbxlx XML을 직접 생성했다. referent 속성은 파일 안에서 유일해야 하고 "null"이면 안 된다. 스크립트 소스를 넣는 CDATA 블록 안에 ]]> 문자열이 있으면 파일이 깨지므로 빌드 스크립트에서 쪼개 이스케이프한다. ExplicitAutoJoints 메타가 없으면 스튜디오가 파트들을 자기 마음대로 용접해 버린다.

이 목록의 공통점이 하나 있다. 전부 오류를 안 낸다. 문법은 통과하고 파일도 열리는데 게임만 안 되는 종류의 함정이다. 도메인 지식이 없으면 디버깅으로 도달할 수 없는 자리다. 뭘 의심해야 하는지를 모르기 때문이다.

에셋을 하나도 안 올리기로 했다

공부가 끝나고 설계로 넘어가는 자리에서 제약을 하나 걸었다. 로블록스에 아무것도 업로드하지 않는다. 애니메이션도, 사운드도, 텍스처도, 마켓플레이스 모델도 쓰지 않는다.

이유는 아이 때문이다. 에셋을 올리기 시작하면 그 게임은 내 계정에 묶인다. 업로드한 애니메이션에는 ID가 붙고 그 ID는 올린 사람 소유다. 파일을 아이에게 넘겨줘도 아이 쪽에서 열면 절반이 안 나온다. 나중에 아이가 자기 계정으로 뭘 해보려고 할 때 그게 막힌다. 그래서 파일만 있으면 어디서든 열리는 상태를 조건으로 걸었다.

이 제약 하나가 구현을 통째로 바꿨다.

애니메이션은 보통 에디터로 만들어 업로드하고 AnimationId 를 쓰는데, 여기서는 Motor6D.Transform 을 매 프레임 직접 써서 포즈 21종을 냈다. 이펙트는 텍스처를 올려 파티클을 쓰는 대신 PartMaterial=Neon 을 주고 트윈으로 크기와 투명도를 움직였다. 텍스처가 없다. 궤적도 텍스처 트레일 대신 TrailBeam 을 컬러 그라디언트만으로 썼다. 맵은 스튜디오에서 손으로 배치하는 대신 MapBuilder.lua 가 코드로 절차 생성했고, UI 도 GUI 인스턴스를 배치하는 대신 전부 코드로 만들었다. .rbxlx 에 GUI 를 안 넣는다.

대가도 있다. 절차적 포즈는 업로드 애니메이션보다 확실히 투박하다. 사운드는 아예 없다. 타격감이 제일 크게 손해를 본다.

그런데 부수 효과가 하나 있었다. 맵과 UI를 전부 코드로 생성하니 git diff가 읽힌다. 스튜디오에서 손으로 배치했으면 변경 내역이 바이너리 덩어리였을 것이다. 지금은 "석주를 입구 반대쪽 반원에만 세운다" 같은 결정이 코드 한 줄로 남고, 왜 그렇게 했는지도 그 옆에 주석으로 남는다. 나중에 이 게임을 아이와 같이 열어볼 때, 볼 수 있는 게 남아 있다는 뜻이다.

81분 뒤

밤 10시 36분에 내가 친 문장이다. 오타 그대로다.

오 게임이 된다. 쳇지피티가 만든건 안되던데.

착수 21시 15분에서 81분이다. 그 사이에 나온 것은 Luau 스크립트와 파이썬 빌드 도구, 그리고 build/주츠.rbxlx 673KB짜리 파일 하나다. 더블클릭하면 스튜디오가 열리고 F5를 누르면 광장에 캐릭터가 선다.

여기서 정직하게 말해야 할 게 하나 있다. 나는 이 차이가 지시 때문이라고 생각하지만 증명하지 못했다. ChatGPT로 했을 때와 이때는 두 가지가 동시에 바뀌었다. 도구가 바뀌었고 지시가 바뀌었다. 변수를 두 개 같이 움직여 놓고 한쪽이 원인이라고 말할 수는 없다. 증명하려면 ChatGPT에 같은 네 줄을 그대로 넣어봐야 하는데 안 해봤다. 아이가 옆에서 기다리고 있었고 나는 되는 쪽으로 계속 갔다.

그래서 이 글이 주장할 수 있는 건 여기까지다. 도메인 지식이 없는 영역에서 "만들어"만 넣었을 때 안 됐고, "공부해봐"를 세 줄 앞에 붙였을 때 됐다. 그 세 줄이 만든 14분에 무엇이 생겼는지는 파일로 남아 있다.

되는 게임과 재밌는 게임은 다르다

돌아가는 걸 확인하고 다음에 내가 한 질문은 이거다.

  1. 뭐랄까 그냥 더미만 때리면 끝나는거야? 다른 사람이랑 경쟁하는건 없어?
  2. 주츠가 뭔가 아이템이 없고 그냥 이렇게 하는 게임인거야?

기술적으로는 다 됐는데 게임으로는 비어 있었다. 이 질문 뒤에 랭크 1v1과 ELO, 보스 레이드, 주구 9종, 순위표 세 종류가 붙었다. 지금 코드는 Luau 38개 파일에 19,315줄이다. 오늘 다시 세어본 값이고, 프로젝트 문서에는 18,900줄로 적혀 있다. 문서가 코드를 못 따라간 자리다.

아이템을 붙이면서 규칙을 하나 박았다. 무기 9종에 상위 호환을 하나도 두지 않는다. 전부 성향 교환이다. 사거리가 34% 늘면 피해가 10% 깎이고, 가드가 55% 오르면 역시 피해가 깎인다. 전설 등급도 마찬가지여서, 각성이 45% 빨리 차는 무기는 최대 체력을 16% 잃는다. 기준 상태로 아무것도 안 든 「무주」가 그대로 남아 있고 그게 여전히 유효한 선택지다.

이렇게 한 건 아이가 할 게임이기 때문이다. 상위 호환이 하나라도 있으면 게임이 "그거 가진 애와 못 가진 애"로 갈린다. 그 갈림이 돈으로 살 수 있는 거면 더 나빠진다. 그래서 로벅스로는 파워를 팔지 않기로 했다. 데미지·체력·쿨타임·사거리에 영향을 주는 유료 항목이 하나도 없다. 캐릭터별 숙련도는 아예 돈으로 못 산다. 실력의 증거는 돈으로 사면 증거가 아니게 된다.

그리고 스튜디오에서 실제로 눌러본 뒤에 진짜 문제들이 나왔다.

저주가 상점 안까지 쫓아왔다. 추격 해제 조건을 NPC와 표적 사이 거리로 잡아 놨는데, 쫓아가는 동안 그 거리는 계속 가깝다. 그래서 영원히 안 끊긴다. 기준을 NPC의 집 좌표로 바꾸고, 아예 전투 구역을 광장에서 분리해 별도 층을 만들었다.

안내판 글씨가 잘렸다. TextScaled = true가 RichText의 <font size> 태그와 싸운다. 태그가 절대 크기를 강제하는데 TextScaled는 그걸 계산에 안 넣는다. 그래서 가로로 넘치고 잘린다. 맵에 있는 표지판 일곱 개를 전부 고정 크기 + 줄바꿈으로 바꿨다.

전투 구역을 좁혔더니 층 전체가 한꺼번에 달려들었다. 반경 80~90을 52~58로 줄였는데 어그로 거리는 그대로 90이었다. 넓은 광장 기준으로 잡은 숫자였다. 축소가 만든 부작용이다.

전투 구역에 들어가면 즉시 튕겨 나왔다. 입장 지점 좌표가 귀환 발판 좌표와 같았다. 텔레포트하면 발판을 밟는 셈이고, 발판을 밟으면 귀환이다. 같은 버그가 랭크 대기실에도 있었다. 대기 위치 여덟 개 중 2번이 나가기 발판 안에 있어서 두 번째로 큐를 잡는 사람만 즉시 튕겼다.

세 공간이 구별되지 않았다. 광장과 대기실과 전투 구역을 전부 같은 언어로 지었다. 원형 바닥에 네온 링에 유리 벽. 예쁘긴 한데 지금 내가 어디 있는지 알 수가 없다.

G키가 고장 난 줄 알았다. 각성 핸들러가 함수 반환값을 그냥 버리고 있었다. 게이지가 모자라면 아무 반응이 없으니 키가 안 먹는 걸로 읽혔다. 지금은 "각성 게이지 45% — 100% 필요"라고 이유를 띄운다.

폰에서는 화면이 안 보였다. HUD 좌표가 전부 데스크톱 1280×720 기준 고정 픽셀이었다. 폰 뷰포트에서는 같은 값이 화면의 두 배 비중을 먹는다. 거기다 데스크톱에서 "남는 자리"에 흩어 놓은 패널들이 좁은 화면에서 서로 겹치고, 로블록스 기본 UI인 상단 크롬과 좌하단 조이스틱과도 겹쳤다.

정적 검증은 전부 통과한 상태였다

위 일곱 건이 나온 시점에 자동 검사는 전부 초록이었다.

Luau 문법      0건    (공식 luau-compile, 38파일)
교차 참조      OK     (스킬 id·포즈·연출·상품·퀘스트·리모트)
XML 유효       OK     (referent 유일성 포함)

문법 오류 0건이고 스킬 정의와 구현의 연결도 안 끊겼고 XML도 유효하다. 그 상태에서 저주가 상점까지 쫓아오고, 글씨가 잘리고, 들어가면 튕기고, 폰에서는 화면이 안 보였다.

검사가 부실해서가 아니다. 저 세 가지는 각자 맡은 걸 정확히 검사했다. 다만 "발판 좌표와 입장 좌표가 같으면 안 된다"는 건 문법도 참조 정합성도 아니다. 그건 게임을 켜고 문 안으로 걸어 들어가 봐야 나온다.

이게 이 프로젝트에서 두 번째로 크게 배운 것이다. AI에게 공부를 먼저 시키면 코드가 어디서 틀렸는지 알 수 있는 상태까지는 간다. 하지만 뭐가 재미없고 뭐가 불편한지는 거기서 안 나온다. 그건 켜보는 사람 몫이고, 그 사람이 나 아니면 없다.

돌아가는 것과 공개되는 것은 또 달랐다

새벽 0시 27분에 내가 친 말이다.

일단 된거 같은데, 출시하자

여기서부터 한 시간 반이 더 걸렸고, 그 한 시간 반에는 코딩이 한 줄도 없었다.

먼저 배포 가이드를 읽을 수가 없었다. AI가 쓴 문서라 메뉴 이름이 전부 영어였는데 내 크리에이터 허브는 한국어 UI였다. Audience Reach를 찾으라는데 화면에는 「오디언스 도달」이라고 적혀 있다. 그래서 문서를 다시 쓰게 했다. 지금 그 문서에는 표가 하나 붙어 있다. 왼쪽이 한국어 메뉴 이름, 오른쪽이 영어 원문, 그 옆이 어디에 있는지다. 영어를 지운 게 아니라 한국어를 앞에 세우고 영어를 옆에 남겼다. 검색은 영어로 해야 결과가 나오기 때문이다.

그다음이 진짜 관문이었다. 체험을 만들고 파일을 올리면 URL이 발급되는데, 그 상태로는 아무도 못 들어온다. 「오디언스 도달」 화면에 빨간 경고가 떠 있고 거기 「플레이할 수 없는 게임입니다」라고 적혀 있다. 2026년부터 수위 및 규정 준수 설문지를 완료해야 공개 게시가 된다. 전투 게임이니 폭력 표현 항목에 정직하게 답해야 하고, 답한 결과가 연령 등급으로 환산되고, 그 등급이 누가 들어올 수 있는지를 결정한다.

문제는 문서에 적힌 메뉴 위치가 실제 화면과 달랐다는 것이다. 새벽 1시 38분에 내가 친 게 "왜 공개가 안 되냐"였고, 스크린샷을 몇 장 붙이고 나서 1시 48분에 이렇게 썼다.

되따

그리고 마지막이 이름과 썸네일이었다. 게임 제목은 한국어로 등록하고 영어를 부수 항목으로 넣는 구조인데, 문서에는 반대로 적혀 있어서 또 고쳤다. 썸네일은 게임 스크린샷을 그냥 쓰면 안 됐다. 화면에 HUD와 안내 문구가 다 찍혀 있어서 「개발 중 화면」으로 보인다. 그래서 촬영 모드를 하나 새로 붙였다. K를 누르면 UI가 전부 사라지고 카메라가 미리 정해둔 아홉 구도로 옮겨 가고 캐릭터가 포즈로 고정된다. 게임 안에서 게임 홍보 이미지를 찍는 기능이다.

이 한 시간 반에 대해 할 말은 하나다. 게임이 돌아가는 것과 남이 그 게임에 들어올 수 있는 것은 완전히 다른 작업이다. 그리고 뒤쪽은 AI가 공부해서 알려줄 수 있는 범위가 좁다. 화면에 실제로 뜬 메뉴 이름, 빨간 경고 문구, 버튼 위치는 스크린샷을 찍어 보여주기 전까지 AI가 알 수 없었다. 이 구간에서 내가 한 일의 절반은 화면을 캡처해서 붙이는 것이었다.

아직 확인 안 한 것

인수인계 문서에 미검증 항목표가 있다. 우선도 "높음"이 여덟 개 남아 있다. 랭크 1v1 전체 흐름은 2인 테스트를 해야 하는데 아직 안 했다. 대기실이 정말 밀폐됐는지 — 점프와 대시를 조합해 네 방향에서 틈을 찾아봐야 한다. 주구를 들었을 때 손 위치는 추정값이라 무기가 손에서 떠 있거나 파묻힐 수 있다.

그리고 그 표 바로 위 문장이 이렇게 시작한다.

스튜디오에서 실제로 플레이해 본 적이 없다.

이건 지금 사실이 아니다. 그 아래로 실기 피드백이 다섯 라운드 기록돼 있다. 갱신을 못 따라간 문장이 헤더에 남아 있는 것이다. 소소해 보이지만 같은 종류의 문제다. 문서는 합의를 적지만 이행을 보장하지 않는다.

숫자로 정리하면 이렇다. 작업을 여섯 번에 나눠 했고, 착수에서 로블록스 공개까지 약 4시간 33분이 걸렸다. 그중 코드가 안 나온 처음 14분이 나머지를 결정했다고 나는 생각한다.

지금 걸려 있는 것

모르는 영역에서 AI에게 첫 지시를 줄 때 두 가지를 넣는다.

하나. "만들어" 앞에 "공부해봐"를 붙인다. 요구를 구체화할 수 없을 때가 있다. 그 판에서 뭐가 가능한지를 모르면 요구 자체가 안 나온다. 그때 억지로 요구를 지어내는 대신 공부를 지시한다. 내가 한 건 그것뿐이다.

둘. 공부한 걸 파일로 남기게 한다. 이게 더 중요하다. 21시 25분에 나온 학습노트가 없었다면 22시 36분에 안 됐을 때 어디를 의심할지 몰랐을 것이다. 「Animator가 PreSimulation 직전에 덮어쓴다」가 파일에 적혀 있으면, 포즈가 안 보일 때 이벤트를 바꿔 실험해 보라는 다음 행동이 나온다. 적혀 있지 않으면 "왜 안 되지"에서 멈춘다.

파일이어야 하는 이유는 또 있다. AI가 공부한 내용은 그 세션이 끝나면 사라진다. 나는 세션을 여섯 번 갈아탔다. 매번 새 창에서 다시 시작했는데 그때마다 학습노트를 읽고 들어갔다. 대화가 아니라 파일이었기 때문에 그게 가능했다.

그리고 남은 게 하나 있다. 이 게임을 아이가 만들고 싶어 했는데 결국 내가 만들었다. 아이는 옆에서 봤고 테스트를 했고 뭐가 재미없는지를 말했지만, 네 줄짜리 그 지시는 내가 쳤다. 다음에 아이가 자기 손으로 하려면 저 네 줄을 아이가 쓸 수 있어야 한다. 그러려면 "모르는 걸 만들 때는 공부부터 시킨다"를 아이가 알아야 하는데, 그건 게임 만드는 법이 아니라 일하는 법에 가깝다. 그 얘기는 1편에서 루프라고 불렀던 것과 같은 자리로 돌아간다.

댓글

이 블로그의 인기 게시물

아이들은 우리 게임에 들어오지 못한다

AI가 썼다는 브라우저 300만 줄에서 112만 줄은 남의 코드였다

AI가 준 선택지, 초5 아들은 어떻게 골랐을까?