Files
blub-blub/autoload/day_cycle.gd
T
ttipo 711cd453ff 게임 안에서 바다의 하루를 정할 수 있게
메뉴 > 시간 하위 메뉴에 세 갈래를 둔다.
- 실제 시각: 바깥 시계 그대로 (기본)
- 빠르게: 하루를 5분으로 압축해 돌린다
- 한 시각에 멈추기: 새벽/아침/낮/저녁/밤

프리셋의 이름과 시각을 DayCycle.FIXED_PRESETS 한곳에 같이 두어,
메뉴 항목과 실제로 적용되는 시각이 어긋날 수 없게 했다.
고른 값은 window_state의 time_mode/time_hour에 저장한다.

모드를 바꿀 때 지금 보이던 시각을 먼저 잡아 둔다.
mode를 먼저 바꾼 뒤 hour()를 부르면 이미 새 모드의 시계를 읽어
0시로 튀었다 -- 낮에 "빠르게"를 켜면 한밤중으로 떨어지는 버그였다.

명령줄 --hour=21은 그대로 살아 있고 FIXED 모드의 지름길이 되었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 13:58:30 +09:00

163 lines
5.3 KiB
GDScript

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
## 시간을 어떻게 굴릴지.
## REAL 바깥 시계를 그대로 (기본)
## FIXED 한 시각에 멈춰 둔다. 밤바다만 보고 싶을 때.
## FAST 하루를 몇 분으로 압축해 돌린다. 하루가 지나가는 걸 보고 싶을 때.
enum Mode { REAL, FIXED, FAST }
## 메뉴에 올리는 고정 시각들. 이름과 시각을 한곳에 둬서 메뉴와 어긋나지 않게 한다.
const FIXED_PRESETS := [
{"name": "새벽", "hour": 4.5},
{"name": "아침", "hour": 8.0},
{"name": "", "hour": 13.0},
{"name": "저녁", "hour": 18.7},
{"name": "", "hour": 22.0},
]
## 빠르게 돌릴 때 하루가 몇 분인지.
const FAST_DAY_MINUTES := 5.0
var mode := Mode.REAL:
set(value):
if mode == value:
return
# 지금 보이던 시각을 먼저 잡아 둔다. mode를 바꾼 뒤에 hour()를 부르면
# 이미 새 모드의 시계를 읽어 0시로 튀어 버린다.
var carry := hour()
mode = value
# 빠르게로 넘어갈 때는 보던 시각에서 이어 흐르게 한다.
# 0시로 떨어지면 한밤중으로 뚝 끊긴다.
if mode == Mode.FAST:
_fast_hour = carry
refresh()
## 고정 모드일 때 멈춰 있을 시각.
var fixed_hour := 13.0:
set(value):
fixed_hour = fposmod(value, 24.0)
refresh()
var tint := Color.WHITE
var light := 1.0
var phase := ""
var _fast_hour := 0.0
var _timer := 0.0
func _ready() -> void:
# 개발 중 특정 시각을 눈으로 확인할 때: godot --path . -- --hour=21
for arg in OS.get_cmdline_user_args():
if arg.begins_with("--hour="):
fixed_hour = float(arg.trim_prefix("--hour="))
mode = Mode.FIXED
refresh()
func _process(delta: float) -> void:
# 압축한 시계는 매 틀 굴려야 매끄럽게 흐른다. 화면 갱신만 뜸하게 한다.
if mode == Mode.FAST:
_fast_hour = fposmod(_fast_hour + delta * 24.0 / (FAST_DAY_MINUTES * 60.0), 24.0)
_timer += delta
if _timer < REFRESH_INTERVAL:
return
_timer = 0.0
refresh()
## 지금 몇 시인지. 소수점까지 있는 시각(13.5 = 오후 1시 30분).
func hour() -> float:
match mode:
Mode.FIXED: return fixed_hour
Mode.FAST: return _fast_hour
var t := Time.get_time_dict_from_system()
return float(t.hour) + float(t.minute) / 60.0 + float(t.second) / 3600.0
## "13:24" 처럼 보여줄 문자열.
func clock_text() -> String:
var h := hour()
return "%02d:%02d" % [int(h), int(fposmod(h * 60.0, 60.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]