먼 바다 배경·하루의 빛·층 구조·감상 모드
네 가지 작업이 같은 함수들을 겹쳐 고쳐서 한 커밋으로 묶는다. 쪼개려면 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:
+12
-19
@@ -4,13 +4,14 @@ extends Node
|
||||
|
||||
# --- 격자 ---
|
||||
## 바다는 3/4 부감 격자다. 바닥은 위에서 내려다보고, 그 위에 놓인 것은 서 있는 모습으로 그린다.
|
||||
## 세로 행 수는 고정이고, 칸의 픽셀 크기는 스트립 크기에 맞춰 늘어난다.
|
||||
## 이렇게 해야 창 높이를 바꿔도 저장해 둔 배치가 그대로 유효하다.
|
||||
## 한 화면에 한 구역만 담기므로 열 수는 화면 가로를 나눠 갖는다.
|
||||
const GRID_ROWS := 6
|
||||
## 구역 하나가 차지하는 열 수. 구역당 GRID_ROWS * ZONE_COLUMNS 칸이 배치 공간이 된다.
|
||||
const ZONE_COLUMNS := 9
|
||||
## 스트립(가로로 길고 세로로 짧은 창)에서 칸이 지나치게 납작해지지 않는 수를 고른다.
|
||||
const ZONE_COLUMNS := 36
|
||||
|
||||
## 구역. 왼쪽(얕은 바다)에서 오른쪽(열수구)으로 갈수록 깊어진다.
|
||||
## 구역 = 층. 위층(얕은 바다)에서 아래층(열수구)으로 내려갈수록 깊어진다.
|
||||
## 한 번에 한 층만 화면에 담기고, 층 사이는 왼쪽 층 선택기로 오간다.
|
||||
## unlock_cost : 해금에 드는 거품
|
||||
## require_beauty: 바로 앞 구역에 쌓인 아름다움이 이만큼은 되어야 한다.
|
||||
## "앞 구역을 충분히 꾸며야 다음이 열린다"는 규칙.
|
||||
@@ -328,25 +329,17 @@ func first_id_of_zone(kind: String, zone_index: int) -> String:
|
||||
|
||||
# --- 색과 깊이 ---
|
||||
|
||||
## 스트립 가로 위치 f(0~1)에서의 값.
|
||||
## 구역 중심에서는 그 구역 값 그대로, 경계에서는 이웃과 반씩 섞인다.
|
||||
## 바닥과 물빛이 같은 함수를 쓰기 때문에 경계가 어긋나지 않는다.
|
||||
func _ramp_bounds(f: float) -> Array:
|
||||
## 바다 전체 가로 위치 f(0~1)가 어느 층인지.
|
||||
## 예전에는 층끼리 이어 붙어 있어 경계에서 색을 섞었지만, 이제 한 화면에
|
||||
## 한 층만 보이므로 섞을 이유가 없다. 층마다 제 색을 그대로 쓴다.
|
||||
func zone_at(f: float) -> int:
|
||||
var n := ZONES.size()
|
||||
var z := clampf(f, 0.0, 1.0) * n - 0.5
|
||||
if z <= 0.0:
|
||||
return [0, 0, 0.0]
|
||||
if z >= n - 1:
|
||||
return [n - 1, n - 1, 0.0]
|
||||
var i := int(floor(z))
|
||||
return [i, i + 1, z - i]
|
||||
return clampi(int(floor(clampf(f, 0.0, 0.9999) * n)), 0, n - 1)
|
||||
|
||||
|
||||
func ramp_color(key: String, f: float) -> Color:
|
||||
var b := _ramp_bounds(f)
|
||||
return Color(ZONES[b[0]][key]).lerp(ZONES[b[1]][key], b[2])
|
||||
return ZONES[zone_at(f)][key]
|
||||
|
||||
|
||||
func ramp_float(key: String, f: float) -> float:
|
||||
var b := _ramp_bounds(f)
|
||||
return lerpf(float(ZONES[b[0]][key]), float(ZONES[b[1]][key]), b[2])
|
||||
return float(ZONES[zone_at(f)][key])
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
extends Node
|
||||
## 실제 시계에 맞춰 바다의 빛을 바꾼다.
|
||||
##
|
||||
## 게임 안의 시간을 따로 돌리지 않고 바깥 시각을 그대로 쓴다.
|
||||
## 이 위젯은 하루 종일 화면 아래에 켜져 있는 물건이라, 밤에 켰는데 바다가 한낮이면
|
||||
## 오히려 어색하다. 새벽에 일하면 바다도 새벽이어야 창밖처럼 읽힌다.
|
||||
##
|
||||
## 이름(새벽/아침/낮/저녁/밤)은 구간으로 끊지만 빛은 끊지 않는다.
|
||||
## 색과 밝기는 시각 사이를 계속 보간해서, 해가 지는 동안 서서히 물든다.
|
||||
|
||||
## 이름이 바뀐 순간에만 나온다. 색이 바뀔 때마다 나오지는 않는다.
|
||||
signal phase_changed(phase_name: String)
|
||||
|
||||
## 구간의 시작 시각. 마지막(밤)은 자정을 넘겨 다음 새벽까지 이어진다.
|
||||
const PHASES := [
|
||||
{"hour": 4.0, "name": "새벽"},
|
||||
{"hour": 6.5, "name": "아침"},
|
||||
{"hour": 10.0, "name": "낮"},
|
||||
{"hour": 17.0, "name": "저녁"},
|
||||
{"hour": 20.0, "name": "밤"},
|
||||
]
|
||||
|
||||
## 하루의 빛. 사이 시각은 이 표를 보간해서 만든다.
|
||||
## tint : 바다 전체에 곱하는 색. 흰색이면 한낮 그대로.
|
||||
## light: 수면에서 내려오는 빛의 양. 물결 무늬와 빛줄기의 세기가 여기 붙는다.
|
||||
const LIGHT_KEYS := [
|
||||
{"hour": 0.0, "tint": Color(0.26, 0.33, 0.62), "light": 0.08},
|
||||
{"hour": 4.0, "tint": Color(0.48, 0.44, 0.80), "light": 0.18},
|
||||
{"hour": 6.0, "tint": Color(0.88, 0.68, 0.74), "light": 0.42},
|
||||
{"hour": 7.5, "tint": Color(1.00, 0.87, 0.73), "light": 0.72},
|
||||
{"hour": 11.0, "tint": Color(1.00, 1.00, 1.00), "light": 1.00},
|
||||
{"hour": 16.0, "tint": Color(1.00, 0.98, 0.92), "light": 0.95},
|
||||
{"hour": 18.5, "tint": Color(1.00, 0.72, 0.50), "light": 0.55},
|
||||
{"hour": 20.5, "tint": Color(0.44, 0.44, 0.72), "light": 0.20},
|
||||
{"hour": 24.0, "tint": Color(0.26, 0.33, 0.62), "light": 0.08},
|
||||
]
|
||||
|
||||
## 빛은 천천히 변하므로 매 프레임 다시 잴 이유가 없다.
|
||||
const REFRESH_INTERVAL := 0.5
|
||||
|
||||
## 0 이상이면 그 시각으로 고정한다. 테스트와 눈으로 확인할 때 쓴다.
|
||||
var forced_hour := -1.0:
|
||||
set(value):
|
||||
forced_hour = value
|
||||
refresh()
|
||||
|
||||
var tint := Color.WHITE
|
||||
var light := 1.0
|
||||
var phase := ""
|
||||
|
||||
var _timer := 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# 개발 중 특정 시각을 눈으로 확인할 때: godot --path . -- --hour=21
|
||||
for arg in OS.get_cmdline_user_args():
|
||||
if arg.begins_with("--hour="):
|
||||
forced_hour = float(arg.trim_prefix("--hour="))
|
||||
refresh()
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_timer += delta
|
||||
if _timer < REFRESH_INTERVAL:
|
||||
return
|
||||
_timer = 0.0
|
||||
refresh()
|
||||
|
||||
|
||||
## 지금 몇 시인지. 소수점까지 있는 시각(13.5 = 오후 1시 30분).
|
||||
func hour() -> float:
|
||||
if forced_hour >= 0.0:
|
||||
return fposmod(forced_hour, 24.0)
|
||||
var t := Time.get_time_dict_from_system()
|
||||
return float(t.hour) + float(t.minute) / 60.0 + float(t.second) / 3600.0
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
var h := hour()
|
||||
tint = tint_at(h)
|
||||
light = light_at(h)
|
||||
|
||||
var next_phase := phase_at(h)
|
||||
if next_phase != phase:
|
||||
phase = next_phase
|
||||
phase_changed.emit(phase)
|
||||
|
||||
|
||||
## 그 시각이 어느 구간인지. 자정부터 첫 구간 전까지는 전날 밤이 이어진다.
|
||||
func phase_at(h: float) -> String:
|
||||
var found := String(PHASES[PHASES.size() - 1].name)
|
||||
for p in PHASES:
|
||||
if h >= float(p.hour):
|
||||
found = String(p.name)
|
||||
return found
|
||||
|
||||
|
||||
func tint_at(h: float) -> Color:
|
||||
var b := _bounds(h)
|
||||
return Color(LIGHT_KEYS[b[0]].tint).lerp(LIGHT_KEYS[b[1]].tint, b[2])
|
||||
|
||||
|
||||
func light_at(h: float) -> float:
|
||||
var b := _bounds(h)
|
||||
return lerpf(float(LIGHT_KEYS[b[0]].light), float(LIGHT_KEYS[b[1]].light), b[2])
|
||||
|
||||
|
||||
## 그 시각을 감싸는 두 열쇠와 사이 비율. [앞, 뒤, 0~1]
|
||||
func _bounds(h: float) -> Array:
|
||||
var t := clampf(h, 0.0, 24.0)
|
||||
for i in range(LIGHT_KEYS.size() - 1):
|
||||
var a := float(LIGHT_KEYS[i].hour)
|
||||
var b := float(LIGHT_KEYS[i + 1].hour)
|
||||
if t <= b:
|
||||
return [i, i + 1, inverse_lerp(a, b, maxf(t, a))]
|
||||
var last := LIGHT_KEYS.size() - 1
|
||||
return [last, last, 0.0]
|
||||
@@ -0,0 +1 @@
|
||||
uid://bw8bwvut3qhox
|
||||
Reference in New Issue
Block a user