먼 바다 배경·하루의 빛·층 구조·감상 모드

네 가지 작업이 같은 함수들을 겹쳐 고쳐서 한 커밋으로 묶는다.
쪼개려면 hunk 단위로 갈라야 하는데 서로 얽혀 있어 오히려 위험하다.

먼 바다 배경 (backdrop.gd)
- 3/4 부감에서 화면 위쪽은 더 먼 곳이다. 거기까지 모래를 깔면
  바다가 아니라 사막처럼 보여서, 위쪽 띠를 물로 비웠다.
- 먼 물 그라디언트 → 능선 → 먼 실루엣 순으로 깊이를 만든다.
- Seabed가 뒷줄일수록 색을 물빛 쪽으로 당겨 공기원근을 준다.
- 빛줄기를 알파를 더하는 방향으로 바꿨다. 걷어내는 방향이면
  바닥만 밝아지고 물기둥은 텅 빈 채로 남는다.

하루의 빛 (day_cycle.gd)
- 실제 시각에 맞춰 새벽/아침/낮/저녁/밤. 이름은 구간으로 끊고
  색과 밝기는 계속 보간한다.
- 밤에는 물결과 빛줄기가 잦아들고, 스스로 빛나는 것은 어둠을
  되밀어 오히려 도드라진다.
- godot --path . -- --hour=21 로 특정 시각을 눈으로 확인할 수 있다.

한 화면에 한 층
- 가로 연속 스크롤을 버리고 층 전환으로 바꿨다. 위층(얕은 바다)에서
  아래층(열수구)으로 내려간다. 층 선택기·휠·↑↓ 키.
- 층은 내부적으로 여전히 옆으로 늘어서 있고 화면이 한 구간만 비춘다.
  갈아탈 때만 세로로 미끄러뜨려 깊이가 위아래로 읽히게 했다.
- 칸 크기를 가로에서 정하도록 뒤집고 ZONE_COLUMNS를 36으로 올렸다.
  층당 칸이 96 -> 216이라 정원과 해금 곡선은 다시 봐야 한다.
- 한 화면에 한 층만 보이므로 층 색은 이웃과 섞지 않는다.

감상 모드 (F11)
- 창을 화면 전체로. 세로로 남는 공간은 격자가 아니라 물기둥에 준다.
  격자를 키우면 타일만 커지고 보이는 열은 오히려 줄어든다.
- 창을 화면과 픽셀까지 똑같이 맞추면 Windows가 독점 전체화면으로
  승격시켜 size 지정이 먹지 않는다. usable_rect까지만 쓴다.

그 밖에
- 저장 window_state: scroll_x -> layer
- 알림이 정보 바와 겹치던 것을 바 아래로 옮겼다
- 난파선 선체 폴리곤이 제 몸을 가로질러 삼각분할이 실패하던 것 수정

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-02 12:41:36 +09:00
parent 89069b06f0
commit 519e4980de
19 changed files with 942 additions and 170 deletions
+140 -1
View File
@@ -119,7 +119,8 @@
| 구역 수 | 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` |
| 격자 | 구역당 16× 6행 (총 80×6 = 480칸) | `Catalog.ZONE_COLUMNS` / `GRID_ROWS` |
| 칸 가로:세로 | 1.15 | `Aquarium.TILE_ASPECT` |
| 물체 높이 | 칸 높이의 0.75~1.95배 | 각 항목의 `height` |
| 집 값 | 그 구역 싼 생물의 약 6배 | 각 집의 `base_cost` |
| 헐거나 내보낼 때 환불 | 50% | `Catalog.REFUND_RATE` |
@@ -198,6 +199,26 @@
바닥은 `seabed.gd`가 칸마다 그리고, 그 위에 `water.gdshader`가 물빛과
일렁이는 빛 그물을 덮는다. 깊이감은 이 오버레이의 `murk`가 만든다.
## 먼 바다 배경
3/4 부감에서 화면 위쪽은 "더 먼 곳"이다. 거기까지 모래를 깔면 바다가 아니라
사막처럼 보였다. 그래서 격자 위쪽 띠를 `backdrop.gd`가 물로 채운다.
- **먼 물** — 위로 갈수록 어두워지는 세로 그라디언트. 열마다 사각형을 그리고
정점 색을 먹여 가로(구역)·세로(거리) 양쪽으로 동시에 번지게 한다.
- **능선** — 격자 바로 위에 물결치는 먼 바닥. 이 선이 수평선 노릇을 한다.
- **먼 실루엣** — 능선 위의 낮고 넓은 덩어리들. 무엇인지 알아볼 필요는 없고
"저 너머에도 뭔가 있다"는 신호만 주면 된다. 가늘고 높게 그리면 비석처럼
서 버려서 먼 풍경으로 안 읽힌다.
- **공기원근** — `seabed.gd`가 뒷줄(위쪽) 칸일수록 색을 물빛 쪽으로 22%까지
당겨서 멀어 보이게 한다.
- **빛줄기** — `water.gdshader`가 비스듬히 내려오는 빛을 얹는다. 세기는
`murk`의 반대(`_ray_strength()`)라 얕은 바다에서만 뚜렷하고 심해에서는 사라진다.
띠의 높이는 HUD가 `reserved_top()`으로 알려주는 값이다. 정보 바가 그 안에 앉고,
`BAR_CLEARANCE`만큼 남는 자리에 배경이 드러난다. 바를 키우면 배경이 가려지고
격자가 좁아지므로, 이 셋은 같이 조정해야 한다.
## 창 동작 (Windows)
- 투명·테두리 없음: `project.godot``per_pixel_transparency/allowed` + `size/transparent`
@@ -232,3 +253,121 @@
`SaveManager.persistence_enabled`를 끄면 저장/자동저장/종료 시 저장이 모두 멎는다.
스모크 테스트가 이걸 끈다. 끄지 않으면 테스트가 끝나면서 초기화된 상태를
실제 저장 파일에 덮어써 진행도가 날아간다.
## 화면보다 넓은 바다
칸 크기를 화면 너비에서 나누지 않고 **행 높이에서 정한다**(`cell_size()`).
세로는 격자 높이 ÷ 행 수, 가로는 거기에 `TILE_ASPECT`를 곱한 값이다.
그래서 구역의 열을 늘리면 칸이 작아지는 대신 **바다가 화면 밖으로 넓어진다.**
좌표계가 둘로 갈린다.
- **화면 좌표** — 입력(`event.position`), 휴지통 영역
- **바다 좌표** — 격자·집·생물·배경이 사는 곳
`World` 노드 하나가 이 둘을 잇는다. `scroll_x`만큼 `World.position.x`를 음수로
밀어 두고, 입력이 들어오면 `to_world()`로 되돌린다. 바다에 속한 것은 전부
`World` 아래에 있으므로 스크롤에 자동으로 따라온다 — 배경, 바닥, 집, 생물,
물빛, 구분선, 구역 이름, 잠금 베일, 배치 미리보기, 물 만짐 연출까지.
**둘러보는 법**: 빈 바닥을 끌거나(`_begin_pan()`) 마우스 휠. 무언가를 누르고
끌면 그것을 옮기는 것이고, 빈 곳을 누르고 끌면 바다를 미는 것이다 —
`_press_ref`가 비었는지로 갈린다.
**자동으로 따라가는 곳**: 구역이 새로 열렸을 때와, 상점에서 집을 골랐을 때.
후자가 없으면 화면 밖 구역에는 지을 자리를 찍을 수가 없다.
보던 위치는 `window_state["scroll_x"]`에 저장한다. 격자 크기가 정해지기 전에
복원값이 들어올 수 있어서, 클램프 전의 요청값을 `_wanted_scroll`에 남겨 두었다가
`_on_layout_changed()`에서 다시 적용한다.
## 하루의 빛
`autoload/day_cycle.gd`가 **실제 시각**에 맞춰 바다의 빛을 바꾼다.
게임 안의 시간을 따로 돌리지 않은 이유는 이것이 하루 종일 화면 아래에 켜져 있는
물건이기 때문이다. 새벽에 일하는데 바다가 한낮이면 창밖처럼 읽히지 않는다.
**이름은 끊고 빛은 잇는다.** 새벽/아침/낮/저녁/밤은 시각으로 구간을 나누지만
(`PHASES`), 색과 밝기는 `LIGHT_KEYS`를 계속 보간한다. 해가 지는 동안 서서히
물들어야지, 19시가 되는 순간 화면이 딸깍 바뀌면 안 된다.
- `tint` — 바다에 **곱하는** 색. 밤에는 전체가 어두워지고 저녁에는 주황이 돈다.
- `light` — 수면에서 오는 빛의 양. 물결 무늬와 빛줄기(`sun` 유니폼)의 세기가 여기 붙는다.
`Aquarium._apply_daylight()``_daylit`에 담긴 층에만 `modulate`를 건다.
**배치 미리보기(Ghost)는 일부러 뺐다** — 밤이라고 초록/빨강 판정이 흐려지면
집을 어디에 놓을지 알 수 없다. HUD와 상점도 World 밖이라 늘 또렷하다.
**밤의 발광**: 전체에 어둠이 곱해지므로 스스로 빛나는 것(`glow`)은 그대로 두면
같이 묻힌다. `_draw_glow()``1 - light`만큼 색을 끌어올리고 후광을 넓혀,
어두울수록 오히려 도드라지게 했다. 심해 해파리가 밤에 살아나는 것이 이 때문이다.
**눈으로 확인하기**: `godot --path . -- --hour=21` 처럼 시각을 고정할 수 있다.
테스트도 `DayCycle.forced_hour`로 같은 문을 쓴다.
## 감상 모드 (전체화면)
F11이나 메뉴로 창을 화면 전체로 키운다. 그냥 창만 키우면 안 되는 이유가 있다 —
칸 크기가 격자 높이에서 나오기 때문에, 화면이 높아질수록 **타일만 거대해지고
보이는 열은 오히려 줄어든다.** 감상하려고 켰는데 볼 수 있는 바다가 좁아진다.
그래서 세로로 남는 공간을 격자가 아니라 **물기둥**에 준다.
`Aquarium.grid_rect()`가 감상 모드에서는 아래쪽 `SHOWCASE_GRID_RATIO`(0.45)만
격자로 쓰고, 나머지 위쪽은 Backdrop이 먼 바다로 채운다. 바닥은 크게 보이면서
위로는 빛줄기가 내려오는 깊은 물이 열린다.
**물기둥을 비어 보이지 않게 하는 것들**
- 빛줄기를 *더하는* 방향으로 바꿨다. 전에는 알파를 낮춰 물을 걷어냈는데,
그러면 바닥만 밝아지고 물기둥은 텅 빈 채로 남는다. 이제는 알파를 올려
물 속에 떠 있는 밝은 기둥으로 그린다.
- 빛줄기 무늬의 y 계수를 줄였다. 크게 두면 화면이 높을 때 무늬가 가로로 누워
빛줄기로 안 보인다.
- 부유물은 바다 전체가 아니라 **지금 보이는 곳**에만 뿌린다(`_follow_view()`).
넓은 바다에 고르게 뿌리면 화면당 밀도가 낮아진다. `local_coords = false`
이미 나온 것은 그 자리에 머물러, 화면을 밀어도 물이 같이 끌려오지 않는다.
- 격자선은 옅게 누른다. 지금은 놓는 시간이 아니라 보는 시간이다.
**창 크기의 함정**: 창을 화면과 픽셀까지 똑같이 맞추면 Windows가 이를
독점 전체화면(`MODE_EXCLUSIVE_FULLSCREEN`)으로 승격시켜 버리고, 그 뒤로는
`size`를 넣어도 먹지 않아 **감상 모드에서 빠져나올 수 없다.** 그래서 감상 모드도
작업표시줄을 뺀 `usable_rect`까지만 쓴다. 보기에는 거의 같고 복귀는 확실하다.
칸 크기가 통째로 달라지므로 스크롤 픽셀값을 그대로 두면 엉뚱한 곳으로 튄다.
보고 있던 열을 기억했다가(`_center_column()`) 그 자리로 되돌린다(`look_at_column()`).
## 층 (한 화면에 한 지역)
예전에는 다섯 구역이 옆으로 이어져 있고 가로 스크롤로 훑었다. 지금은
**한 화면이 한 층**이고, 층은 위에서 아래로 얕은 바다 → 열수구 순으로 쌓인다.
**구현은 여전히 가로다.** 층 n은 열 `n * ZONE_COLUMNS`부터 시작하고, 화면은 그중
한 구간만 비춘다(`layer_offset()`). 저장 형식(전역 열 번호)을 건드리지 않아도 되고,
격자 계산도 그대로 쓸 수 있다.
**보이는 것만 세로다.** 층을 갈아탈 때 `_slide_to_layer()`가 가로 위치는 즉시
새 층으로 옮기고 세로만 화면 높이만큼 밀어 넣었다가 0으로 당긴다. 아래층으로 가면
아래에서 올라오고, 위층으로 가면 위에서 내려온다. 깊이가 세로로 읽히는 것은
이 연출뿐이지만, 그것으로 충분하다.
**칸 크기가 뒤집혔다.** 예전에는 높이에서 칸을 정하고 넘치는 만큼 옆으로 스크롤했다.
이제는 한 층이 화면을 꽉 채워야 하므로 **가로에서 정한다**
`cell_w = 화면너비 / ZONE_COLUMNS`, `cell_h = cell_w / TILE_ASPECT`.
격자는 언제나 화면 아래쪽에 붙고, 남는 위쪽은 물기둥이 된다.
스트립처럼 세로가 모자라면 `cell_h`를 눌러 넣는다.
`ZONE_COLUMNS`는 이 식 때문에 36으로 올렸다. 너무 적으면 스트립(가로로 길고
세로로 짧은 창)에서 칸이 3:1로 납작해진다. 대신 층당 칸이 96 → 216으로 늘었으므로
**정원과 해금 곡선은 다시 봐야 한다.**
**색은 층마다 평평하다.** 예전에는 구역 경계에서 색을 섞어 이음매를 감췄지만,
이제 한 화면에 한 층만 보이므로 섞을 이유가 없다. 섞으면 오히려 한 화면 안에서
좌우로 색이 흐르는데, 깊이는 더 이상 가로가 아니다. `Catalog.ramp_color()`
그 자리의 층 색을 그대로 돌려준다.
**층 선택기**(`scenes/ui/layer_bar.gd`)는 왼쪽 아래에 세로로 선다. 단추마다 그 층의
물빛을 입혀서, 목록만 봐도 위에서 아래로 어두워지는 것이 보인다. 잠긴 층도 눌러서
해금 조건을 보러 갈 수 있다.
함께 사라진 것: 가로 패닝, 구역 구분선(한 화면에 둘이 안 보이므로 무의미),
`window_state["scroll_x"]`(→ `["layer"]`).