전체 글 125

서버 없는 게임에서 백업과 복원을 어떻게 만들었나 — 게임 화폐가 늘지 않는 설계

※ 이 글은 제가 일본어로 쓴 글(jacepark.com)을 한국어로 옮긴 것입니다.이시즈모에는 서버도 계정도 없습니다. 기록은 기기 안에만 있습니다. 기종을 바꾸거나 앱을 지우면 기록이 사라집니다. 그래서 파일 하나로 기록을 옮기는 백업과 복원을 만들었습니다.구조는 단순해 보였지만, 게임 안의 화폐(젬)가 얽히는 순간 정할 것이 한꺼번에 늘었습니다.기본 모양 — 파일 하나와 비밀번호백업은 저장 데이터를 압축한 뒤 비밀번호로 암호화한 파일 하나. 어디에 둘지는 사용자가 정한다비밀번호는 앱이 갖고 있지 않다. 잊으면 그 파일은 영영 열리지 않는다. 그래서 만들 때 두 번 입력받고, 눈 아이콘으로 직접 확인할 수 있게 했다복원은 통째로 바꿔치기. 저장 데이터가 하나뿐이라 합칠 것이 없다암호화와 압축에서 정한..

BGM이 아무 말 없이 멈춘다 — expo-audio는 재생을 멈춰도 JS에 알리지 않는다

※ 이 글은 제가 일본어로 쓴 글(jacepark.com)을 한국어로 옮긴 것입니다.이시즈모에서는 씨름판 위에서 3분 정도의 BGM을 계속 틀어 둡니다. 어느 날 BGM이 멈춘 채 돌아오지 않는 것을 알아차렸습니다. 효과음은 평소대로 납니다. 개발 빌드 로그를 봐도 앱은 BGM을 「재생 중」이라고 믿고 있었습니다.이상하게도 설정 화면에 갔다가 돌아오면 BGM이 다시 울리기 시작했습니다. 이 「낫는 방식」이 원인을 보이지 않게 하고 있었습니다.환경: Expo SDK 57 · React Native 0.86 · expo-audio(2026년 9월 시점)원인 — 재생은 멈췄는데, JS는 모른다iOS에서는 전화가 걸려 올 때처럼 소리 재생이 중단되거나, 이어폰을 꽂고 빼는 등으로 소리의 출구가 바뀌면 재생이 멈..

안드로이드에서 동영상을 내보내면 1%에서 앱이 죽는다 — JS로는 잡히지 않는 네이티브 크래시

※ 이 글은 제가 일본어로 쓴 글(jacepark.com)을 한국어로 옮긴 것입니다.이시즈모에는 그날의 한판을 세로형 짧은 동영상으로 만들어, 숏폼 서비스에서 자랑할 수 있는 기능이 있습니다. iOS에서는 문제없이 동작하고 있었습니다.안드로이드에서 「동영상 만들기」를 누르면 진행률 1%에서 앱이 통째로 사라졌습니다. 오류 화면도 나오지 않습니다. JS의 try도 듣지 않았습니다. 몇 번을 눌러도 같습니다.환경: Expo SDK 57 · React Native 0.86 · @azzapp/react-native-skia-video 0.10.1(2026년 9월 시점 최신)동영상이 만들어지기까지먼저 이시즈모의 동영상 만드는 방식을 적어 둡니다. 대전은 모두 돌의 움직임 기록(리플레이)으로 남깁니다. 동영상을 만..

게임 앱의 발열을 두 번 오진했다 — 재는 방법을 바꾸니 범인은 JS 스레드였다

※ 이 글은 제가 일본어로 쓴 글(jacepark.com)을 한국어로 옮긴 것입니다.이시즈모는 손가락으로 돌을 튕겨 상대의 돌을 씨름판 밖으로 밀어내는 게임입니다. React Native와 Skia로, 씨름판 위에서 돌 9개가 구르고 부딪히는 모습을 60fps로 그립니다.TestFlight로 배포했더니 「폰이 뜨거워서 계속할 수 없다」는 보고가 왔습니다(iPhone 15 Pro). 이대로는 낼 수 없습니다. 이 열을 저는 두 번 오진했습니다.환경: Expo SDK 57 · React Native 0.86 · React Native Skia · Reanimated 4.5(2026년 9월 시점). 계측은 주로 iPhone SE 개발 빌드입니다.버벅임과 발열은 다르다처음에 갈라 두었어야 할 것이 이것이었습니다..

리워드 광고의 실패는 세 갈래로 온다 — try/catch만으로는 화면이 멈춘다

※ 이 글은 제가 일본어로 쓴 글(jacepark.com)을 한국어로 옮긴 것입니다.이시즈모에는 광고를 끝까지 보면 젬(게임 안의 돈)을 받을 수 있는 리워드 광고가 있습니다. 설정, 외장, 데이터 복원, 「오늘의 한판(本日の一番)」 네 곳에서, 사용자가 직접 버튼을 눌렀을 때만 볼 수 있습니다. 젬은 돌의 외장을 사거나, 데이터를 복원할 때 씁니다.이 버튼을 누르면 가끔 「광고를 보고 있습니다」 표시인 채로 90초 멈추는 일이 있었습니다. 광고는 나오지 않습니다. 90초가 지나서야 보상이 들어옵니다.환경: Expo SDK 57 · React Native 0.86 · react-native-google-mobile-ads(2026년 9월 시점)원인 — 실패를 받지 못하고 있었다광고를 표시하는 코드는 처음..

RevenueCat 구독 구현에서 어려웠던 것은 「상태 판정」이었다

※ 이 글은 제가 일본어로 쓴 글(jacepark.com)을 한국어로 옮긴 것입니다.손가락으로 돌을 튕겨 상대의 돌을 씨름판 밖으로 밀어내는 게임 「이시즈모(石相撲)」를 냈습니다. 유료 상품은 월 480엔 구독 하나입니다.RevenueCat SDK를 넣고 구매 버튼을 붙이는 데까지는 문서대로 금방 끝납니다. 시간이 걸린 것은 「지금 이 사용자가 가입자인가」를, 언제, 어떻게 믿을 것인가였습니다. 이시즈모를 만들면서 확인한 것만 나열합니다.환경: Expo SDK 57 · React Native 0.86 · react-native-purchases 10.9.1(2026년 9월 시점)가장 위험한 한 줄 — 구매 직후의 강제 재조회구매가 끝나면 상태를 확실히 하고 싶어집니다. 캐시를 버리고 서버에 다시 묻는 코..

47일, 앱 하나를 내기까지 밟은 지뢰

※ 이 글은 제가 일본어로 쓴 글(jacepark.com)을 한국어로 옮긴 것입니다.2026년 6월 30일에 시작해서, 8월 15일에 App Store에 냈습니다. Google Play는 그 전날인 8월 14일. 47일입니다. 심사 리젝트 왕복 한 번을 포함해서.ahha — 일본 복권(LOTO6·LOTO7)의 통계 분석 앱입니다. 과거 당첨 데이터에서 출현 경향을 보여 주고, 번호 조합을 만들고, 판매 마감 전에 알림을 보냅니다. 무료로 쓸 수 있고 일부 기능이 월정액 구독으로 열리는 구조입니다.기술적으로 특별한 것은 아무것도 없습니다. 그런데 특별하지 않은 것들이 계속 발목을 잡았습니다. 이 글은 그 지뢰의 목록입니다. 각 항목은 별도의 글에서 자세히 다뤘고, 여기서는 「무엇을 밟았는가」만 나열합니다...

배포하고 세 시간 뒤에 사이트가 사라졌다

※ 이 글은 제가 일본어로 쓴 글(jacepark.com)을 한국어로 옮긴 것입니다.배포는 정상적으로 끝났습니다. 09:41. 사이트도 제대로 표시됐습니다. 세 시간 뒤에 접속하니 403 Forbidden이었습니다.서버에 들어가 문서 루트를 봅니다.ahha.iffthen.com/ assets/ ← 이것만 남아 있다index.html도 .htaccess도 각 페이지의 파일도 전부 사라져 있었습니다. assets/만 살아남아 있습니다. 다시 배포하니 복구됐습니다. 그리고 몇 시간 뒤에 또 사라졌습니다.첫 번째 조사 — 원인을 찾지 못했다당연히 배포 스크립트를 의심했습니다. rsync --delete를 쓰고 있었기 때문입니다. 서버를 뒤졌습니다.crontab -l → 이 사이트 관련 작업은 ..

그 공지, 사용자는 실제로 보고 있나요

※ 이 글은 제가 일본어로 쓴 글(jacepark.com)을 한국어로 옮긴 것입니다.ahha의 구독 상태 처리를 정리하면서 이런 공지를 만들었습니다.프리미엄 플랜이 종료되었습니다이용 기간이 만료되어 무료 플랜으로 전환되었습니다.기간이 만료됐을 때, 그리고 결제 수단 문제로 갱신에 실패했을 때 띄우는 공지입니다. 상태 판정도 제대로 하고, 문구도 다듬고, 중복 표시 방지까지 넣었습니다. 체크리스트에 「○」를 붙였습니다.그리고 두 세션 뒤에 다시 보다가 알아차렸습니다. 이 공지를 볼 수 있는 사용자가 거의 없었습니다.구조를 보면 바로 알 수 있다공지의 상태는 전역 변수 하나로 들고 있었습니다.export type PlanEndedNotice = "expired" | "billingIssue" | null;e..

네이티브 모듈이 Web을 죽이는 세 가지 방식

※ 이 글은 제가 일본어로 쓴 글(jacepark.com)을 한국어로 옮긴 것입니다.React Native Web을 쓰면 앱과 Web을 하나의 코드베이스로 돌릴 수 있습니다. 편합니다.그런데 네이티브 전용 라이브러리를 하나 추가할 때마다 Web이 조용히 죽습니다. 그것도 매번 다른 방식으로 죽습니다. ahha를 개발하면서 세 번을 밟고 나서야 겨우 패턴이 보였습니다.사례 1 — 검은 화면과, 엉뚱한 에러랜딩 페이지의 「Web에서 체험하기」를 누르면 앱의 미리보기 화면으로 넘어가게 해 두었습니다. 배포하고 눌러 보니 새까맣게 나왔습니다.Uncaught TypeError: Failed to set an indexed property [0] on 'CSSStyleDeclaration':Indexed prope..