Files
Cinderwild/scripts/world/day_night.gd
Paul Bailey fc7eb3cfed M1b: survival stats, day/night, fire fuel, cooking, foraging, torches, death + grave
- Health, stamina, hunger, warmth and wet with HUD bars, status tags and clock
- 20-minute day/night cycle with moon, stars and lit clouds
- Campfire fuel (2 min start, 15 min max), heat radius, cooking needs a lit fire
- Berry bushes (regrow 10 min), mushrooms, roasted mushroom, torch
- Stamina costs for sprint, jump and harvest hits
- Death respawns at island start and leaves a grave bag
- Saves include stats, time of day, fire fuel, regrowth timers and graves
- New test_survival_stats suite; tests fail if a script error aborts them
2026-11-04 01:19:41 +00:00

117 lines
4.3 KiB
GDScript

extends Node
## Day/night cycle. Moves the sun along its arc and drives sky colours, sun and
## moon light, ambient light and fog from the time of day.
##
## Time is a fraction of a day (0 = midnight, 0.25 = 06:00 sunrise,
## 0.5 = noon, 0.75 = 18:00 sunset). One day lasts `day_length` real seconds
## (20 min). Saved with the world (see world.gd collect_save).
signal new_day(day: int)
@export var day_length := 1200.0
@export var sun_tilt_deg := 35.0 ## arc leans toward +Z, so noon sun sits at 55°
const DAY_BIAS := 0.2
var day := 1
var time := 8.0 / 24.0
var frozen := false ## title-screen backdrop: hold a fixed time
var sun: DirectionalLight3D
var moon: DirectionalLight3D
var env: Environment
var sky_mat: ShaderMaterial
var daylight := 1.0 ## 0 at night, 1 in full day (smooth)
var sun_height := 1.0 ## sin of sun elevation (-1..1)
var _sky_timer := 0.0
# Palette: [zenith, horizon, fog]
const DAY_COLS := [Color(0.24, 0.44, 0.72), Color(0.72, 0.79, 0.85), Color(0.70, 0.77, 0.85)]
const DUSK_COLS := [Color(0.22, 0.27, 0.48), Color(0.98, 0.62, 0.38), Color(0.82, 0.62, 0.50)]
const NIGHT_COLS := [Color(0.02, 0.03, 0.07), Color(0.07, 0.09, 0.16), Color(0.07, 0.09, 0.14)]
func setup(world_sun: DirectionalLight3D, environment: Environment) -> void:
sun = world_sun
env = environment
sky_mat = env.sky.sky_material as ShaderMaterial
moon = DirectionalLight3D.new()
moon.name = "Moon"
moon.light_color = Color(0.55, 0.65, 1.0)
moon.shadow_enabled = false
moon.directional_shadow_max_distance = 120.0
moon.sky_mode = DirectionalLight3D.SKY_MODE_LIGHT_ONLY # keep the sky shader's LIGHT0 = sun
sun.add_sibling(moon)
apply(true)
func hours() -> float:
return time * 24.0
func clock_text() -> String:
var h := hours()
return "Day %d %02d:%02d" % [day, int(h), int(fmod(h, 1.0) * 60.0)]
func is_night() -> bool:
return daylight < 0.3
func advance(seconds: float) -> void:
time += seconds / day_length
while time >= 1.0:
time -= 1.0
day += 1
new_day.emit(day)
apply(false)
func _process(delta: float) -> void:
if not frozen:
advance(delta)
## Updates lights every call; the sky material (which triggers a radiance
## rebuild) only a few times a second unless `force`.
func apply(force: bool) -> void:
if sun == null:
return
var a := (time - 0.25) * TAU
var t := deg_to_rad(sun_tilt_deg)
# +DAY_BIAS lifts the whole arc: sunrise ~05:00, sunset ~19:00, so days
# are longer than nights and dusk lingers.
var to_sun := Vector3(cos(a), sin(a) * cos(t) + DAY_BIAS, sin(a) * sin(t)).normalized()
sun_height = to_sun.y
daylight = smoothstep(-0.2, 0.25, to_sun.y)
var low := 1.0 - smoothstep(0.05, 0.45, absf(to_sun.y)) # 1 near the horizon
sun.global_basis = Basis.looking_at(-to_sun if to_sun.y > -0.2 else Vector3.DOWN, Vector3.FORWARD)
sun.light_energy = 1.8 * smoothstep(-0.03, 0.15, to_sun.y)
sun.light_color = Color(1.0, 0.95, 0.83).lerp(Color(1.0, 0.58, 0.32), low)
sun.visible = sun.light_energy > 0.01
sun.shadow_enabled = sun.visible
var moon_dir := Vector3(-to_sun.x, absf(to_sun.y) * 0.8 + 0.25, -to_sun.z * 0.5).normalized()
moon.global_basis = Basis.looking_at(-moon_dir, Vector3.FORWARD)
moon.light_energy = 0.38 * (1.0 - daylight)
moon.visible = moon.light_energy > 0.01
moon.shadow_enabled = moon.visible and not sun.visible
env.ambient_light_energy = lerpf(0.3, 1.0, daylight)
env.fog_light_energy = lerpf(0.25, 1.0, daylight)
env.volumetric_fog_density = lerpf(0.0025, 0.0045, daylight)
_sky_timer -= get_process_delta_time()
if not force and _sky_timer > 0.0:
return
_sky_timer = 0.25
var cols: Array = []
for i in range(3):
var dayc: Color = DAY_COLS[i].lerp(DUSK_COLS[i], low * daylight)
cols.append(NIGHT_COLS[i].lerp(dayc, daylight))
env.fog_light_color = cols[2]
if sky_mat != null:
sky_mat.set_shader_parameter("zenith_col", cols[0])
sky_mat.set_shader_parameter("horizon_col", cols[1])
sky_mat.set_shader_parameter("energy", lerpf(0.35, 1.0, daylight))
sky_mat.set_shader_parameter("star_strength", 1.0 - smoothstep(0.0, 0.35, daylight))
sky_mat.set_shader_parameter("cloud_lit", Color(0.16, 0.18, 0.24).lerp(Color(1, 1, 1), daylight))
sky_mat.set_shader_parameter("sun_tint", Color(1.0, 0.86, 0.62).lerp(Color(1.0, 0.55, 0.3), low))
## 0..100 "how warm the air is" before fires, shelter and clothes.
func air_warmth() -> float:
return lerpf(22.0, 68.0, daylight)