полная мета-структура игры: pause + settings + save/load
Some checks failed
Godot CI / Import + smoke test (push) Failing after 13s

PAUSE-МЕНЮ (autoload PauseMenu CanvasLayer):
- ESC во время геймплея открывает оверлей с тёмным фоном
  и центральной панелью в Шиндовс-стиле.
- 4 кнопки: Продолжить / Настройки / В главное меню /
  Сохранить и выйти.
- process_mode = ALWAYS чтобы работать при tree.paused = true.
- При закрытии меню в 3D-сценах автоматически возвращает
  Input.mouse_mode = CAPTURED.
- В главном меню pause не активируется (_can_pause false).
- set_input_as_handled блокирует FPV-контроллер от перехвата
  ESC одновременно.

НАСТРОЙКИ (под-панель в Pause-меню):
- Слайдер «Чувствительность мыши» 0.001-0.010 (по умолчанию 0.0025).
- Слайдер «Громкость» 0-100% — связан с AudioServer Bus 0 Master
  через linear_to_db (audio ассетов пока нет, но шкала рабочая).
- Кнопки Назад / Применить. Применить пишет в user://settings.json.
- settings.json грузится автоматически при старте GameState._ready.

SAVE/LOAD:
- GameState.save_game(scene_path) → user://save01.json в JSON
  с player_name, прогрессом актов 1-2, ачивками, текущей сценой.
- GameState.load_game() читает и восстанавливает state, заполняет
  last_scene_path для перехода.
- has_save() / delete_save() помощники.
- Кнопка «Продолжить» в главном меню активна только если есть
  save и грузит игрока в сохранённую сцену.
- Кнопка «Новая игра» удаляет старый save и сбрасывает state.

AUTO-SAVE:
- При закрытии окна через X (NOTIFICATION_WM_CLOSE_REQUEST) —
  save_game перед quit.
- Кнопка Q в FPV-контроллере — save+quit.
- Кнопка «Сохранить и выйти» в pause-меню — save+quit.
- Кнопка «В главное меню» — save перед переходом.

FPV-КОНТРОЛЛЕР:
- mouse_sensitivity больше не локальное @export, читается из
  GameState.mouse_sensitivity каждый mouse_motion event
  (применяется живьём после Apply).
- _input → _unhandled_input чтобы PauseMenu мог перехватить ESC
  через set_input_as_handled.
- Убран ESC-toggle захвата мыши (теперь это делает PauseMenu).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dmitrii Bykov 2026-05-24 18:28:58 +03:00
parent a6f6785188
commit f8040fb399
7 changed files with 442 additions and 12 deletions

115
scripts/ui/pause_menu.gd Normal file
View file

@ -0,0 +1,115 @@
extends CanvasLayer
@onready var bg: ColorRect = $BG
@onready var main_panel: PanelContainer = $MainPanel
@onready var settings_panel: PanelContainer = $SettingsPanel
@onready var btn_resume: Button = $MainPanel/V/BtnResume
@onready var btn_settings: Button = $MainPanel/V/BtnSettings
@onready var btn_main: Button = $MainPanel/V/BtnMainMenu
@onready var btn_quit: Button = $MainPanel/V/BtnQuit
@onready var sens_slider: HSlider = $SettingsPanel/V/SensRow/Slider
@onready var sens_value: Label = $SettingsPanel/V/SensRow/Value
@onready var vol_slider: HSlider = $SettingsPanel/V/VolRow/Slider
@onready var vol_value: Label = $SettingsPanel/V/VolRow/Value
@onready var btn_apply: Button = $SettingsPanel/V/Buttons/Apply
@onready var btn_back: Button = $SettingsPanel/V/Buttons/Back
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
visible = false
main_panel.visible = true
settings_panel.visible = false
layer = 100
btn_resume.pressed.connect(_resume)
btn_settings.pressed.connect(_open_settings)
btn_main.pressed.connect(_to_main_menu)
btn_quit.pressed.connect(_quit)
btn_apply.pressed.connect(_apply_settings)
btn_back.pressed.connect(_close_settings)
sens_slider.value_changed.connect(func(_v: float) -> void: _update_value_labels())
vol_slider.value_changed.connect(func(_v: float) -> void: _update_value_labels())
func _input(event: InputEvent) -> void:
if event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
if visible:
if settings_panel.visible:
_close_settings()
else:
_resume()
get_viewport().set_input_as_handled()
elif _can_pause():
_open()
get_viewport().set_input_as_handled()
func _can_pause() -> bool:
var scene: Node = get_tree().current_scene
if scene == null:
return false
# Не паузим в главном меню
if scene.name == "MainMenu":
return false
return true
func _open() -> void:
visible = true
main_panel.visible = true
settings_panel.visible = false
get_tree().paused = true
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
btn_resume.grab_focus()
func _resume() -> void:
visible = false
get_tree().paused = false
# Восстановить захват мыши только для 3D-сцен (с FPV игроком)
var scene: Node = get_tree().current_scene
if scene != null and scene.has_node("PlayerFPV"):
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _open_settings() -> void:
sens_slider.value = GameState.mouse_sensitivity * 1000.0
vol_slider.value = GameState.master_volume * 100.0
_update_value_labels()
main_panel.visible = false
settings_panel.visible = true
btn_back.grab_focus()
func _close_settings() -> void:
settings_panel.visible = false
main_panel.visible = true
btn_resume.grab_focus()
func _update_value_labels() -> void:
sens_value.text = "%.4f" % (sens_slider.value / 1000.0)
vol_value.text = "%d%%" % int(vol_slider.value)
func _apply_settings() -> void:
GameState.mouse_sensitivity = sens_slider.value / 1000.0
GameState.master_volume = vol_slider.value / 100.0
GameState._apply_audio_volume()
GameState.save_settings()
_close_settings()
func _to_main_menu() -> void:
GameState.save_game(get_tree().current_scene.scene_file_path if get_tree().current_scene else "")
visible = false
get_tree().paused = false
SceneManager.go_main()
func _quit() -> void:
GameState.save_game(get_tree().current_scene.scene_file_path if get_tree().current_scene else "")
get_tree().quit()