Add synchronized time and weather systems
This commit is contained in:
parent
30ad9215ca
commit
58fb3d6605
37 changed files with 1900 additions and 17 deletions
|
|
@ -15,6 +15,8 @@ const FishingShopInteractionType = preload(
|
|||
@onready var _fishing_shop: FishingShopInteractionType = (
|
||||
_starter_island.get_fishing_shop()
|
||||
)
|
||||
@onready var _world_environment: WorldEnvironment = $Environment/WorldEnvironment
|
||||
@onready var _sun: DirectionalLight3D = $Environment/Sun
|
||||
|
||||
|
||||
func get_player_water_triggers() -> Array[PlayerWaterTrigger]:
|
||||
|
|
@ -40,6 +42,14 @@ func get_player_spawn_transform() -> Transform3D:
|
|||
return _starter_island.get_player_spawn_transform()
|
||||
|
||||
|
||||
func get_world_environment() -> WorldEnvironment:
|
||||
return _world_environment
|
||||
|
||||
|
||||
func get_sun() -> DirectionalLight3D:
|
||||
return _sun
|
||||
|
||||
|
||||
func get_fishable_water_regions() -> Array[FishableWaterRegion]:
|
||||
var waters: Array[FishableWaterRegion] = []
|
||||
for region: WorldRegion in _get_regions():
|
||||
|
|
|
|||
126
world/world_time_service.gd
Normal file
126
world/world_time_service.gd
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
class_name WorldTimeService
|
||||
extends Node
|
||||
|
||||
signal time_changed(time_hours: float, phase: Phase)
|
||||
signal phase_changed(phase: Phase)
|
||||
|
||||
enum Phase {
|
||||
DAWN,
|
||||
DAY,
|
||||
DUSK,
|
||||
NIGHT,
|
||||
}
|
||||
|
||||
const HOURS_PER_DAY: float = 24.0
|
||||
const REAL_SECONDS_PER_CYCLE: float = 60.0 * 60.0
|
||||
const HOURS_PER_REAL_SECOND: float = HOURS_PER_DAY / REAL_SECONDS_PER_CYCLE
|
||||
const DAY_START_HOUR: float = 8.0
|
||||
const NIGHT_START_HOUR: float = 20.0
|
||||
const TRANSITION_HALF_HOURS: float = 0.5
|
||||
const DAWN_START_HOUR: float = DAY_START_HOUR - TRANSITION_HALF_HOURS
|
||||
const DAWN_END_HOUR: float = DAY_START_HOUR + TRANSITION_HALF_HOURS
|
||||
const DUSK_START_HOUR: float = NIGHT_START_HOUR - TRANSITION_HALF_HOURS
|
||||
const DUSK_END_HOUR: float = NIGHT_START_HOUR + TRANSITION_HALF_HOURS
|
||||
const DEFAULT_START_HOUR: float = DAY_START_HOUR
|
||||
|
||||
var _time_hours: float = DEFAULT_START_HOUR
|
||||
var _running: bool = false
|
||||
var _phase: Phase = Phase.DAWN
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_process(false)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
advance_time(delta)
|
||||
|
||||
|
||||
func begin_session(start_hour: float = DEFAULT_START_HOUR) -> void:
|
||||
_running = true
|
||||
set_process(true)
|
||||
_set_time_hours(start_hour, true)
|
||||
|
||||
|
||||
func end_session() -> void:
|
||||
_running = false
|
||||
set_process(false)
|
||||
_set_time_hours(DEFAULT_START_HOUR, true)
|
||||
|
||||
|
||||
func advance_time(real_seconds: float) -> void:
|
||||
if not _running or real_seconds <= 0.0:
|
||||
return
|
||||
_set_time_hours(
|
||||
_time_hours + real_seconds * HOURS_PER_REAL_SECOND,
|
||||
false,
|
||||
)
|
||||
|
||||
|
||||
func synchronize_time(authoritative_time_hours: float) -> void:
|
||||
if not is_finite(authoritative_time_hours):
|
||||
return
|
||||
_set_time_hours(authoritative_time_hours, true)
|
||||
|
||||
|
||||
func get_time_hours() -> float:
|
||||
return _time_hours
|
||||
|
||||
|
||||
func get_phase() -> Phase:
|
||||
return _phase
|
||||
|
||||
|
||||
func is_night_period() -> bool:
|
||||
return _time_hours < DAY_START_HOUR or _time_hours >= NIGHT_START_HOUR
|
||||
|
||||
|
||||
func is_transition() -> bool:
|
||||
return _phase in [Phase.DAWN, Phase.DUSK]
|
||||
|
||||
|
||||
func get_clock_text() -> String:
|
||||
return format_clock_time(_time_hours)
|
||||
|
||||
|
||||
static func phase_for_hour(time_hours: float) -> Phase:
|
||||
var hour: float = _normalized_hour(time_hours)
|
||||
if hour >= DAWN_START_HOUR and hour < DAWN_END_HOUR:
|
||||
return Phase.DAWN
|
||||
if hour >= DAWN_END_HOUR and hour < DUSK_START_HOUR:
|
||||
return Phase.DAY
|
||||
if hour >= DUSK_START_HOUR and hour < DUSK_END_HOUR:
|
||||
return Phase.DUSK
|
||||
return Phase.NIGHT
|
||||
|
||||
|
||||
static func format_clock_time(time_hours: float) -> String:
|
||||
var hour: float = _normalized_hour(time_hours)
|
||||
var total_minutes: int = floori(hour * 60.0)
|
||||
var hour_24: int = floori(float(total_minutes) / 60.0)
|
||||
var minute: int = total_minutes % 60
|
||||
var hour_12: int = hour_24 % 12
|
||||
if hour_12 == 0:
|
||||
hour_12 = 12
|
||||
return "%d:%02d %s" % [
|
||||
hour_12,
|
||||
minute,
|
||||
"am" if hour_24 < 12 else "pm",
|
||||
]
|
||||
|
||||
|
||||
func _set_time_hours(time_hours: float, force_emit: bool) -> void:
|
||||
var normalized: float = _normalized_hour(time_hours)
|
||||
var next_phase: Phase = phase_for_hour(normalized)
|
||||
var phase_was_changed: bool = next_phase != _phase
|
||||
var time_was_changed: bool = not is_equal_approx(normalized, _time_hours)
|
||||
_time_hours = normalized
|
||||
_phase = next_phase
|
||||
if phase_was_changed:
|
||||
phase_changed.emit(_phase)
|
||||
if force_emit or time_was_changed or phase_was_changed:
|
||||
time_changed.emit(_time_hours, _phase)
|
||||
|
||||
|
||||
static func _normalized_hour(time_hours: float) -> float:
|
||||
return fposmod(time_hours, HOURS_PER_DAY)
|
||||
1
world/world_time_service.gd.uid
Normal file
1
world/world_time_service.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://5lceyulaglpf
|
||||
389
world/world_time_visual_controller.gd
Normal file
389
world/world_time_visual_controller.gd
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
class_name WorldTimeVisualController
|
||||
extends Node
|
||||
|
||||
const UPDATE_INTERVAL_SECONDS: float = 0.1
|
||||
const SUN_YAW_DEGREES: float = -32.0
|
||||
const WEATHER_TRANSITION_SECONDS: float = 10.0
|
||||
const RAIN_EMITTER_OFFSET := Vector3(0.0, 7.0, 0.0)
|
||||
|
||||
const DAY_SKY_TOP := Color(0.204, 0.498, 0.643)
|
||||
const DAY_SKY_HORIZON := Color(0.663, 0.843, 0.847)
|
||||
const DAY_GROUND_BOTTOM := Color(0.157, 0.361, 0.439)
|
||||
const DAY_GROUND_HORIZON := Color(0.549, 0.749, 0.765)
|
||||
const DAY_AMBIENT := Color(0.82, 0.90, 0.92)
|
||||
const DAY_FOG := Color(0.549, 0.749, 0.765)
|
||||
const DAY_SUN := Color(0.88, 0.94, 0.96)
|
||||
|
||||
const NIGHT_SKY_TOP := Color(0.026, 0.050, 0.105)
|
||||
const NIGHT_SKY_HORIZON := Color(0.10, 0.17, 0.28)
|
||||
const NIGHT_GROUND_BOTTOM := Color(0.020, 0.040, 0.080)
|
||||
const NIGHT_GROUND_HORIZON := Color(0.075, 0.135, 0.22)
|
||||
const NIGHT_AMBIENT := Color(0.28, 0.35, 0.48)
|
||||
const NIGHT_FOG := Color(0.13, 0.21, 0.32)
|
||||
const NIGHT_SUN := Color(0.35, 0.44, 0.62)
|
||||
|
||||
const WARM_SKY_TOP := Color(0.31, 0.30, 0.42)
|
||||
const WARM_SKY_HORIZON := Color(0.96, 0.48, 0.22)
|
||||
const WARM_GROUND_BOTTOM := Color(0.20, 0.14, 0.16)
|
||||
const WARM_GROUND_HORIZON := Color(0.82, 0.34, 0.18)
|
||||
const WARM_AMBIENT := Color(0.92, 0.58, 0.40)
|
||||
const WARM_FOG := Color(0.74, 0.39, 0.27)
|
||||
const WARM_SUN := Color(1.0, 0.58, 0.30)
|
||||
|
||||
const CLOUDY_TINT := Color(0.53, 0.61, 0.66)
|
||||
const RAINY_TINT := Color(0.31, 0.42, 0.50)
|
||||
const FOGGY_TINT := Color(0.62, 0.70, 0.72)
|
||||
|
||||
var _time_service: WorldTimeService
|
||||
var _weather_service: WorldWeatherService
|
||||
var _world_environment: WorldEnvironment
|
||||
var _sun: DirectionalLight3D
|
||||
var _rain_target: Node3D
|
||||
var _environment: Environment
|
||||
var _sky_material: ProceduralSkyMaterial
|
||||
var _rain: GPUParticles3D
|
||||
var _elapsed: float = 0.0
|
||||
var _weather_from: WorldWeatherService.Weather = (
|
||||
WorldWeatherService.Weather.SUNNY
|
||||
)
|
||||
var _weather_to: WorldWeatherService.Weather = (
|
||||
WorldWeatherService.Weather.SUNNY
|
||||
)
|
||||
var _weather_transition: float = 1.0
|
||||
|
||||
|
||||
func setup(
|
||||
time_service: WorldTimeService,
|
||||
world_environment: WorldEnvironment,
|
||||
sun: DirectionalLight3D,
|
||||
weather_service: WorldWeatherService = null,
|
||||
rain_target: Node3D = null,
|
||||
) -> void:
|
||||
_time_service = time_service
|
||||
_weather_service = weather_service
|
||||
_world_environment = world_environment
|
||||
_sun = sun
|
||||
_rain_target = rain_target
|
||||
if not _prepare_runtime_environment():
|
||||
set_process(false)
|
||||
return
|
||||
_prepare_rain()
|
||||
if _weather_service != null:
|
||||
_weather_from = _weather_service.get_weather()
|
||||
_weather_to = _weather_from
|
||||
_weather_transition = 1.0
|
||||
if not _weather_service.weather_changed.is_connected(
|
||||
_on_weather_changed
|
||||
):
|
||||
_weather_service.weather_changed.connect(_on_weather_changed)
|
||||
set_process(true)
|
||||
_apply_time(_time_service.get_time_hours())
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if _time_service == null:
|
||||
return
|
||||
_update_rain_position()
|
||||
if _weather_transition < 1.0:
|
||||
_weather_transition = minf(
|
||||
_weather_transition + delta / WEATHER_TRANSITION_SECONDS,
|
||||
1.0,
|
||||
)
|
||||
_elapsed += delta
|
||||
if _elapsed < UPDATE_INTERVAL_SECONDS:
|
||||
return
|
||||
_elapsed = 0.0
|
||||
_apply_time(_time_service.get_time_hours())
|
||||
|
||||
|
||||
func apply_time_immediately(time_hours: float) -> void:
|
||||
_apply_time(time_hours)
|
||||
|
||||
|
||||
func apply_weather_immediately(
|
||||
weather: WorldWeatherService.Weather,
|
||||
) -> void:
|
||||
_weather_from = weather
|
||||
_weather_to = weather
|
||||
_weather_transition = 1.0
|
||||
if _time_service != null:
|
||||
_apply_time(_time_service.get_time_hours())
|
||||
|
||||
|
||||
func _prepare_runtime_environment() -> bool:
|
||||
if (
|
||||
_time_service == null
|
||||
or _world_environment == null
|
||||
or _world_environment.environment == null
|
||||
or _sun == null
|
||||
):
|
||||
push_error("World time visuals require an environment and sun.")
|
||||
return false
|
||||
_environment = _world_environment.environment.duplicate(true) as Environment
|
||||
if _environment == null or _environment.sky == null:
|
||||
push_error("World time visuals could not duplicate the world environment.")
|
||||
return false
|
||||
var runtime_sky: Sky = _environment.sky.duplicate(true) as Sky
|
||||
if runtime_sky == null or runtime_sky.sky_material == null:
|
||||
push_error("World time visuals require a procedural sky material.")
|
||||
return false
|
||||
_sky_material = (
|
||||
runtime_sky.sky_material.duplicate(true) as ProceduralSkyMaterial
|
||||
)
|
||||
if _sky_material == null:
|
||||
push_error("World time visuals require a ProceduralSkyMaterial.")
|
||||
return false
|
||||
runtime_sky.sky_material = _sky_material
|
||||
_environment.sky = runtime_sky
|
||||
_world_environment.environment = _environment
|
||||
return true
|
||||
|
||||
|
||||
func _prepare_rain() -> void:
|
||||
_rain = GPUParticles3D.new()
|
||||
_rain.name = "LocalRain"
|
||||
_rain.amount = 480
|
||||
_rain.amount_ratio = 0.0
|
||||
_rain.lifetime = 1.25
|
||||
_rain.fixed_fps = 30
|
||||
_rain.local_coords = false
|
||||
_rain.visibility_aabb = AABB(
|
||||
Vector3(-7.0, -9.0, -7.0), Vector3(14.0, 12.0, 14.0)
|
||||
)
|
||||
var process_material := ParticleProcessMaterial.new()
|
||||
process_material.emission_shape = (
|
||||
ParticleProcessMaterial.EMISSION_SHAPE_BOX
|
||||
)
|
||||
process_material.emission_box_extents = Vector3(6.5, 1.0, 6.5)
|
||||
process_material.direction = Vector3.DOWN
|
||||
process_material.spread = 5.0
|
||||
process_material.initial_velocity_min = 11.0
|
||||
process_material.initial_velocity_max = 15.0
|
||||
process_material.gravity = Vector3(0.0, -2.0, 0.0)
|
||||
_rain.process_material = process_material
|
||||
var drop_mesh := BoxMesh.new()
|
||||
drop_mesh.size = Vector3(0.018, 0.42, 0.018)
|
||||
var drop_material := StandardMaterial3D.new()
|
||||
drop_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
||||
drop_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
drop_material.albedo_color = Color(0.48, 0.78, 0.90, 0.72)
|
||||
drop_mesh.material = drop_material
|
||||
_rain.draw_pass_1 = drop_mesh
|
||||
add_child(_rain)
|
||||
_update_rain_position()
|
||||
|
||||
|
||||
func _apply_time(time_hours: float) -> void:
|
||||
if _environment == null or _sky_material == null or _sun == null:
|
||||
return
|
||||
var hour: float = fposmod(time_hours, WorldTimeService.HOURS_PER_DAY)
|
||||
var daylight: float = _daylight_amount(hour)
|
||||
var warmth: float = _transition_warmth(hour)
|
||||
var sky_top: Color = _blended_color(
|
||||
NIGHT_SKY_TOP, DAY_SKY_TOP, WARM_SKY_TOP, daylight, warmth
|
||||
)
|
||||
var sky_horizon: Color = _blended_color(
|
||||
NIGHT_SKY_HORIZON,
|
||||
DAY_SKY_HORIZON,
|
||||
WARM_SKY_HORIZON,
|
||||
daylight,
|
||||
warmth,
|
||||
)
|
||||
var ground_bottom: Color = _blended_color(
|
||||
NIGHT_GROUND_BOTTOM,
|
||||
DAY_GROUND_BOTTOM,
|
||||
WARM_GROUND_BOTTOM,
|
||||
daylight,
|
||||
warmth,
|
||||
)
|
||||
var ground_horizon: Color = _blended_color(
|
||||
NIGHT_GROUND_HORIZON,
|
||||
DAY_GROUND_HORIZON,
|
||||
WARM_GROUND_HORIZON,
|
||||
daylight,
|
||||
warmth,
|
||||
)
|
||||
var ambient: Color = _blended_color(
|
||||
NIGHT_AMBIENT, DAY_AMBIENT, WARM_AMBIENT, daylight, warmth
|
||||
)
|
||||
var fog_color: Color = _blended_color(
|
||||
NIGHT_FOG, DAY_FOG, WARM_FOG, daylight, warmth
|
||||
)
|
||||
var weather_tint: Color = _weather_color()
|
||||
var tint_strength: float = _weather_value(
|
||||
0.0, 0.30, 0.48, 0.62
|
||||
)
|
||||
_sky_material.sky_top_color = sky_top.lerp(weather_tint, tint_strength)
|
||||
_sky_material.sky_horizon_color = sky_horizon.lerp(
|
||||
weather_tint, tint_strength
|
||||
)
|
||||
_sky_material.ground_bottom_color = ground_bottom.lerp(
|
||||
weather_tint, tint_strength * 0.62
|
||||
)
|
||||
_sky_material.ground_horizon_color = ground_horizon.lerp(
|
||||
weather_tint, tint_strength * 0.76
|
||||
)
|
||||
_environment.background_energy_multiplier = (
|
||||
lerpf(0.52, 0.85, daylight)
|
||||
* _weather_value(1.0, 0.82, 0.66, 0.74)
|
||||
)
|
||||
_environment.ambient_light_color = ambient.lerp(
|
||||
weather_tint, tint_strength * 0.45
|
||||
)
|
||||
_environment.ambient_light_energy = (
|
||||
lerpf(0.66, 1.08, daylight)
|
||||
* _weather_value(1.0, 0.88, 0.76, 0.82)
|
||||
)
|
||||
_environment.fog_light_color = fog_color.lerp(
|
||||
weather_tint, tint_strength * 0.82
|
||||
)
|
||||
_environment.fog_light_energy = (
|
||||
lerpf(0.56, 0.82, daylight)
|
||||
* _weather_value(1.0, 0.92, 0.82, 1.05)
|
||||
)
|
||||
_environment.fog_aerial_perspective = _weather_value(
|
||||
0.35, 0.48, 0.62, 0.92
|
||||
)
|
||||
_environment.fog_sky_affect = _weather_value(
|
||||
0.35, 0.48, 0.68, 0.94
|
||||
)
|
||||
_environment.fog_depth_curve = _weather_value(
|
||||
1.6, 1.5, 1.4, 1.18
|
||||
)
|
||||
_environment.fog_depth_begin = _weather_value(
|
||||
42.0, 32.0, 20.0, 4.0
|
||||
)
|
||||
_environment.fog_depth_end = _weather_value(
|
||||
170.0, 140.0, 95.0, 42.0
|
||||
)
|
||||
_environment.adjustment_brightness = (
|
||||
lerpf(0.93, 0.98, daylight)
|
||||
* _weather_value(1.0, 0.97, 0.92, 0.96)
|
||||
)
|
||||
_environment.adjustment_saturation = (
|
||||
lerpf(0.82, 0.91, daylight)
|
||||
* _weather_value(1.0, 0.88, 0.78, 0.70)
|
||||
)
|
||||
_sun.rotation_degrees = Vector3(
|
||||
-360.0 * fposmod(hour - WorldTimeService.DAY_START_HOUR, 24.0) / 24.0,
|
||||
SUN_YAW_DEGREES,
|
||||
0.0,
|
||||
)
|
||||
_sun.light_color = _blended_color(
|
||||
NIGHT_SUN, DAY_SUN, WARM_SUN, daylight, warmth
|
||||
)
|
||||
_sun.light_energy = (
|
||||
lerpf(0.0, 0.10, daylight)
|
||||
* _weather_value(1.0, 0.55, 0.25, 0.35)
|
||||
)
|
||||
_update_rain_amount()
|
||||
|
||||
|
||||
func _on_weather_changed(
|
||||
weather: WorldWeatherService.Weather,
|
||||
_seconds_remaining: float,
|
||||
) -> void:
|
||||
if weather == _weather_to:
|
||||
return
|
||||
_weather_from = _weather_to
|
||||
_weather_to = weather
|
||||
_weather_transition = 0.0
|
||||
|
||||
|
||||
func _weather_value(
|
||||
sunny: float,
|
||||
cloudy: float,
|
||||
rainy: float,
|
||||
foggy: float,
|
||||
) -> float:
|
||||
return lerpf(
|
||||
_weather_state_value(_weather_from, sunny, cloudy, rainy, foggy),
|
||||
_weather_state_value(_weather_to, sunny, cloudy, rainy, foggy),
|
||||
_weather_transition,
|
||||
)
|
||||
|
||||
|
||||
func _weather_state_value(
|
||||
weather: WorldWeatherService.Weather,
|
||||
sunny: float,
|
||||
cloudy: float,
|
||||
rainy: float,
|
||||
foggy: float,
|
||||
) -> float:
|
||||
match weather:
|
||||
WorldWeatherService.Weather.CLOUDY:
|
||||
return cloudy
|
||||
WorldWeatherService.Weather.RAINY:
|
||||
return rainy
|
||||
WorldWeatherService.Weather.FOGGY:
|
||||
return foggy
|
||||
return sunny
|
||||
|
||||
|
||||
func _weather_color() -> Color:
|
||||
return _weather_state_color(_weather_from).lerp(
|
||||
_weather_state_color(_weather_to), _weather_transition
|
||||
)
|
||||
|
||||
|
||||
func _weather_state_color(
|
||||
weather: WorldWeatherService.Weather,
|
||||
) -> Color:
|
||||
match weather:
|
||||
WorldWeatherService.Weather.CLOUDY:
|
||||
return CLOUDY_TINT
|
||||
WorldWeatherService.Weather.RAINY:
|
||||
return RAINY_TINT
|
||||
WorldWeatherService.Weather.FOGGY:
|
||||
return FOGGY_TINT
|
||||
return Color.WHITE
|
||||
|
||||
|
||||
func _update_rain_amount() -> void:
|
||||
if _rain == null:
|
||||
return
|
||||
var rain_amount: float = _weather_value(0.0, 0.0, 1.0, 0.0)
|
||||
_rain.amount_ratio = rain_amount
|
||||
_rain.emitting = rain_amount > 0.01
|
||||
|
||||
|
||||
func _update_rain_position() -> void:
|
||||
if _rain == null or _rain_target == null:
|
||||
return
|
||||
_rain.global_position = _rain_target.global_position + RAIN_EMITTER_OFFSET
|
||||
|
||||
|
||||
func _daylight_amount(hour: float) -> float:
|
||||
if hour >= WorldTimeService.DAWN_START_HOUR and hour < WorldTimeService.DAWN_END_HOUR:
|
||||
return smoothstep(
|
||||
WorldTimeService.DAWN_START_HOUR,
|
||||
WorldTimeService.DAWN_END_HOUR,
|
||||
hour,
|
||||
)
|
||||
if hour >= WorldTimeService.DAWN_END_HOUR and hour < WorldTimeService.DUSK_START_HOUR:
|
||||
return 1.0
|
||||
if hour >= WorldTimeService.DUSK_START_HOUR and hour < WorldTimeService.DUSK_END_HOUR:
|
||||
return 1.0 - smoothstep(
|
||||
WorldTimeService.DUSK_START_HOUR,
|
||||
WorldTimeService.DUSK_END_HOUR,
|
||||
hour,
|
||||
)
|
||||
return 0.0
|
||||
|
||||
|
||||
func _transition_warmth(hour: float) -> float:
|
||||
if hour >= WorldTimeService.DAWN_START_HOUR and hour < WorldTimeService.DAWN_END_HOUR:
|
||||
return 1.0 - absf(hour - WorldTimeService.DAY_START_HOUR) / WorldTimeService.TRANSITION_HALF_HOURS
|
||||
if hour >= WorldTimeService.DUSK_START_HOUR and hour < WorldTimeService.DUSK_END_HOUR:
|
||||
return 1.0 - absf(hour - WorldTimeService.NIGHT_START_HOUR) / WorldTimeService.TRANSITION_HALF_HOURS
|
||||
return 0.0
|
||||
|
||||
|
||||
func _blended_color(
|
||||
night_color: Color,
|
||||
day_color: Color,
|
||||
warm_color: Color,
|
||||
daylight: float,
|
||||
warmth: float,
|
||||
) -> Color:
|
||||
return night_color.lerp(day_color, daylight).lerp(warm_color, warmth)
|
||||
1
world/world_time_visual_controller.gd.uid
Normal file
1
world/world_time_visual_controller.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://svirs8vbypad
|
||||
152
world/world_weather_service.gd
Normal file
152
world/world_weather_service.gd
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
class_name WorldWeatherService
|
||||
extends Node
|
||||
|
||||
signal weather_changed(weather: Weather, seconds_remaining: float)
|
||||
|
||||
enum Weather {
|
||||
SUNNY,
|
||||
CLOUDY,
|
||||
RAINY,
|
||||
FOGGY,
|
||||
}
|
||||
|
||||
const DEFAULT_WEATHER: Weather = Weather.SUNNY
|
||||
const SUNNY_DURATION_RANGE := Vector2(480.0, 900.0)
|
||||
const CLOUDY_DURATION_RANGE := Vector2(300.0, 720.0)
|
||||
const RAINY_DURATION_RANGE := Vector2(300.0, 600.0)
|
||||
const FOGGY_DURATION_RANGE := Vector2(300.0, 600.0)
|
||||
|
||||
var _weather: Weather = DEFAULT_WEATHER
|
||||
var _seconds_remaining: float = SUNNY_DURATION_RANGE.x
|
||||
var _running_authority: bool = false
|
||||
var _rng := RandomNumberGenerator.new()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
set_process(false)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
advance_weather(delta)
|
||||
|
||||
|
||||
func begin_authoritative_session(seed_value: int) -> void:
|
||||
_rng.seed = seed_value
|
||||
_running_authority = true
|
||||
set_process(true)
|
||||
_set_weather(DEFAULT_WEATHER, _roll_duration(DEFAULT_WEATHER), true)
|
||||
|
||||
|
||||
func begin_remote_session() -> void:
|
||||
_running_authority = false
|
||||
set_process(false)
|
||||
_set_weather(DEFAULT_WEATHER, SUNNY_DURATION_RANGE.x, true)
|
||||
|
||||
|
||||
func end_session() -> void:
|
||||
_running_authority = false
|
||||
set_process(false)
|
||||
_set_weather(DEFAULT_WEATHER, SUNNY_DURATION_RANGE.x, true)
|
||||
|
||||
|
||||
func advance_weather(real_seconds: float) -> void:
|
||||
if not _running_authority or real_seconds <= 0.0:
|
||||
return
|
||||
_seconds_remaining -= real_seconds
|
||||
while _seconds_remaining <= 0.0:
|
||||
var overrun: float = -_seconds_remaining
|
||||
var next_weather: Weather = _choose_next_weather()
|
||||
_set_weather(next_weather, _roll_duration(next_weather), true)
|
||||
_seconds_remaining -= overrun
|
||||
|
||||
|
||||
func apply_authoritative_snapshot(
|
||||
weather: Weather,
|
||||
seconds_remaining: float,
|
||||
) -> void:
|
||||
if not is_valid_weather(int(weather)) or not is_finite(seconds_remaining):
|
||||
return
|
||||
_set_weather(weather, maxf(seconds_remaining, 0.0), false)
|
||||
|
||||
|
||||
func get_weather() -> Weather:
|
||||
return _weather
|
||||
|
||||
|
||||
func get_seconds_remaining() -> float:
|
||||
return _seconds_remaining
|
||||
|
||||
|
||||
func is_raining() -> bool:
|
||||
return _weather == Weather.RAINY
|
||||
|
||||
|
||||
func is_foggy() -> bool:
|
||||
return _weather == Weather.FOGGY
|
||||
|
||||
|
||||
func get_weather_name() -> String:
|
||||
return weather_name(_weather)
|
||||
|
||||
|
||||
static func is_valid_weather(value: int) -> bool:
|
||||
return value >= Weather.SUNNY and value <= Weather.FOGGY
|
||||
|
||||
|
||||
static func weather_name(weather: Weather) -> String:
|
||||
match weather:
|
||||
Weather.SUNNY:
|
||||
return "sunny"
|
||||
Weather.CLOUDY:
|
||||
return "cloudy"
|
||||
Weather.RAINY:
|
||||
return "rainy"
|
||||
Weather.FOGGY:
|
||||
return "foggy"
|
||||
return "unknown"
|
||||
|
||||
|
||||
func _set_weather(
|
||||
weather: Weather,
|
||||
seconds_remaining: float,
|
||||
force_emit: bool,
|
||||
) -> void:
|
||||
var changed: bool = weather != _weather
|
||||
_weather = weather
|
||||
_seconds_remaining = maxf(seconds_remaining, 0.0)
|
||||
if force_emit or changed:
|
||||
weather_changed.emit(_weather, _seconds_remaining)
|
||||
|
||||
|
||||
func _choose_next_weather() -> Weather:
|
||||
match _weather:
|
||||
Weather.SUNNY:
|
||||
return Weather.CLOUDY
|
||||
Weather.RAINY, Weather.FOGGY:
|
||||
return Weather.CLOUDY
|
||||
Weather.CLOUDY:
|
||||
var roll: float = _rng.randf()
|
||||
if roll < 0.45:
|
||||
return Weather.SUNNY
|
||||
if roll < 0.80:
|
||||
return Weather.RAINY
|
||||
return Weather.FOGGY
|
||||
return Weather.SUNNY
|
||||
|
||||
|
||||
func _roll_duration(weather: Weather) -> float:
|
||||
var duration_range: Vector2 = _duration_range(weather)
|
||||
return _rng.randf_range(duration_range.x, duration_range.y)
|
||||
|
||||
|
||||
func _duration_range(weather: Weather) -> Vector2:
|
||||
match weather:
|
||||
Weather.SUNNY:
|
||||
return SUNNY_DURATION_RANGE
|
||||
Weather.CLOUDY:
|
||||
return CLOUDY_DURATION_RANGE
|
||||
Weather.RAINY:
|
||||
return RAINY_DURATION_RANGE
|
||||
Weather.FOGGY:
|
||||
return FOGGY_DURATION_RANGE
|
||||
return SUNNY_DURATION_RANGE
|
||||
1
world/world_weather_service.gd.uid
Normal file
1
world/world_weather_service.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://v4c54isq6w74
|
||||
Loading…
Add table
Add a link
Reference in a new issue