클리커에서 집·일꾼 방치형 구조로 전환
거품은 이제 클릭이 아니라 바다에 있는 생물이 시간에 따라 번다. 화면 가장자리에
깔아 두는 위젯인데 클릭이 주된 수입원이면 연타가 최적 플레이가 되어 형태와
어긋난다. 클릭 수익과 클릭 업그레이드를 걷어내고 업그레이드는 전부 생산 배율로
바꿨다. 물을 만지면 거품이 퍼지지만 얻는 것은 없다.
격자 칸을 차지하는 것은 집뿐이고, 생물은 칸이 아니라 집에 산다(한 채당 3마리).
칸 → 집 → 생물 → 거품으로 한계가 두 겹 걸린다. 예전에는 생물과 장식이 같은
자원(칸)을 놓고 경쟁해서 최적 플레이가 늘 "장식을 사지 말 것"이었는데, 집과
일꾼으로 나누니 그 경쟁이 의존으로 바뀐다. 집은 같은 종류도 여러 채 지을 수 있고
거품을 벌지 않는다 — 정원과 생산을 한 물건이 주면 곱연산이 두 번 걸린다.
어느 생물이 어느 집에 사는지는 저장하지 않는다. 집은 정원만 주므로 어디 살든
결과가 같고, 저장하면 재배정과 인덱스 관리만 붙는다. 그릴 때 구역마다 배정한다.
그래서 저장 형식에서 생물은 위치 없이 {id: 마리 수}다. 옛 저장본은 그때의
장식을 집으로, 생물을 마릿수로 옮겨오고 정원이 모자라면 집을 세워 메운다.
수입이 생물에서만 나오므로 막다른 길을 두 곳에서 막았다. 마지막 생물은 내보낼 수
없고, 살던 생물이 갈 곳이 없어지는 집은 헐 수 없다.
집과 생물의 그림 형태를 갈랐다. 집 10채 중 8채가 생물과 같은 rock이어서 항아리와
거품돌이 색만 다른 같은 덩어리로 보였다. 집은 vessel/wreck/pillar만, 생물은
plant/rock/fish/glow만 쓴다. 헤엄치는 생물은 집을 도는 타원 궤도에 올라가고,
원점이 궤도를 따라 움직이므로 Y 정렬이 앞뒤를 알아서 바꿔준다 — 뒤로 돌면 집에
가렸다가 앞으로 나온다. 얕은 바다 생물이 거품돌과 해초뿐이라 이 움직임을 볼 수
없어서 구피를 넣었다.
실행해 보고 잡은 버그 셋:
- 저장본이 없으면 load_game이 그냥 반환해 빈 바다로 시작했다. 집도 생물도 없어
아무 일도 일어나지 않는다. 이제 새 게임을 시작한다.
- 창 높이·도킹·모니터 설정이 복원되지 않고 기본값으로 덮어써지고 있었다.
window_state에 미리 있던 키만 복사하던 것을 저장본에 있는 것을 전부 싣도록 고쳤다.
- 정보 바가 격자 첫 줄을 덮어 왼쪽 위에 놓인 것이 보이지 않았다. GRID_TOP 상수를
버리고 HUD가 잰 실제 바 높이를 받아 쓴다.
- main.gd가 초기화할 때 존재하지 않는 aquarium.rebuild()를 부르고 있었다.
스모크 테스트 159개.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uze4mF1NAGfFRio77cMFqg
This commit is contained in:
+121
-28
@@ -8,11 +8,65 @@
|
||||
|
||||
## 핵심 설계 판단
|
||||
|
||||
**칸이 곧 한계다.** 클리커 장르는 보통 한 종류를 무한히 사서 가격 곡선으로만
|
||||
**클릭으로 벌지 않는다.** 이건 클리커가 아니라 방치형 위젯이다. 거품은 오직 바다에
|
||||
놓인 생물이 시간에 따라 번다. 화면 가장자리에 깔아 두고 다른 일을 하는 물건인데
|
||||
클릭이 주된 수입원이면 위젯을 쳐다보며 연타하는 것이 최적 플레이가 되어, 형태와 어긋난다.
|
||||
물을 만지면 거품이 퍼지지만 그건 연출일 뿐 얻는 것은 없다.
|
||||
|
||||
여기서 두 가지가 따라온다. 첫째, **새로 시작하면 항아리 집 한 채와 거품돌·구피 한 마리씩이
|
||||
이미 있다**(`Catalog.STARTER_HOUSES` / `STARTER_FISH`). 바닥에 붙는 것과 헤엄치는 것을
|
||||
하나씩 둔 것은, 켜자마자 두 가지 움직임이 다 보이게 하기 위해서다. 빈 바다는 집도 생물도 없어서
|
||||
아무리 기다려도 아무 일이 일어나지 않기 때문이다. 시작 거품 20은 세 번째 거품돌 값에
|
||||
맞춰 두어, 시작하자마자 남은 한 자리를 직접 채워보며 구조를 익히게 했다.
|
||||
둘째, **마지막 남은 생물은 내보낼 수 없다**(`GameState.can_release_fish`). 생물이 0이 되면
|
||||
수입도 0이 되고, 환불받은 거품이 다음 한 마리 값에 못 미치면 영영 되돌아올 수 없기 때문이다.
|
||||
업그레이드도 전부 생산 배율(`bps_mult`)이고, 클릭 관련 항목은 남기지 않았다.
|
||||
|
||||
저장본이 아예 없으면(이 컴퓨터에서 처음 켰으면) `SaveManager.load_game`이 새 게임을
|
||||
시작한다. 클릭으로 벌던 시절의 저장본처럼 집이나 생물이 없는 저장본을 불러올 때도
|
||||
`GameState.from_dict`가 시작 상태를 채워준다.
|
||||
|
||||
**집은 건물, 생물은 일꾼이다.** 칸을 차지하는 것은 집뿐이고, 생물은 칸이 아니라
|
||||
**집에 산다.** 한 채가 3마리를 들인다(`Catalog.HOUSE_CAPACITY`). 그래서 한계가 두 겹이다.
|
||||
|
||||
```
|
||||
칸 → 집을 몇 채 지을 수 있는가
|
||||
집 → 생물을 몇 마리 키울 수 있는가
|
||||
생물 → 거품을 얼마나 버는가
|
||||
```
|
||||
|
||||
앞서는 생물도 장식도 똑같이 한 칸을 먹는 "놓는 것"이었다. 그러면 둘이 같은 자원(칸)을
|
||||
놓고 직접 경쟁해서, 최적 플레이는 언제나 "장식을 사지 말고 칸을 생물로 채우는 것"이 된다.
|
||||
꾸미기를 성장에 묶으려고 아름다움 배율을 넣어도 이 경쟁을 완전히 이기지는 못했다.
|
||||
집과 일꾼으로 나누면 경쟁이 **의존**으로 바뀐다. 집을 짓지 않으면 생물을 늘릴 방법이
|
||||
아예 없으므로, 꾸미는 것이 성장의 우회로가 아니라 전제 조건이 된다.
|
||||
|
||||
**정원은 어느 집이든 3으로 같게 두었다.** 그래야 큰 집과 작은 집이 다른 축에서
|
||||
갈린다. 3×2 난파선과 1×1 유리병 편지가 똑같이 3마리를 들이므로, 넓은 집은
|
||||
아름다움으로, 좁은 집은 칸 효율로 값을 한다. 정원을 크기에 비례시키면 큰 집이
|
||||
모든 면에서 우월해져 선택이 사라진다. 집마다 다르게 하고 싶으면 카탈로그 항목에
|
||||
`capacity`를 넣으면 된다(`Catalog.capacity_of`).
|
||||
|
||||
**집은 같은 종류라도 여러 채 짓는다.** 정원을 늘리는 것이 성장의 주된 축이므로,
|
||||
장식처럼 종류당 한 번만 살 수 있으면 상한이 곧바로 막힌다. 값은 생물과 똑같이
|
||||
지을수록 ×1.15로 오른다. 집이 거품을 직접 벌지 않게 둔 것도 이 때문이다 —
|
||||
정원과 생산을 한 물건이 동시에 주면 곱연산이 두 번 걸려 곡선이 터진다.
|
||||
|
||||
**어느 생물이 어느 집에 사는지는 저장하지 않는다.** 집은 정원만 주므로 어느 집에
|
||||
살든 결과가 같다. 저장하면 집을 헐거나 옮길 때 재배정 로직과 인덱스 관리가 붙는데,
|
||||
그 복잡도로 사는 것이 아무것도 없다. 그릴 때 구역마다 집을 격자 순서로, 생물을
|
||||
카탈로그 순서로 세워 채운다(`Aquarium._assign_fish`). 그래서 저장 형식에서 생물은
|
||||
위치 없이 `{id: 마리 수}`다.
|
||||
|
||||
**막다른 길을 두 군데서 막는다.** 수입이 생물에서만 나오므로 생물이 0이 되면 되돌아올
|
||||
길이 없다. 그래서 마지막 한 마리는 내보낼 수 없고(`can_release_fish`), 살고 있는
|
||||
생물이 갈 곳이 없어지는 집은 헐 수 없다(`can_remove_house`).
|
||||
|
||||
**칸이 곧 집의 한계다.** 클리커 장르는 보통 한 종류를 무한히 사서 가격 곡선으로만
|
||||
속도를 조절한다. 그러면 배치는 장식이 되고, 꾸미기는 성장과 무관한 곁가지가 된다.
|
||||
여기서는 격자 칸이 보유 개수를 직접 제한한다. "무엇을 어디에 놓을까"가
|
||||
매 순간 진짜 고민이 되고, 자리를 늘리는 유일한 길이 다음 구역을 여는 것이라
|
||||
꾸미기·성장·진행이 한 줄로 이어진다.
|
||||
여기서는 격자 칸이 지을 수 있는 집 수를 직접 제한하고, 그 집이 다시 생물 수를
|
||||
제한한다. "무엇을 어디에 지을까"가 매 순간 진짜 고민이 되고, 자리를 늘리는 마지막
|
||||
길이 다음 구역을 여는 것이라 꾸미기·성장·진행이 한 줄로 이어진다.
|
||||
|
||||
**직교 격자 + 3/4 부감 시점을 쓴다.** 아이소메트릭은 가로·세로 두 축이 모두 화면
|
||||
높이를 먹기 때문에(높이 = (열+행) × 타일높이 / 2) 220px 스트립에서는 격자가
|
||||
@@ -37,7 +91,7 @@
|
||||
|
||||
**꾸미기와 성장을 하나로 묶었다.** 방치형 게임에서 꾸미기는 보통 "번 돈을 쓰는 곳"에
|
||||
그치고, 그러면 꾸미기가 성장의 경쟁 상대가 되어 아무도 꾸미지 않는다.
|
||||
그래서 모든 구매물이 **아름다움(beauty)** 수치를 가지고, 아름다움이 다시 전체 생산량에
|
||||
그래서 생물도 집도 모두 **아름다움(beauty)** 수치를 가지고, 아름다움이 다시 전체 생산량에
|
||||
곱해지도록 했다. 꾸미는 것과 최적으로 플레이하는 것이 같은 방향을 가리킨다.
|
||||
|
||||
```
|
||||
@@ -51,26 +105,32 @@
|
||||
|
||||
| 항목 | 값 | 위치 |
|
||||
| --- | --- | --- |
|
||||
| 생물 가격 성장률 | ×1.15 (장르 표준) | `Catalog.COST_GROWTH` |
|
||||
| 집 정원 | 3마리 (집마다 바꾸려면 `capacity`) | `Catalog.HOUSE_CAPACITY` |
|
||||
| 시작 상태 | 항아리 1채 + 거품돌·구피 1마리씩 | `Catalog.STARTER_HOUSES` / `STARTER_FISH` |
|
||||
| 물고기 공전 속도 | 0.5 rad/s (한 바퀴 약 12초) | `Aquarium.ORBIT_SPEED` |
|
||||
| 시작 거품 | 20 (세 번째 거품돌 값) | `Catalog.STARTER_BUBBLES` |
|
||||
| 업그레이드 | 생산 배율 6종, 다 사면 ×17.1 | `Catalog.UPGRADES` |
|
||||
| 가격 성장률 | ×1.15 — 생물도 집도 같다 | `Catalog.COST_GROWTH` |
|
||||
| 아름다움 배율 계수 | 0.02 | `Catalog.BEAUTY_MULT_SCALE` |
|
||||
| 오프라인 상한 | 8시간 | `SaveManager.OFFLINE_CAP_SECONDS` |
|
||||
| 오프라인 효율 | 50% | `SaveManager.OFFLINE_RATE` |
|
||||
| 자동 저장 | 30초 | `SaveManager.AUTOSAVE_INTERVAL` |
|
||||
| 상점 공개 조건 | 해금된 구역 + 가격의 50%를 벌어본 적 있음 | `GameState.is_revealed()` |
|
||||
| 구역 수 | 5 (구역당 생물 2 + 장식 2) | `Catalog.ZONES` |
|
||||
| 구역 수 | 5 (구역당 생물 2 + 집 2) | `Catalog.ZONES` |
|
||||
| 구역 해금 비용 | 30.0K / 3.00M / 400M / 40.0B | `Catalog.ZONES[i].unlock_cost` |
|
||||
| 구역 해금 아름다움 | 앞 구역에 90 / 500 / 2.20K / 8.00K | `Catalog.ZONES[i].require_beauty` |
|
||||
| 격자 | 구역당 9열 × 6행 (총 45×6 = 270칸) | `Catalog.ZONE_COLUMNS` / `GRID_ROWS` |
|
||||
| 물체 높이 | 칸 높이의 0.75~1.95배 | 각 아이템의 `height` |
|
||||
| 치울 때 환불 | 50% | `Catalog.REFUND_RATE` |
|
||||
| 정보 바 자리 | 위쪽 30px는 격자에서 뺌 | `Aquarium.GRID_TOP` |
|
||||
| 물체 높이 | 칸 높이의 0.75~1.95배 | 각 항목의 `height` |
|
||||
| 집 값 | 그 구역 싼 생물의 약 6배 | 각 집의 `base_cost` |
|
||||
| 헐거나 내보낼 때 환불 | 50% | `Catalog.REFUND_RATE` |
|
||||
| 정보 바 자리 | HUD가 재어 알려준 바 높이 + 6px | `HUDLayer.reserved_top()` |
|
||||
| 스트립 높이 | 160 / 220 / 300px | `Main.HEIGHTS` |
|
||||
|
||||
생물 10종·장식 10종을 구역당 2+2로 나눠 두었다. 한 구역을 70%쯤 채우면
|
||||
다음 구역 해금 비용이 대략 20~90분치 생산량이 되도록 잡아 두었지만,
|
||||
칸 제한이 들어오면서 곡선이 크게 달라졌으므로 실제로 플레이해 보며 다시 맞춰야 한다.
|
||||
현재 밸런스는 첫 몇 시간을 촘촘하게 만드는 데 맞춰져 있고, 실제로 플레이해 보며
|
||||
`Catalog`의 `base_cost` / `bps` / `beauty` 세 숫자만 조정하면 된다.
|
||||
생물 10종·집 10종을 구역당 2+2로 나눠 두었다. 집 값은 "그 구역에서 가장 싼 생물의
|
||||
약 6배"라는 규칙으로 잡아, 집 한 채 → 생물 세 마리 → 다시 집 한 채로 리듬이 번갈아
|
||||
오도록 했다. 집이 정원을 쥐게 되면서 곡선이 통째로 달라졌으므로, 실제 수치는
|
||||
플레이해 보며 다시 맞춰야 한다. 조정할 것은 `Catalog`의 `base_cost` / `bps` / `beauty`
|
||||
세 숫자와 `HOUSE_CAPACITY` 하나다.
|
||||
|
||||
## 데이터 추가하는 법
|
||||
|
||||
@@ -79,15 +139,37 @@
|
||||
|
||||
```gdscript
|
||||
{
|
||||
"id": "shrimp", "name": "청소새우", "shape": "fish",
|
||||
"id": "shrimp", "name": "청소새우", "zone": 0, "shape": "fish", "height": 0.8,
|
||||
"desc": "바닥을 훑고 다닌다.",
|
||||
"color": Color(0.9, 0.4, 0.4), "base_cost": 400.0, "bps": 3.0, "beauty": 5.0,
|
||||
},
|
||||
```
|
||||
|
||||
`shape`은 `plant` / `rock`(바닥 고정) / `fish` / `glow`(제자리 위를 떠다님) 중 하나이고,
|
||||
집은 `HOUSES`에 넣는다. 생물과 같은 항목을 쓰되 `bps` 대신 `size`(바닥에서 차지하는
|
||||
칸, 기본 1×1)를 쓰고, 정원을 3에서 바꾸고 싶을 때만 `capacity`를 더한다.
|
||||
|
||||
```gdscript
|
||||
{
|
||||
"id": "reef_cave", "name": "산호 동굴", "zone": 1, "shape": "rock",
|
||||
"size": Vector2i(2, 2), "height": 1.3,
|
||||
"desc": "틈마다 물고기가 숨는다.",
|
||||
"color": Color(0.9, 0.5, 0.5), "base_cost": 30000.0, "beauty": 70.0,
|
||||
},
|
||||
```
|
||||
|
||||
`shape`은 두 계통으로 갈라져 있다. 섞어 쓰면 화면에서 "건물"과 "사는 것"이 구분되지 않는다.
|
||||
|
||||
| 계통 | 값 | 움직임 |
|
||||
| --- | --- | --- |
|
||||
| 생물 | `plant` / `rock` | 집 둘레 제자리에 붙어 선다 |
|
||||
| 생물 | `fish` / `glow` | 집 둘레를 돌며 뒤로 돌아 들어갔다 나온다 |
|
||||
| 집 | `vessel` / `wreck` / `pillar` | 바닥에 고정 |
|
||||
|
||||
`scenes/aquarium/tile_item.gd`가 형태별로 서 있는 모습을 직접 그린다.
|
||||
`size`는 바닥에서 차지하는 칸(기본 1×1), `height`는 칸 높이의 몇 배로 서는지다.
|
||||
`height`는 칸 높이의 몇 배로 서는지다.
|
||||
`zone`은 어느 구역에 속하는지다(0 = 얕은 바다). 그 구역이 열려야 상점에 나타나고,
|
||||
집은 그 구역 밖에 지을 수 없다(구역 경계를 걸치는 것도 안 된다).
|
||||
생물은 자기 구역의 집에만 들어간다.
|
||||
|
||||
물빛 오버레이가 덮이면 형태가 바닥에 묻히므로, 모든 면에 어두운 테두리를 두른다
|
||||
(`_fill()`). 테두리를 빼면 무엇인지 읽히지 않는다.
|
||||
@@ -95,6 +177,21 @@
|
||||
## 아트
|
||||
|
||||
지금은 스프라이트 없이 `_draw()`로 도형을 그린다. 색과 형태만으로 구분되는 자리 표시용이다.
|
||||
|
||||
**집과 생물의 형태를 아예 갈라 두었다.** 처음에는 집 10채 중 8채가 생물과 같은
|
||||
`rock`이어서, 화면에서 항아리와 거품돌이 색만 다른 같은 덩어리로 보였다.
|
||||
지금은 집이 `vessel`(배가 부르고 입구가 뚫린 그릇) / `wreck`(옆으로 누운 뼈대와 갈비뼈
|
||||
사이의 빈틈) / `pillar`(이음매가 있는 기둥) 셋만 쓰고, 셋 다 물고기가 드나들 만한
|
||||
틈이나 입구를 실루엣에 드러낸다. 생물은 이 셋을 쓰지 않는다.
|
||||
`tests/smoke.gd`의 "형태 계통"이 이 규칙이 깨지지 않는지 지킨다.
|
||||
|
||||
**헤엄치는 생물은 집 둘레를 돈다.** `fish`/`glow`는 제자리에 서지 않고 집을 중심으로
|
||||
납작한 타원 궤도를 돈다(`Aquarium._orbit_center` / `TileItem.set_orbit`).
|
||||
노드의 원점 자체가 궤도를 따라 움직이므로 **Y 정렬이 앞뒤를 알아서 바꿔준다** —
|
||||
뒤로 돌면 집에 가리고 앞으로 나오면 집 앞에 그려진다. 여기에 뒤쪽에서 크기를 0.72배,
|
||||
투명도를 0.45로 낮춰 "집 안으로 들어갔다 나온다"로 읽히게 했다.
|
||||
같은 집의 물고기는 정원 수만큼 위상을 나눠 갖고, 집마다 도는 방향이 반대다.
|
||||
바닥에 붙는 `plant`/`rock`은 돌지 않는다 — 해초가 집 주위를 헤엄치면 이상하다.
|
||||
실제 아트가 생기면 `assets/art/`에 넣고 `tile_item.gd`를
|
||||
`Sprite2D` 기반으로 바꾸면 된다(카탈로그에 `texture` 항목 추가).
|
||||
|
||||
@@ -115,17 +212,13 @@
|
||||
|
||||
## 아직 안 만든 것 (우선순위 순)
|
||||
|
||||
1. **꾸미기 배치 자유도** — 지금은 산 물건이 자기 구역 안 랜덤 위치에 놓인다.
|
||||
드래그로 옮기고 위치를 저장하면 "내 바다"라는 느낌이 훨씬 강해진다.
|
||||
`DecorSprite`에 위치를 저장하고 `GameState.producers`를 개수 대신
|
||||
인스턴스 배열로 바꾸는 작업이 필요하다.
|
||||
2. **두 번째 화폐(진주)** — 아름다움에 비례해 아주 천천히 쌓이고,
|
||||
특별한 장식만 살 수 있게. 거품 인플레이션과 분리된 장기 목표가 생긴다.
|
||||
3. **환생/이사** — 더 큰 수조로 옮기면서 영구 배율을 얻는 장르 표준 루프.
|
||||
4. **소리** — 물소리와 거품 소리. 위젯이라 기본은 음소거가 맞을 듯.
|
||||
5. **트레이 아이콘** — Godot 기본 기능에 없어서 GDExtension이나 별도 런처가 필요하다.
|
||||
1. **두 번째 화폐(진주)** — 아름다움에 비례해 아주 천천히 쌓이고,
|
||||
특별한 집만 지을 수 있게. 거품 인플레이션과 분리된 장기 목표가 생긴다.
|
||||
2. **환생/이사** — 더 큰 수조로 옮기면서 영구 배율을 얻는 장르 표준 루프.
|
||||
3. **소리** — 물소리와 거품 소리. 위젯이라 기본은 음소거가 맞을 듯.
|
||||
4. **트레이 아이콘** — Godot 기본 기능에 없어서 GDExtension이나 별도 런처가 필요하다.
|
||||
당장은 작업표시줄 창으로 충분.
|
||||
6. **폭을 화면 일부만 쓰기** — 지금은 항상 폭 전체다. 왼쪽/가운데/오른쪽 정렬과
|
||||
5. **폭을 화면 일부만 쓰기** — 지금은 항상 폭 전체다. 왼쪽/가운데/오른쪽 정렬과
|
||||
폭 비율 선택이 있으면 넓은 모니터에서 덜 부담스럽다.
|
||||
|
||||
## 한글 문장
|
||||
|
||||
Reference in New Issue
Block a user