first commit

This commit is contained in:
Leonid Pershin
2026-08-10 20:39:06 +03:00
commit f16f60445c
281 changed files with 43576 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
extends CanvasLayer
## Меню паузы. Кладётся внутрь игровой сцены и работает само по себе:
## ловит действие «pause», останавливает дерево и переиспользует те же
## экраны настроек и сохранений, что и главное меню.
##
## Слой обрабатывается всегда (process_mode = ALWAYS), поэтому анимации и
## ввод продолжают работать, пока остальная игра стоит на паузе.
## Куда уходим по «В главное меню».
@export_file("*.tscn") var main_menu_scene: String = "res://scenes/ui/main_menu.tscn"
signal opened
signal closed
@onready var _saves: SaveManagerService = get_node("/root/SaveManager")
@onready var _root: Control = $Root
@onready var _card: Control = $Root/Center/Card
@onready var _menu: VBoxContainer = $Root/Center/Card/Body/Menu
@onready var _settings_screen: Control = $Root/SettingsScreen
@onready var _save_screen: SaveMenuScreen = $Root/SaveScreen
## Режим курсора до паузы — игра могла держать его захваченным.
var _previous_mouse_mode := Input.MOUSE_MODE_VISIBLE
func _ready() -> void:
_root.visible = false
for button: Button in _menu.get_children():
# Мышь и клавиатура делят одну подсветку: наведение = фокус.
button.mouse_entered.connect(button.grab_focus)
button.pressed.connect(_on_menu_pressed.bind(button.name))
_settings_screen.closed.connect(_focus_first_button)
_save_screen.closed.connect(_focus_first_button)
func _unhandled_input(event: InputEvent) -> void:
# Вложенные экраны сами обрабатывают Esc и гасят событие — сюда оно не
# дойдёт, но проверка оставлена на случай другой раскладки для «pause».
if _settings_screen.visible or _save_screen.visible:
return
if not event.is_action_pressed("pause"):
return
toggle()
get_viewport().set_input_as_handled()
func toggle() -> void:
if _root.visible:
close()
else:
open()
func open() -> void:
# Миниатюру снимаем до показа меню, иначе в слот попадёт само меню.
_saves.capture_thumbnail()
get_tree().paused = true
_previous_mouse_mode = Input.mouse_mode
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_root.modulate.a = 0.0
_root.visible = true
# Контейнеры раскладываются только после того, как узел стал видимым.
await get_tree().process_frame
_card.pivot_offset = _card.size * 0.5
_card.scale = Vector2(0.96, 0.96)
var tween := create_tween().set_parallel(true).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_CUBIC)
tween.tween_property(_root, "modulate:a", 1.0, 0.15)
tween.tween_property(_card, "scale", Vector2.ONE, 0.2)
_focus_first_button()
opened.emit()
func close() -> void:
var tween := create_tween().set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_CUBIC)
tween.tween_property(_root, "modulate:a", 0.0, 0.12)
await tween.finished
_root.visible = false
get_tree().paused = false
Input.mouse_mode = _previous_mouse_mode
closed.emit()
func _on_menu_pressed(button_name: StringName) -> void:
match String(button_name):
"ResumeButton":
close()
"SaveButton":
_save_screen.open(SaveMenuScreen.Mode.SAVE)
"LoadButton":
_save_screen.open(SaveMenuScreen.Mode.LOAD)
"SettingsButton":
_settings_screen.open()
"MainMenuButton":
_return_to_main_menu()
"QuitButton":
_quit()
func _focus_first_button() -> void:
for button: Button in _menu.get_children():
if button.visible:
button.grab_focus()
return
func _return_to_main_menu() -> void:
get_tree().paused = false
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
_saves.end_session()
get_tree().change_scene_to_file(main_menu_scene)
func _quit() -> void:
var tween := create_tween()
tween.tween_property(_root, "modulate:a", 0.0, 0.2)
await tween.finished
get_tree().paused = false
get_tree().quit()