비눗방울 띄우기
길게 누를수록 비눗방울이 커지고, 바람 따라 가시덤불 사이를 지나 멀리 보낸다.
프롬프트를 복사해 claude.ai 에 붙여 넣으면 바로 실행됩니다 · Opus 5.5 · Effort High 기준
- 권장 모델
- Claude Opus 5.5 · Effort High
- 화면비
- 16:9 (가로)
- 조작
- 누르고 있으면 불기 · 떼면 날아간다 · 좌우로 쓸어 입김
- 결과물
- HTML 파일 하나 · 따로 설치할 것 없음
프롬프트
Claude 가 읽는 제작 지시문입니다. 맨 위 [바꿀 곳]의 값을 바꿔 붙여 넣어도 됩니다.
브라우저에서 바로 도는 미니게임 「비눗방울 띄우기」를 HTML 파일 하나로 만들어 줘.
핵심 아이디어: 길게 누를수록 막대 고리에 비눗방울이 부풀고, 떼면 바람을 타고 정원의 가시덤불 사이로 흘러간다. 좌우로 쓸어 입김을 불어 띄워 올리며 멀리 보낸다. 큰 방울은 점수를 두 배까지 주지만 막이 얇아 일찍 터지고 틈에 걸리기 쉽다. 이 작품의 얼굴은 비누막의 박막 간섭 빛이다 — 낮은 채도의 투명한 무지갯빛이 막 두께를 따라 천천히 흐르다가, 수명이 다할 즈음 위쪽부터 검은 막이 번져 내려오고 「톡」 터진다.
시그니처 순간: 열린 뒤 5.0초부터 7.0초 사이, 화면 가로 42% · 세로 40~50% 에 떠 있는 반지름 0.093S 비눗방울의 위쪽에서 검은 막이 번져 방울의 절반을 덮고, 7.0초에 터져 물방울 28개가 반지름의 1.6배까지 0.25초 동안 퍼진다.
[바꿀 곳]
CONFIG 맨 위에 아래 값을 이름 붙여 둔다. 이 값만 바꿔도 게임 느낌이 바뀌어야 한다.
- soapPerRound: 3 — 한 판에 부는 비눗방울 수
- windBase: 0.22 — 기본 바람(S/초, 오른쪽)
- windGust: 0.05 — 바람 너울 진폭(S/초), 거리 5.0 이후 0.08
- rMin: 0.025, rMax: 0.11 — 방울 반지름(S)
- fullBlowTime: 2.0 — 최대 크기까지 부는 시간(초), overBlowTime: 2.4 — 이때 고리에서 터짐
- lifeSmall: 14, lifeBig: 9 — 수명(초). 수명 L = lifeSmall − (lifeSmall − lifeBig)·k (k = 크기 비율 0~1)
- breathSide: 0.08, breathUp: 0.09, breathTapUp: 0.10, breathCooldown: 0.4
- filmSat: 0.5 — 비누막 색의 채도 배율(0 = 회색, 1 = 물리값 그대로)
- seedStart: 20261009 — 첫 판 시드
[크기]
- 게임판은 16:9 가로. 판 높이 = S (판의 짧은 변). 판 너비 = 1.778S. 판 크기 = min(창너비, 창높이·16/9) 너비로 가운데 둔다. 판 밖(위아래·좌우 남는 곳)에도 하늘·풀밭·덤불을 그대로 이어 그린다 — 빈 띠 금지.
- 모든 길이·속도는 S 단위. 위치 y 는 판 위쪽 0, 아래쪽 1.
- 땅(풀밭 윗선) y = 0.88. 방울 아래끝이 땅에 닿으면 터진다. 방울 위끝이 y 0.03 위로 가면 부드럽게 아래로 밀린다(터지지 않는다).
- 막대 고리: 세계 x = 0.28, y = 0.52, 고리 반지름 0.035, 굵기 0.006. 나무 막대가 판 왼쪽 아래 (0.06, 0.97)에서 고리까지 비스듬히 온다. 사람·손은 그리지 않는다.
- 방울 반지름 r = rMin + (rMax − rMin)·k. k = min(1, 누른 시간 / fullBlowTime). 최대 지름 0.22 < 가장 좁은 틈 0.26 이어야 한다.
- 카메라: 날아가는 방울이 판 가로 0.75S(판 너비의 42%)에 오도록 camX 목표 = max(0, 방울x − 0.75), camX += (목표 − camX)·min(1, dt·6). 방울이 터지면 목표 0 으로 돌아간다(약 0.6초).
- 글자: 현재 거리 0.06S 굵게(판 위쪽 가운데), 총점 0.045S(왼쪽 위), 남은 비눗물은 왼쪽 위 총점 아래 작은 고리 아이콘(지름 0.03S) 3개.
[장면]
늦은 오후, 안개가 옅게 낀 정원. 바탕은 채도가 낮은 따뜻한 회녹색이다.
- 하늘: 세로 그라데이션 #ECEAE3(위) → #DCE0D6(y 0.7). 오른쪽 위에 아주 옅은 햇빛 번짐(#F4EBDD, 알파 0.35, 반지름 0.9S 방사형) 하나.
- 먼 산울타리: #C3CABD 둥근 덩어리 실루엣, 카메라의 0.15배로 움직인다.
- 가운데 나무·관목: #A9B4A2, 0.4배 시차, 알파 0.8.
- 풀밭: y 0.88 아래 #8F9A82, 풀잎은 #7A8670 가는 곡선. 풀잎 끝은 현재 바람 세기만큼 오른쪽으로 휘며 흔들린다 — 바람이 눈에 보이는 유일한 표시다.
- 가시덤불: 줄기 #4E5847 굵기 0.012S, 가시는 길이 0.012S 작은 삼각형, 잎 #6B7861 작은 타원, 꽃 몇 송이만 바랜 장미색 #C7A19C(강조색은 이것과 비누막 둘뿐). 땅에서 솟은 덤불과 위에서 늘어진 넝쿨 두 가지.
- 판정 모양: 덤불 하나 = 원 5~8개 사슬. 그림은 이 원 사슬을 따라 줄기를 그리고 가시를 붙인다. 판정은 너그럽게: 방울 중심과 원 중심 거리 < r + 원반지름 − 0.004 일 때만 닿음.
- 덤불 배치(판마다 시드로 생성, 한 판 안 세 방울은 같은 정원을 지나간다): 간격 0.6~0.8.
- 구간 A (세계 x 1.2~2.6): 땅 덤불만, 꼭대기 y 0.72~0.76.
- 구간 B (2.6~5.0): 땅 덤불(꼭대기 0.58~0.66)과 늘어진 넝쿨(아래끝 0.22~0.32)이 번갈아.
- 구간 C (5.0~): 위아래 한 쌍, 틈 높이 0.36 에서 거리 1 마다 0.02 씩 줄어 최소 0.26, 틈 가운데 y 0.38~0.58.
- 비눗방울 그리기(가장 중요):
- 방울마다 112×112 오프스크린 캔버스에 ImageData 로 매 프레임 그려, 지름 2r 크기로 부드럽게 늘려 붙인다.
- 미리 LUT 를 만든다: 광로차 δ 0~3000nm 를 2048칸으로. 채널마다 파장 셋의 평균 세기 I = 평균 sin²(π·δ/λ), R = 610·640·670nm, G = 510·540·570nm, B = 430·460·490nm. 색 = 255·(0.08 + 0.85·I). 그다음 lum = 0.3R + 0.59G + 0.11B, 색 = lum + (색 − lum)·filmSat. δ → 0 이면 거의 검다(물리 그대로).
- 픽셀 (nx, ny) ∈ [−1, 1], d² = nx² + ny², d > 1 이면 투명. z = √(1 − d²). 굴절각 cosθt = √(1 − (1 − z²)/1.77).
- 막 두께: 전체 두께 h(t) = 12 + (h0 − 12)·(1 − t/L)^2.2 nm, h0 = 900 − 300·k. 아래로 흘러내리는 모양: 위아래 배율 f = 0.55 + 0.45·(ny + 1) (위 0.55, 아래 1.45). 흐름 s = 0.16·sin(3.1·nx + 1.7·T + 2.0·sin(2.3·ny − 0.9·T)) + 0.08·sin(5.3·ny + 1.3·nx − 1.1·T) (T = 방울 나이). 지역 두께 hl = h·f·(1 + s). δ = 2·1.33·hl·cosθt.
- 알파 = 0.18 + 0.5·(1 − z)^2.5. hl < 40nm 이면 알파 += 0.15·(1 − hl/40) — 검은 막이 연기 낀 유리처럼 또렷이 보이게.
- 그 위에 하이라이트: 왼쪽 위 (−0.45r, −0.5r) 에 반지름 0.18r 흰 부드러운 타원(알파 0.55), 오른쪽 아래 가는 흰 호(알파 0.3), 테두리 흰 1px 선(알파 0.25).
- 검산: t = L/2 에서 h = 12 + (h0 − 12)·0.218 → k=0 이면 약 206nm(1차 호박·자주색). 끝나기 3초 전(L=10) h ≈ 54nm, 위쪽 hl ≈ 30nm 로 검어지기 시작한다.
- 흔들림은 가볍게: 출발·입김 때 진행 방향으로 1.06배·수직으로 0.94배 늘였다가 0.5초에 걸쳐 되돌아온다. 그 외 반지름 ±2% 느린 출렁임.
- 터짐: 물방울 28개(지름 0.006S, #FFFFFF 알파 0.6)가 반지름의 1.6배까지 0.25초 퍼지며 사라지고, 막 조각 호 4개가 함께 흩어진다. 가시에 닿아 터지면 닿은 점에서 퍼지고 그 가시가 0.6초 반짝 젖은 빛을 띤다 — 왜 터졌는지 보인다. 땅에 닿으면 풀잎 위로 물방울이 튄다.
[조작]
- 키보드: Space(또는 Enter·ArrowUp·KeyW)를 누르고 있으면 고리에서 방울을 분다, 떼면 날아간다. 방울이 날고 있을 때: ArrowRight/KeyD = 오른쪽 위 입김(Δv = +0.08, −0.09), ArrowLeft/KeyA = 왼쪽 위 입김(Δv = −0.08, −0.09), Space 짧게 = 위 입김(Δv = 0, −0.10). 입김 사이 최소 0.4초. M = 소리 끄기. e.repeat 는 무시.
- 터치·마우스(pointer 이벤트): 방울이 없을 때 누르고 있으면 불기, 떼면 날아감(누른 채 손가락이 움직여도 불기다 — 이 누름에서는 쓸기 판정을 하지 않는다). 방울이 날 때 좌우로 쓸기(가로 이동 ≥ 화면 너비 5%, 가로 > 세로)는 그 방향 입김 — 문턱을 넘는 그 순간(pointermove) 바로 낸다. 0.35초 안에 떼는 탭은 위 입김.
- 폰 세로 화면에서도 화면 어디를 눌러도 되므로 엄지 하나로 된다.
- 반응: 누르는 그 프레임에 방울이 고리에 맺히기 시작하고, 입김 그 프레임에 방울이 늘어나며 밀린다. 입김마다 방울 뒤쪽에 반투명 흰 호 하나(0.3초)가 스친다.
- 맨 처음 입력(구경 모드에서 시작하는 입력)과 'over' 에서 새 판을 여는 입력은 시작에만 쓰이고 불기로 이어지지 않는다. 소리 단추를 누른 것은 시작 입력이 아니다.
[규칙]
- 한 판 = 비눗물 soapPerRound(3)번. 방울 하나가 터지면 다음 방울을 불 수 있다. 방울은 한 번에 하나만 난다.
- 바람: vx 는 바람 w = windBase + windGust·sin(0.7·t + 1.3·x) 쪽으로 초당 1.2 비율로 다가간다. vy 는 아래로 0.035S/초² 가라앉고 초당 1.0 비율로 감쇠(종단 속도 0.035S/초), 여기에 0.03·sin(1.1·t + 2.1·x) 의 오르내림. 놓는 순간 vy = −0.06.
- 검산: 입김 없이 k=0.5 방울(r 0.0675, L 11.5초)은 약 4.2초에 첫 덤불(x 1.2, 꼭대기 0.72)을 아래끝 약 0.64 로 넘고, 약 9.5~12.5초 뒤 x 2.4~3.0 근처에서 땅에 닿거나 수명이 다한다. 그래서 첫 판 처음 20초는 거의 실패하지 않는다. 입김 한 번에 약 0.09S 떠오른다.
- 수명: 나이가 L 이 되면 공중에서 검은 막 끝에 터진다. 덤불·땅에 닿으면 즉시 터진다. 누른 시간이 overBlowTime(2.4초)을 넘으면 고리에서 터지고 비눗물 하나를 잃는다 — 2.0초가 지나면 막이 빠르게 얇아져(h0 이 0.4초 동안 95% 까지 줄어) 검게 변하는 것이 경고다.
- 점수: 날고 있는 방울의 점수 = floor((x − 0.28)·100·(1 + k)), 실시간으로 window.__game.score 에 더해 보인다. 터지면 그 값이 확정된다. 판 점수 = 세 방울 합. 큰 방울(k=1)은 같은 거리에 두 배.
- 기본값에서 기대되는 결과: 작은 방울 최대 비행 약 5S, 큰 방울 약 3S. 보통 첫 판 점수 400~900, 판 길이 25~50초.
- 끝: 세 번째 방울이 터지고 0.8초 동안 마지막 물방울 연출(state 는 'play'), 끝나면 'over'. 방울이 없고 불지도 않는 상태가 8초 이어지면 남은 비눗물을 버리고 같은 연출로 끝난다 — 입력이 없으면 약 9초 안에 끝난다.
- 'over' 화면: 판 위에 #EDEBE4 알파 0.6 덮개, 가운데 점수 0.12S, 그 아래 최고 점수(메모리), 「눌러서 다시」. 뜨자마자 아무 키·탭이면 새 판(시드 +1).
[구경 모드]
- 열리자마자 'demo'. 첫 프레임부터 k = 0.8(r = 0.093S, L = 10초) 방울이 이미 나이 3.0초로 세계 x 0.9, y 0.42 에 떠 있고 카메라가 따라간다 — 첫 화면부터 큰 무지갯빛 방울이 화면 가운데 근처에 있다. 이 방울은 열린 뒤 7.0초에 공중에서 검은 막 끝에 터진다(시그니처 순간). 구경 모드 동안 이 방울은 덤불에 닿지 않도록 처음 정원을 비워 둔다(x 3.0 까지 덤불 없음). 컴퓨터는 이 방울에 입김을 넣어 y 0.40~0.50 에 머물게 한다(7.0초에 x 약 1.8).
- 그다음 컴퓨터가 스스로 분다: 누르는 시간 1.0~2.0초(시드 난수), 놓고, 앞의 가장 가까운 덤불(0.8S 안)을 보고 목표 높이를 정한다 — 땅 덤불이면 꼭대기 − r − 0.08, 넝쿨이면 아래끝 + r + 0.08, 쌍이면 틈 가운데. 0.5초 뒤 예상 y 가 목표 + 0.02 보다 아래면 입김(오른쪽 위). 대부분 덤불을 두세 개 넘다가 공중에서 수명이 다해 검게 터진다. 구경 모드는 비눗물 제한 없이 계속 돈다.
- 판 아래쪽 가운데에 「눌러서 시작 — 누르고 있으면 불기 · 좌우로 쓸면 입김」을 0.04S 글자, 알파 0.55~0.85 로 느리게 숨 쉬듯 보인다.
[소리]
- WebAudio 합성음만, 첫 입력 뒤에 AudioContext 를 만든다(또는 resume).
- 불기: 대역 통과 900Hz(Q 0.7) 잡음, 크기에 따라 0.04~0.08 음량, 부푸는 동안 사인 220→330Hz 가 아주 작게(0.02).
- 놓기: 사인 600→900Hz 60ms 「뽁」. 입김: 저역 통과 1200Hz 잡음 0.18초. 터짐: 고역 통과 2500Hz 잡음 30ms + 사인 1800Hz 20ms, 가시에 터지면 1.3배 크게.
- 바탕: 저역 통과 400Hz 바람 잡음 음량 0.02, 바람 너울에 맞춰 오르내림.
- 끝: 사인 세 음 C5·E5·G5 부드럽게.
- 오른쪽 위 구석에 지름 0.05S 둥근 소리 단추(스피커 모양 선 그림), M 키도 같다.
[구현]
- HTML 파일 하나로 만든다. 바깥 파일·글꼴·라이브러리·네트워크 요청을 쓰지 않는다(미리보기 칸에서 막힌다).
- 첫 <script> 맨 앞에 CONFIG 객체를 두고 [바꿀 곳]의 값과 이 글에 나온 숫자(속도·크기·색·시간)를 이름 붙여 모은다.
- 게임 진행은 requestAnimationFrame 의 타임스탬프로 고정 시간 간격(1/120초) 갱신을 돌린다. 한 프레임에 밀린 시간은 0.1초까지만 따라잡는다. setTimeout·setInterval·CSS 애니메이션으로 움직이지 않는다.
- 난수는 시드 고정 난수(mulberry32 등)만 쓰고, 판마다 시드를 하나씩 늘린다. Math.random 은 쓰지 않는다.
- 캔버스는 늘 창 전체 크기다. 창 크기가 바뀌면 다시 맞추고, 게임판 비율이 정해진 게임은 판을 가운데 두되 남는 곳에도 배경을 그린다(빈 띠를 두지 않는다). 스크롤이 생기지 않게 한다. devicePixelRatio 는 2 까지만 선명도에 반영한다.
- 키보드와 터치를 둘 다 받는다. 키 입력은 window 의 keydown·keyup 으로 받아, 화면을 먼저 누르지 않아도 첫 키가 먹게 한다. 짧게 눌렀다 뗀 키도 놓치지 않게 keydown 때 처리하거나 기록해 둔다. 터치는 페이지 스크롤·확대가 일어나지 않게 막는다(touch-action: none).
- 점수·상태를 window.__game = { state, score } 에 두고 매 프레임 갱신한다. state 는 'demo'(구경 모드)·'play'·'over' 셋 중 하나, score 는 숫자다.
- 열리자마자 'demo' — 컴퓨터가 스스로 플레이하는 구경 모드와 「눌러서 시작」 안내. 첫 키 입력이나 탭이면 새 판 'play'. 판이 끝나면 'over' 화면, 거기서 키 입력이나 탭이면 새 판. 'over' 화면은 뜨자마자 키 입력·탭을 받는다(입력을 막는 대기 시간을 두지 않는다). 끝나는 연출이 있으면 그동안은 state 를 'play' 로 두고, 연출이 끝나 입력을 받을 수 있게 된 때 'over' 로 바꾼다.
- 플레이 중 아무 입력이 없으면 늦어도 40초 안에 판이 끝난다.
- 브라우저 저장소(localStorage·sessionStorage·IndexedDB·쿠키)를 쓰지 않는다 — 미리보기 칸에서 막혀 오류가 난다. 최고 점수는 메모리에만 둔다.
- 소리는 WebAudio 로 합성한 소리만, 첫 입력 뒤에만 낸다. 화면 구석에 소리 끄기 단추(M 키도)를 둔다.
- 탭이 숨으면 멈추고 돌아오면 이어서 한다.
- 글자는 시스템 글꼴만 쓴다: 'Pretendard', 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', sans-serif.
- 모든 함수·상수를 먼저 정의하고, 시작(초기화와 첫 requestAnimationFrame)은 스크립트 맨 끝에서 한 번만 부른다.
- 콘솔에 오류가 나지 않게 한다. 스스로 대조하는 표는 console.table, 어긋남은 console.warn 으로만 낸다.
이 작품만의 구현:
- 구조: CONFIG → mulberry32 → buildFilmLUT → makeGarden(seed) → Bubble(생성·update·draw) → 입력 → 소리 → update(dt) → render → selfCheck → 시작.
- 2D 캔버스만 쓴다. 비누막은 위의 ImageData 방식(방울 하나에 112×112, 한 번에 많아야 둘 — 고리에 맺힌 것과 나는 것). 픽셀 반복 안에서 sin 은 셋까지, LUT 조회로 색을 얻는다. 배경 층은 판 크기가 바뀔 때만 다시 그릴 수 있는 것은 오프스크린에 미리 그린다.
- 판정은 고정 간격 갱신 안에서. 방울과 덤불 원 사슬은 원-원 거리로만 잰다.
- 상태 기계: demo / play / over. play 안에 phase: ready(고리 비어 있음) · blowing · flying · ending.
- 시작할 때 selfCheck 가 console.table 로 아래를 대조하고, 어긋나면 console.warn:
- L(k=0) = 14, L(k=1) = 9, 2·rMax = 0.22 < 최소 틈 0.26
- LUT[δ=0] 밝기 < 0.12, LUT 전체 평균 채도(최대−최소 채널)/255 ≤ 0.35
- 입김 없는 k=0.5 방울을 고정 간격으로 미리 굴려 본 결과: 첫 덤불 통과 = true, 터지는 시각(땅 또는 수명) 9.5~12.5초
- 구경 모드 첫 방울 터지는 시각 = 7.0초(±0.2)
- 입력 없는 판이 끝나는 시각 ≤ 40초
- 결과 HTML 은 60KB 안팎을 목표로 한다.
[하지 말 것]
- 반짝이 입자·꽃잎 흩날림·먼지 입자를 뿌리지 않는다 — 비누막 빛이 주인공이고, 바람은 풀잎으로만 보인다.
- 렌즈 플레어·빛 번짐(shadowBlur 발광)·네온 테두리를 쓰지 않는다 — 투명하고 얇은 막의 섬세함이 묻힌다.
- 비누막을 무지개 원색 고리(색상환 회전)로 칠하지 않는다 — 반드시 두께 기반 간섭 LUT 로, filmSat 만큼 낮춘 채도로. 원색 무지개는 장난감처럼 보인다.
- 바탕에 남청색·짙은 색·고채도 색을 쓰지 않는다 — 검은 막이 보이려면 바탕이 밝고 차분해야 한다.
- 사람·손·얼굴·동물 캐릭터, 플랫 캐릭터, 새 떼, 벌레를 그리지 않는다 — 정원과 방울만으로 충분하다.
- 화면 모서리에 버전·제목 라벨, 조작 설명 상자를 두지 않는다 — 안내는 구경 모드의 한 줄뿐.
- 바깥 글꼴·이미지·라이브러리를 불러오지 않는다 — 미리보기 칸에서 막힌다.
- 방울을 여러 개 동시에 날리지 않는다 — 방울 하나의 수명과 검은 막을 지켜보는 것이 놀이의 중심이다.
- 'over' 에 입력 대기 시간을 두지 않는다 — 바로 다시 할 수 있어야 손이 계속 간다.
사용 방법
필요한 것: claude.ai 계정 — 무료 계정에서도 Claude Opus 5.5 를 고를 수 있습니다
- claude.ai 에서 새 대화를 엽니다.
- 모델 메뉴에서 Claude Opus 5.5 를 고르고, 같은 메뉴의 Effort(답변 깊이)를 High 로 둡니다.
- 복사한 프롬프트를 붙여 넣고 전송합니다. 결과로 나온 HTML 파일을 브라우저로 열면 바로 해 볼 수 있습니다.
추가 요청 예시
결과가 나온 뒤 같은 대화에서 이어서 요청하면 해당 부분만 수정됩니다.
- 가장 멀리 간 거리를 기록으로 보여 줘
- 정원을 비 갠 오후 무지개 하늘로 바꿔 줘
- 비눗방울 두 개가 붙으면 하나로 합쳐지게 해 줘
미니게임