바로가기
실험실

LuckyPicky

선행을 적으면 네잎클로버가 자라는 앱

화면


01
홈
01홈

선행 한 번에 잎이 하나씩 붙습니다

선행 기록
02선행 기록

오늘 한 일을 한 줄로 적어 넣습니다

03클로버 완성

네 장째를 채우면 클로버가 상단 배지로 날아갑니다

행운 뽑기
04행운 뽑기

코인 하나로 캡슐 머신을 돌립니다

행운지수
05행운지수

왕복하는 게이지를 탭한 순간이 오늘 점수가 됩니다

행운지수 결과
06행운지수 결과

잡은 점수와 오늘의 색·숫자·아이템을 한 장으로 받습니다

보관함
07보관함

뽑은 행운권이 등급별로 쌓입니다

재료 고르기
08재료 고르기

태울 카드를 직접 골라 성공 확률을 올립니다

09강화 연출

재료가 빨려 들어가고 게이지가 한 바퀴 돌면 성패가 갈립니다

나의 기록
10나의 기록

선행한 날이 달력에 색으로 남습니다

기술 스택


02
FlutterDartRiverpodSupabasePostgreSQLGoogle AdMobGitHub Actions

데이터와 API


03
Supabase

재화·카드·기록을 서버가 들고 있고, 앱은 결과만 받는다

Google AdMob

광고를 보면 뽑기용 코인이 하루 다섯 개까지 들어온다

인터뷰


04

어떤 서비스인가요?

"선행"이라는 재화로 "운"을 사는 어플입니다.

어떤 계기로 만들게 됐나요?

평소에 업보라는 걸 자주 생각했어요. 카르마라고도 하죠. 야구선수 오타니도 자기 덕을 위해 쓰레기를 줍는다고 알려져 있고요. 제가 좋아하는 롤 프로팀 T1 의 팬 문화도 비슷해요. 롤드컵 기간이 되면 팬들이 일부러 착한 일을 하고 인증을 올립니다. 내 선행이 조금이라도 응원하는 팀에 보탬이 되길 바라면서요

그러다 생각이 들더라고요. 운이라는 게 정말 문득 찾아오기만 하는 걸까, 그렇게 수동적인 걸까. 능동적으로 얻을 수는 없을까. 그러다 네잎클로버가 떠올랐고, 선행을 쌓아 스스로 운을 만드는 서비스로 만들어 봤어요

구상


05
선행을 한 줄 적으면 잎이 하나 붙고, 잎이 4개가 모이면 클로버가 완성된다
완성된 클로버로 소원권을 만든다
지난 기록을 달력에서 한눈에 본다

유저 플로우


06
  1. 01
    오늘 한 선행을 한 줄 적는다

    홈 한가운데 클로버에 잎이 하나 붙어요.

  2. 02
    잎 네 장을 채운다

    네 장째를 적는 순간 클로버가 완성돼서 상단 배지로 날아갑니다.

    온라인비행 연출 뒤 숫자가 오른다오프라인연출이 아예 재생되지 않는다
  3. 03
    뽑기 탭에서 캡슐 머신을 돌린다

    코인 한 개로 행운권 한 장이 나와요.

    코인이 있다바로 돌린다없다광고를 보고 코인을 받는다
  4. 04
    보관함에서 카드를 굽는다

    같은 등급 셋을 태워 재조합하거나, 여러 장을 먹여 한 장을 강화합니다.

    성공카드가 뒤집히며 등급이 오른다실패화면이 흔들리고 재료만 사라진다
  5. 05
    운세 탭에서 오늘의 행운지수를 잡는다

    게이지가 왕복하는 걸 탭한 순간의 값이 오늘 점수가 돼요.

구조


07
보내는 것되돌아오는 것

개발 과정


08
  1. 01

    착한 일을 적으면 잎이 붙는 화면부터

    처음 만든 건 화면 세 개였어요. 한 줄 적으면 잎이 하나 붙고, 네 장이 되면 클로버가 되는 것. 목업을 먼저 잡아 놓고 그대로 옮겼는데, 여기서 규칙을 하나 세웠습니다. Material 기본 모양은 하나도 쓰지 않는다. 기본 AppBar, 잉크 리플, 보라색 시드 컬러가 한 개라도 남아 있으면 "플러터로 만든 티"가 나거든요. 버튼도 카드도 Container 로 직접 그리고, 색·그림자·자간은 목업 값을 그대로 옮겨 적었어요.

    작업 PC 가 윈도우라 화면 확인은 전부 안드로이드 에뮬레이터에서 했어요. iOS 는 코드만 맞춰 두고 빌드 검증을 못 한 채로 왔습니다.

  2. 02

    소원권을 걷어내고 캡슐 머신을 넣었어요

    처음 메인은 소원권이었어요. 선행 하나에 잎 하나, 네 번이면 클로버가 완성되고, 그 클로버로 소원권을 만드는 구조요. 그런데 만들고 보니 소원 문구를 사용자가 직접 써야 하더라고요. 쓰는 부담이 있는 데다 다 쓰고 나면 그걸로 끝이라, 재미 요소가 영 떨어졌어요.

    그래서 클로버를 아예 코인처럼 쓰기로 했습니다. 클로버 하나를 넣고 캡슐 머신을 돌려 행운권을 랜덤으로 뽑는 쪽으로요. 무엇이 나올지 모르는 도파민이 붙으니까 매일 열어볼 이유가 생겼어요.

  3. 03

    재화를 전부 서버로 넘겼어요

    로컬에만 저장하던 시절엔 앱을 지우면 모은 게 전부 날아갔어요. 뽑기까지 붙이고 나니 이건 더 못 미루겠더라고요. 게다가 클라이언트가 재화를 계산하면 마음만 먹으면 얼마든지 늘릴 수 있고요.

    Supabase 로 옮기면서 앱이 테이블을 직접 쓰지 못하게 막았어요. 잎 적립도 뽑기도 강화도 전부 security definer 함수 안에서만 일어나고, 앱은 결과 JSON 만 받습니다. 대신 테스트용으로 똑같은 규칙을 구현한 로컬 백엔드를 하나 더 뒀어요 — 규칙을 고칠 땐 SQL 과 그 파일을 항상 같이 고칩니다.

    붙인 것
    Supabase
  4. 04

    강화·재조합은 피파에서 착안했어요

    뽑기만 있으면 카드가 쌓이기만 하고 쓸 데가 없어요. 중복 카드도 계속 나오고요. 피파 게임에서 선수 카드를 재료로 태워 등급을 올리잖아요 — 그걸 그대로 가져왔습니다. 같은 등급 셋을 태워 다시 뽑는 재조합, 여러 장을 먹여 한 장을 올리는 강화 두 가지로요.

    처음엔 카드 상세 아래에 바텀시트로 붙였어요. 좁으니까 재료를 고르는 화면이 답답했고, 무엇을 태우는지도 잘 안 보였고요. 아예 전용 화면으로 꺼내서 지갑에서 카드를 직접 골라 태우게 바꿨습니다. 강화는 대상 → 재료 두 단계, 재조합은 재료 세 장 한 단계로요.

    연출도 여기서 많이 갈아엎었어요. 재료 카드가 중앙으로 빨려 들어가고, 게이지가 차고, 성공하면 카드가 뒤집히고 실패하면 화면이 흔들립니다. 그 과정에서 결과가 미리 새는 문제로 한참 고생했는데, 그건 아래 시행착오에 적었어요.

  5. 05

    운을 잡는 감각을 화면에 넣고 싶었어요

    이 서비스의 모토가 운은 찾아오는 게 아니라 찾아가는 거잖아요. 그런데 앱 안에서 그걸 몸으로 느끼는 대목이 없더라고요. 선행을 적는 것도, 뽑기를 돌리는 것도 결국 결과는 서버가 정하는 거니까요.

    그래서 운세 탭에 타이밍 게임을 넣었어요. 게이지가 0에서 100 사이를 왕복하고, 탭한 순간의 값이 그대로 오늘의 행운지수가 됩니다. 오늘 내 운을 내 손으로 정하는 거죠. 표시값에 커브를 걸어서 90 점대 구간을 순식간에 스쳐 지나가게 했어요 — 그래야 100 을 잡는 맛이 나거든요.

  6. 06

    코인과 클로버를 갈랐어요

    광고 규칙을 만지다 보니 모델이 계속 어긋났어요. 클로버 하나가 곧 뽑기 한 번이면, 선행으로 만든 클로버와 광고로 받은 클로버가 구분이 안 되잖아요. 그러면 "선행으로 운을 만든다"는 축이 통계에서부터 무너집니다.

    그래서 재화를 둘로 나눴어요. 뽑기는 광고로만 들어오는 코인을 쓰고, 선행으로 만든 클로버는 커스텀 행운권 제작·강화 전용이 됩니다. 클로버는 여전히 선행으로만 생기고, 통계에도 그것만 쌓여요. 사용자가 문구를 직접 써서 만드는 커스텀 행운권도 이때 붙였습니다.

    붙인 것
    Google AdMob
  7. 07

    문구를 앱 밖으로 뺐어요

    문구를 고칠 때마다 스토어 심사를 기다리는 게 말이 안 됐어요. 그래서 문구를 Supabase 테이블에서 받아오고, 못 받으면 앱에 실린 문구로 떨어지게 했습니다. 폴백 단위를 (면, 언어, 등급) 조합별로 잘게 쪼개서, 한 면씩 천천히 서버로 옮겨도 안전하게요.

    그 다음에 소스를 어디에 둘지로 한 번 뒤집혔는데, 그것도 시행착오에 적었습니다.

    언어는 한국어·영어·일본어까지만 열었어요. 중국어는 간체·번체가 갈리는 데다 본토 스토어는 심사와 퍼블리셔가 따로라, 혼자 감당할 운영비가 아니라서 뺐습니다.

    붙인 것
    GitHub Actions
  8. 08

    기기를 바꾸면 다 날아가는 걸 뒤늦게 알았어요

    익명 로그인은 세션을 기기에 둬요. 웹은 localStorage 고요. 그래서 다른 기기에서 열면 새 계정이 되고, 모은 게 전부 사라진 것처럼 보입니다. 소셜 로그인을 붙이는 게 정석이지만 그건 심사·약관까지 딸려 오는 일이라, 먼저 복구 코드를 만들었어요.

    계정마다 "형용사 + 뜬금없는 명사" 조합의 코드를 발급해 두고 — 명란한 참치마요 오붓한 스파게티 같은 — 새 기기에서 그 코드를 넣으면 옛 계정의 재화·카드·기록이 현재 세션으로 넘어옵니다. 세션은 건드리지 않고 서버에서만 옮기는 방식이라, 로그인 구조를 바꾸지 않고도 자산이 안 날아가요.

시행착오


09
증상처음엔 광고를 보면 뽑기가 그냥 한 번 돌아가는 "무료 뽑기"였어요. 그런데 이러면 뽑기에 경로가 두 개가 됩니다. 클로버를 쓰는 길과 광고를 보는 길이요. 그러면 클로버가 뽑기 코인이라는 모델 자체가 흔들려요.
시도먼저 클로버 완성 순간에 전면 광고를 넣었어요. 가장 기분 좋은 지점이니 여기서 받자는 계산이었는데, 완성 연출을 만들고 나니 정반대였습니다. 클로버가 배지로 날아가 숫자가 오르는 그 순간에 광고가 덮으면, 선행에 대한 보상 대신 광고가 되더라고요. 그래서 전면 광고를 도로 뺐어요.
결론광고는 **얻는 순간이 아니라 쓰는 순간**에만 두기로 했어요. 무료 뽑기를 없애고, 광고를 보면 코인이 하루 다섯 개까지 들어오게 바꿨습니다. 뽑기는 언제나 코인 한 개를 쓰고요. 얻는 경로가 하나로 정리되니 재화 모델도 같이 단순해졌어요.
재조합 카드가 뒤집히기 전에 결과가 보였어요. 게이지가 차는 동안 이미 무슨 카드인지 알 수 있으니, 뒤집는 연출이 아무 의미가 없었죠.
재료를 골라 강화 버튼을 눌렀는데 서버가 규칙 위반으로 거절하면, 화면이 그냥 지갑으로 돌아가 있었어요. 왜 안 됐는지도 안 나오고, 애써 고른 카드 선택도 사라져 있고요.
처음엔 Supabase `copy_lines` 테이블을 원본으로 뒀어요. 문구를 고치려면 대시보드에서 행을 편집하고요. 그런데 이러니 **누가 언제 무슨 문구를 왜 바꿨는지가 아무 데도 안 남더라고요.** 리뷰도 안 되고, 잘못 넣으면 되돌릴 방법도 없었어요.
웹으로 빌드해서 열었더니 스플래시에서 안 넘어갔어요. 코드를 아무리 봐도 초기화가 안 끝날 이유가 없었고요.
앱이 뜨긴 하는데 탭이 하나도 안 먹었어요. 자동화 스크립트로 좌표를 찍어도 반응이 없고, 손으로 눌러도 그대로였고요.

남은 것


10
  • 웹으로 공개하면 재화 경제가 깨져요.웹에는 광고가 없어서 보상형이 즉시 지급되거든요. 막을 방법을 먼저 만들어야 합니다.
  • 소셜 로그인이 없어요.복구 코드로 급한 불은 껐지만, 결국 Google·Apple 계정 연동으로 가야 합니다.
  • 월간 문구 리서치 루틴이 멈춰 있어요.유행어를 뺀 뒤로 매달 갱신할 이유가 사라져서, 다시 돌릴지는 아직 안 정했습니다.