Implement weather system and developer console features. Added functionality for setting weather conditions, temperature, and wind speed through a new developer console. Enhanced the vault scene to display current weather, temperature, and wind information, and updated game clock to allow time manipulation. Improved cloud rendering and introduced a new climate management system for seasonal temperature adjustments.
This commit is contained in:
+100
-33
@@ -1,25 +1,50 @@
|
||||
class_name WeatherSystem
|
||||
extends Node
|
||||
## Погода: текущий тип, плавные переходы и смена по игровому времени.
|
||||
## Погода: тип, температура, ветер и плавные переходы между состояниями.
|
||||
##
|
||||
## Данные и правила, без отрисовки. Вид (surface_view.gd, sky_view.gd) читает
|
||||
## отсюда `clouds`, `precipitation`, `fog` и `wind` и рисует по ним.
|
||||
## отсюда `clouds`, `precipitation`, `fog` и `wind`, HUD — `temperature`
|
||||
## и `wind_speed`. Норму по сезону даёт climate.gd.
|
||||
##
|
||||
## Такт берётся у GameClock, поэтому погода стоит вместе с паузой и ускоряется
|
||||
## вместе со скоростью игры — своего таймера здесь нет намеренно.
|
||||
|
||||
enum Kind { CLEAR, CLOUDY, OVERCAST, RAIN, STORM, FOG, SNOW }
|
||||
|
||||
## Целевые параметры каждого типа. `clouds` — плотность облаков, `precipitation`
|
||||
## — сила осадков, `fog` — дымка у горизонта, `wind` — скорость сноса.
|
||||
## Целевые параметры картинки для каждого типа. Температура и ветер сюда не
|
||||
## входят: они зависят ещё и от сезона, их считает Climate.
|
||||
##
|
||||
## `key` — латинское имя для консоли: набирать русские названия с латинской
|
||||
## раскладки неудобно, а переключаться посреди отладки — тем более.
|
||||
const KINDS := {
|
||||
Kind.CLEAR: {"label": "Ясно", "clouds": 0.12, "precipitation": 0.0, "fog": 0.0, "wind": 0.3},
|
||||
Kind.CLOUDY: {"label": "Облачно", "clouds": 0.45, "precipitation": 0.0, "fog": 0.0, "wind": 0.5},
|
||||
Kind.OVERCAST: {"label": "Пасмурно", "clouds": 0.85, "precipitation": 0.0, "fog": 0.06, "wind": 0.4},
|
||||
Kind.RAIN: {"label": "Дождь", "clouds": 0.95, "precipitation": 0.55, "fog": 0.14, "wind": 0.7},
|
||||
Kind.STORM: {"label": "Ливень", "clouds": 1.0, "precipitation": 1.0, "fog": 0.22, "wind": 1.0},
|
||||
Kind.FOG: {"label": "Туман", "clouds": 0.3, "precipitation": 0.0, "fog": 0.62, "wind": 0.12},
|
||||
Kind.SNOW: {"label": "Снег", "clouds": 0.9, "precipitation": 0.5, "fog": 0.24, "wind": 0.35},
|
||||
Kind.CLEAR: {
|
||||
"label": "Ясно", "key": "clear",
|
||||
"clouds": 0.12, "precipitation": 0.0, "fog": 0.0,
|
||||
},
|
||||
Kind.CLOUDY: {
|
||||
"label": "Облачно", "key": "cloudy",
|
||||
"clouds": 0.45, "precipitation": 0.0, "fog": 0.0,
|
||||
},
|
||||
Kind.OVERCAST: {
|
||||
"label": "Пасмурно", "key": "overcast",
|
||||
"clouds": 0.85, "precipitation": 0.0, "fog": 0.06,
|
||||
},
|
||||
Kind.RAIN: {
|
||||
"label": "Дождь", "key": "rain",
|
||||
"clouds": 0.95, "precipitation": 0.55, "fog": 0.14,
|
||||
},
|
||||
Kind.STORM: {
|
||||
"label": "Ливень", "key": "storm",
|
||||
"clouds": 1.0, "precipitation": 1.0, "fog": 0.22,
|
||||
},
|
||||
Kind.FOG: {
|
||||
"label": "Туман", "key": "fog",
|
||||
"clouds": 0.3, "precipitation": 0.0, "fog": 0.62,
|
||||
},
|
||||
Kind.SNOW: {
|
||||
"label": "Снег", "key": "snow",
|
||||
"clouds": 0.9, "precipitation": 0.5, "fog": 0.24,
|
||||
},
|
||||
}
|
||||
|
||||
## Куда погода может перейти и с каким относительным весом. Матрица, а не
|
||||
@@ -27,20 +52,19 @@ const KINDS := {
|
||||
const TRANSITIONS := {
|
||||
Kind.CLEAR: {Kind.CLEAR: 4, Kind.CLOUDY: 5, Kind.FOG: 1},
|
||||
Kind.CLOUDY: {Kind.CLEAR: 4, Kind.CLOUDY: 2, Kind.OVERCAST: 4, Kind.FOG: 1},
|
||||
Kind.OVERCAST: {Kind.CLOUDY: 4, Kind.OVERCAST: 2, Kind.RAIN: 3, Kind.SNOW: 2},
|
||||
Kind.OVERCAST: {Kind.CLOUDY: 4, Kind.OVERCAST: 2, Kind.RAIN: 4, Kind.SNOW: 1},
|
||||
Kind.RAIN: {Kind.RAIN: 2, Kind.STORM: 2, Kind.OVERCAST: 5},
|
||||
Kind.STORM: {Kind.RAIN: 5, Kind.OVERCAST: 2},
|
||||
Kind.FOG: {Kind.FOG: 2, Kind.CLEAR: 4, Kind.CLOUDY: 3},
|
||||
Kind.SNOW: {Kind.SNOW: 3, Kind.OVERCAST: 4, Kind.CLOUDY: 2},
|
||||
}
|
||||
|
||||
## Месяцы, когда осадки выпадают снегом.
|
||||
const WINTER_MONTHS: Array[int] = [11, 12, 1, 2]
|
||||
|
||||
const MIN_DURATION := 120
|
||||
const MAX_DURATION := 420
|
||||
## За сколько игровых минут параметры доходят до целевых.
|
||||
const BLEND_MINUTES := 45.0
|
||||
## Насколько воздушная масса может отклонить температуру от нормы, °C.
|
||||
const DRIFT_RANGE := 3.0
|
||||
|
||||
## Сменился тип погоды — для HUD и звука.
|
||||
signal changed(kind: int)
|
||||
@@ -49,9 +73,14 @@ var kind := Kind.CLEAR
|
||||
var clouds := 0.12
|
||||
var precipitation := 0.0
|
||||
var fog := 0.0
|
||||
var wind := 0.3
|
||||
## Ветер 0..1 для картинки; в метрах в секунду — wind_speed.
|
||||
var wind := 0.15
|
||||
var wind_speed := 2.5
|
||||
var temperature := 8.0
|
||||
|
||||
var _minutes_left := MIN_DURATION
|
||||
## Отклонение текущей воздушной массы от климатической нормы, °C.
|
||||
var _temperature_drift := 0.0
|
||||
var _rng := RandomNumberGenerator.new()
|
||||
var _clock: GameClock
|
||||
|
||||
@@ -65,6 +94,7 @@ func _ready() -> void:
|
||||
func attach_clock(clock: GameClock) -> void:
|
||||
_clock = clock
|
||||
_clock.minute_passed.connect(_on_minute_passed)
|
||||
_apply_climate(1.0)
|
||||
|
||||
|
||||
func label() -> String:
|
||||
@@ -76,6 +106,29 @@ func is_snowing() -> bool:
|
||||
return kind == Kind.SNOW
|
||||
|
||||
|
||||
# --- Ручное управление (консоль разработчика) -------------------------------
|
||||
|
||||
## Поставить конкретную погоду немедленно, минуя матрицу переходов.
|
||||
func force_kind(value: int, hold_minutes := MAX_DURATION) -> void:
|
||||
kind = clampi(value, 0, KINDS.size() - 1)
|
||||
_minutes_left = hold_minutes
|
||||
changed.emit(kind)
|
||||
|
||||
|
||||
## Сдвинуть воздушную массу так, чтобы сейчас было примерно `celsius`.
|
||||
## Значение держится до следующей смены погоды, а не жёстко фиксируется.
|
||||
func nudge_temperature(celsius: float) -> void:
|
||||
_temperature_drift += celsius - temperature
|
||||
temperature = celsius
|
||||
|
||||
|
||||
func set_wind_speed(speed: float) -> void:
|
||||
wind_speed = maxf(speed, 0.0)
|
||||
wind = clampf(wind_speed / Climate.WIND_REFERENCE, 0.0, 1.0)
|
||||
|
||||
|
||||
# --- Сохранение -------------------------------------------------------------
|
||||
|
||||
func save_data() -> Dictionary:
|
||||
return {
|
||||
"kind": int(kind),
|
||||
@@ -83,7 +136,9 @@ func save_data() -> Dictionary:
|
||||
"clouds": clouds,
|
||||
"precipitation": precipitation,
|
||||
"fog": fog,
|
||||
"wind": wind,
|
||||
"wind_speed": wind_speed,
|
||||
"temperature": temperature,
|
||||
"drift": _temperature_drift,
|
||||
}
|
||||
|
||||
|
||||
@@ -93,14 +148,17 @@ func load_data(data: Dictionary) -> void:
|
||||
clouds = float(data.get("clouds", 0.12))
|
||||
precipitation = float(data.get("precipitation", 0.0))
|
||||
fog = float(data.get("fog", 0.0))
|
||||
wind = float(data.get("wind", 0.3))
|
||||
temperature = float(data.get("temperature", 8.0))
|
||||
_temperature_drift = float(data.get("drift", 0.0))
|
||||
set_wind_speed(float(data.get("wind_speed", 2.5)))
|
||||
changed.emit(kind)
|
||||
|
||||
|
||||
# --- Внутреннее -------------------------------------------------------------
|
||||
|
||||
func _on_minute_passed(_total_minutes: int) -> void:
|
||||
_blend_toward_target()
|
||||
_apply_target(1.0 / BLEND_MINUTES)
|
||||
_apply_climate(1.0 / BLEND_MINUTES)
|
||||
|
||||
_minutes_left -= 1
|
||||
if _minutes_left > 0:
|
||||
@@ -108,20 +166,29 @@ func _on_minute_passed(_total_minutes: int) -> void:
|
||||
|
||||
kind = _roll_next()
|
||||
_minutes_left = _rng.randi_range(MIN_DURATION, MAX_DURATION)
|
||||
# Новая погода — новая воздушная масса.
|
||||
_temperature_drift = _rng.randf_range(-DRIFT_RANGE, DRIFT_RANGE)
|
||||
changed.emit(kind)
|
||||
|
||||
|
||||
## Параметры не прыгают вместе с типом: они доходят до цели за BLEND_MINUTES.
|
||||
func _blend_toward_target() -> void:
|
||||
_apply_target(1.0 / BLEND_MINUTES)
|
||||
|
||||
|
||||
## Параметры картинки не прыгают вместе с типом: они доходят до цели плавно.
|
||||
func _apply_target(weight: float) -> void:
|
||||
var target: Dictionary = KINDS[kind]
|
||||
clouds = lerpf(clouds, float(target["clouds"]), weight)
|
||||
precipitation = lerpf(precipitation, float(target["precipitation"]), weight)
|
||||
fog = lerpf(fog, float(target["fog"]), weight)
|
||||
wind = lerpf(wind, float(target["wind"]), weight)
|
||||
|
||||
|
||||
## Температура и ветер идут к сезонной норме с поправкой на тип погоды.
|
||||
func _apply_climate(weight: float) -> void:
|
||||
if _clock == null:
|
||||
return
|
||||
var moment := _clock.now()
|
||||
var target := Climate.base_temperature(int(moment["month"]), SkyCycle.hour_of(moment))
|
||||
target += Climate.weather_offset(kind) + _temperature_drift
|
||||
temperature = lerpf(temperature, target, weight)
|
||||
|
||||
set_wind_speed(lerpf(wind_speed, Climate.base_wind(kind), weight))
|
||||
|
||||
|
||||
func _roll_next() -> int:
|
||||
@@ -134,15 +201,15 @@ func _roll_next() -> int:
|
||||
for candidate: int in options:
|
||||
pick -= int(options[candidate])
|
||||
if pick <= 0:
|
||||
return _seasonal(candidate)
|
||||
return _by_temperature(candidate)
|
||||
return kind
|
||||
|
||||
|
||||
## Снег уместен только зимой; в остальное время он превращается в дождь.
|
||||
func _seasonal(candidate: int) -> int:
|
||||
if candidate != Kind.SNOW:
|
||||
return candidate
|
||||
if _clock == null:
|
||||
## Форму осадков решает градусник, а не календарь: у нуля дождь становится
|
||||
## снегом и наоборот. Так это можно проверить, подкрутив температуру.
|
||||
func _by_temperature(candidate: int) -> int:
|
||||
if candidate == Kind.SNOW and temperature > Climate.SNOW_THRESHOLD + 1.5:
|
||||
return Kind.RAIN
|
||||
var month := int(_clock.now()["month"])
|
||||
return Kind.SNOW if WINTER_MONTHS.has(month) else Kind.RAIN
|
||||
if candidate == Kind.RAIN and temperature < Climate.SNOW_THRESHOLD:
|
||||
return Kind.SNOW
|
||||
return candidate
|
||||
|
||||
Reference in New Issue
Block a user