class_name GameClock extends Node ## Игровые дата и время. ## ## Отдельная сущность, а не поле сцены: ход времени понадобится производству, ## жителям и событиям, и сохранение обязано его восстанавливать. ## ## Пауза здесь своя, не движковая. `get_tree().paused` останавливает вообще всё ## и занята меню паузы; здесь же нужно останавливать только ход партии, оставляя ## интерфейс живым. При этом узел обычный, PAUSABLE, поэтому меню паузы ## замораживает время само собой. ## Старт партии: 1 апреля 2012 года, шесть утра. const START := {"year": 2012, "month": 4, "day": 1, "hour": 6, "minute": 0, "second": 0} ## Сколько игровых минут проходит за секунду реального времени на скорости x1. ## Сутки при этом занимают 8 минут реального времени. Множители x2…x4 ## умножают именно это значение, так что менять темп достаточно здесь. const MINUTES_PER_SECOND := 3.0 ## Доступные множители скорости. const SPEEDS: Array[int] = [1, 2, 3, 4] const MINUTES_PER_DAY := 24 * 60 const MONTHS: Array[String] = [ "января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря", ] ## Прошла игровая минута — сюда подключаются HUD, производство, события. signal minute_passed(total_minutes: int) ## Сменились сутки. signal day_passed(day_index: int) ## Изменились скорость или состояние паузы. signal flow_changed var running := true var speed := 1 var _start_unix := int(Time.get_unix_time_from_datetime_dict(START)) var _elapsed_minutes := 0.0 var _last_whole_minute := 0 var _last_day := 0 func _process(delta: float) -> void: if not running: return _advance(delta * MINUTES_PER_SECOND * float(speed)) # --- Текущий момент --------------------------------------------------------- ## Момент как словарь Time: year, month, day, hour, minute, second. func now() -> Dictionary: return Time.get_datetime_dict_from_unix_time(unix_time()) func unix_time() -> int: return _start_unix + int(_elapsed_minutes) * 60 ## «1 апреля 2012» func date_text() -> String: var moment := now() return "%d %s %d" % [ int(moment["day"]), MONTHS[int(moment["month"]) - 1], int(moment["year"]), ] ## «06:00» func time_text() -> String: var moment := now() return "%02d:%02d" % [int(moment["hour"]), int(moment["minute"])] # --- Управление ходом ------------------------------------------------------- func set_speed(value: int) -> void: speed = clampi(value, SPEEDS[0], SPEEDS[SPEEDS.size() - 1]) running = true flow_changed.emit() func play() -> void: if running: return running = true flow_changed.emit() func pause() -> void: if not running: return running = false flow_changed.emit() func toggle() -> void: running = not running flow_changed.emit() ## Перевести часы на указанный момент (словарь как у Time). Возвращает false, ## если момент раньше начала партии — назад отматывать нечего. func set_moment(moment: Dictionary) -> bool: var target := int(Time.get_unix_time_from_datetime_dict(moment)) if target < _start_unix: return false _elapsed_minutes = float(target - _start_unix) / 60.0 _last_whole_minute = int(_elapsed_minutes) _last_day = _day_index() minute_passed.emit(_last_whole_minute) return true # --- Сохранение ------------------------------------------------------------- func save_data() -> Dictionary: return {"minutes": _elapsed_minutes, "speed": speed, "running": running} func load_data(data: Dictionary) -> void: _elapsed_minutes = maxf(float(data.get("minutes", 0.0)), 0.0) speed = clampi(int(data.get("speed", 1)), SPEEDS[0], SPEEDS[SPEEDS.size() - 1]) running = bool(data.get("running", true)) _last_whole_minute = int(_elapsed_minutes) _last_day = _day_index() flow_changed.emit() minute_passed.emit(_last_whole_minute) # --- Внутреннее ------------------------------------------------------------- func _advance(minutes: float) -> void: _elapsed_minutes += minutes # Сигналим по целым минутам, а не каждый кадр: подписчикам нужен такт, # а не дробное значение. var whole := int(_elapsed_minutes) if whole == _last_whole_minute: return _last_whole_minute = whole minute_passed.emit(whole) var day := _day_index() if day == _last_day: return _last_day = day day_passed.emit(day) func _day_index() -> int: return floori(_elapsed_minutes / float(MINUTES_PER_DAY))