Enhance vault scene functionality and user interaction. Updated control instructions for room selection, implemented mode switching for building and demolishing, and refined input handling for mouse events. Added visual selection feedback for rooms and improved ghost placement logic. Updated HUD interactions to reflect new selection mechanics.

This commit is contained in:
Leonid Pershin
2026-08-11 09:19:45 +03:00
parent d9c8a1e0f6
commit ecef4a857c
2 changed files with 187 additions and 31 deletions
+146 -30
View File
@@ -4,8 +4,8 @@ extends Node2D
## Связывает три части: модель (VaultGrid), отрисовку (VaultView) и HUD.
## Здесь же живёт контракт сохранения — сцена лежит в группе "persistent".
##
## Управление: ЛКМ — построить или снести, ПКМ/СКМ — тащить вид,
## колесо — зум, WASD и стрелки — прокрутка.
## Управление: ЛКМ действует по режиму из нижней панели (по умолчанию — выбор
## комнаты), ПКМ/СКМ — тащить вид, колесо — зум, WASD и стрелки — прокрутка.
## Строки сводки ресурсов: ключ из VaultGrid.totals() → подпись и цвет.
const STAT_ROWS := {
@@ -14,7 +14,15 @@ const STAT_ROWS := {
"food": {"label": "Еда", "color": "#f2704a"},
}
const HINT := "ЛКМ — строить · ПКМ — тащить вид · колесо — зум"
const HINT := "ЛКМ — выбрать комнату · ПКМ — тащить вид · колесо — зум"
## Насколько мышь может сместиться между нажатием и отпусканием, чтобы это
## всё ещё считалось кликом, а не перетаскиванием вида.
const CLICK_SLACK := 6.0
## Режим курсора. По умолчанию сцена ничего не строит: строительство
## включается только выбором типа комнаты в нижней панели.
enum Mode { SELECT, BUILD, DEMOLISH }
@onready var _saves: SaveManagerService = get_node("/root/SaveManager")
@onready var _vault: VaultView = $View
@@ -24,9 +32,15 @@ const HINT := "ЛКМ — строить · ПКМ — тащить вид · к
@onready var _status: Label = $Hud/Root/BottomAnchor/Row/BuildBar/Panel/Status
var _grid := VaultGrid.new()
var _selected_kind := "living"
var _demolish := false
var _mode := Mode.SELECT
## Тип комнаты, который построится в режиме Mode.BUILD.
var _build_kind := ""
## Якорная ячейка выбранной комнаты; (-1, -1) — ничего не выбрано.
var _selected_cell := Vector2i(-1, -1)
var _stat_labels := {}
var _tool_group := ButtonGroup.new()
var _right_pressed := false
var _right_press_position := Vector2.ZERO
func _ready() -> void:
@@ -43,7 +57,34 @@ func _ready() -> void:
func _process(_delta: float) -> void:
_vault.set_ghost(_selected_kind, _cell_under_mouse(), _demolish)
# В режиме выбора призрака нет: пустой тип и (-1, -1) гасят подсветку.
if _mode == Mode.SELECT:
_vault.set_ghost("", Vector2i(-1, -1), false)
return
_vault.set_ghost(_build_kind, _cell_under_mouse(), _mode == Mode.DEMOLISH)
## ПКМ обслуживается здесь, а не в _unhandled_input: камера забирает эти
## события себе под перетаскивание вида. Событие не гасим — камера должна
## получить его как обычно.
func _input(event: InputEvent) -> void:
if not (event is InputEventMouseButton):
return
var button := event as InputEventMouseButton
if button.button_index != MOUSE_BUTTON_RIGHT:
return
if button.pressed:
_right_pressed = true
_right_press_position = button.position
return
if not _right_pressed:
return
_right_pressed = false
# Вид не двигали — значит это клик, а не перетаскивание.
if button.position.distance_to(_right_press_position) <= CLICK_SLACK:
_reset_to_select()
func _unhandled_input(event: InputEvent) -> void:
@@ -52,7 +93,9 @@ func _unhandled_input(event: InputEvent) -> void:
var button := event as InputEventMouseButton
if button.button_index != MOUSE_BUTTON_LEFT or not button.pressed:
return
_apply_at(_cell_under_mouse())
# Берём точку из самого события, а не текущее положение курсора: клик
# должен срабатывать там, где кликнули.
_apply_at(_cell_at_screen(button.position))
get_viewport().set_input_as_handled()
@@ -62,26 +105,68 @@ func _cell_under_mouse() -> Vector2i:
return VaultGrid.cell_at(_vault.get_local_mouse_position())
func _apply_at(cell: Vector2i) -> void:
if _demolish:
if _grid.remove_at(cell.y, cell.x):
_after_change("Комната снесена.")
else:
_status.text = "Здесь нечего сносить."
return
## Ячейка под точкой экрана. Учитывает камеру: переводим из координат
## окна в локальные координаты вида.
func _cell_at_screen(screen_position: Vector2) -> Vector2i:
var local := _vault.get_global_transform_with_canvas().affine_inverse() * screen_position
return VaultGrid.cell_at(local)
if _grid.place(_selected_kind, cell.y, cell.x):
_after_change("Построено: %s" % VaultGrid.KINDS[_selected_kind]["label"])
else:
_status.text = "Здесь строить нельзя: занято или вплотную к шахте."
func _apply_at(cell: Vector2i) -> void:
match _mode:
Mode.SELECT:
_select_at(cell)
Mode.DEMOLISH:
if _grid.remove_at(cell.y, cell.x):
_after_change("Комната снесена.")
else:
_status.text = "Здесь нечего сносить."
Mode.BUILD:
if _grid.place(_build_kind, cell.y, cell.x):
_after_change("Построено: %s" % VaultGrid.KINDS[_build_kind]["label"])
else:
_status.text = "Здесь строить нельзя: занято или вплотную к шахте."
func _after_change(message: String) -> void:
# Индексы комнат после изменения съезжают — выделение сбрасываем.
_set_selection(Vector2i(-1, -1))
_vault.queue_redraw()
_refresh_stats()
_status.text = message
# --- Выбор ------------------------------------------------------------------
func _select_at(cell: Vector2i) -> void:
var index := _grid.room_index_at(cell.y, cell.x)
if index < 0:
_set_selection(Vector2i(-1, -1))
_status.text = HINT
return
_set_selection(cell)
_status.text = _describe(_grid.rooms[index])
func _set_selection(cell: Vector2i) -> void:
_selected_cell = cell
_vault.set_selection(cell)
## Строка для нижней панели: что за комната, на каком этаже и её баланс.
func _describe(room: Dictionary) -> String:
var kind: Dictionary = VaultGrid.KINDS[String(room["kind"])]
var parts := PackedStringArray()
parts.append(String(kind["label"]))
parts.append("этаж %d" % (int(room["floor"]) + 1))
for key: String in STAT_ROWS:
var amount := int(kind[key])
if amount != 0:
parts.append("%s %+d" % [String(STAT_ROWS[key]["label"]).to_lower(), amount])
return " · ".join(parts)
# --- HUD --------------------------------------------------------------------
func _build_stats() -> void:
@@ -118,16 +203,18 @@ func _refresh_stats() -> void:
func _build_tools() -> void:
var group := ButtonGroup.new()
# Отдельной кнопки «Выбор» нет: выбор — это состояние по умолчанию, когда
# ни один инструмент не нажат. allow_unpress даёт отжать активный
# инструмент повторным кликом и вернуться к выбору.
_tool_group.allow_unpress = true
for kind: String in VaultGrid.KINDS:
var button := Button.new()
button.text = String(VaultGrid.KINDS[kind]["label"])
button.toggle_mode = true
button.button_group = group
button.button_group = _tool_group
button.add_theme_font_size_override("font_size", 18)
button.button_pressed = kind == _selected_kind
button.pressed.connect(_on_kind_selected.bind(kind))
button.toggled.connect(_on_kind_toggled.bind(kind))
_tools.add_child(button)
var separator := Control.new()
@@ -137,21 +224,49 @@ func _build_tools() -> void:
var demolish := Button.new()
demolish.text = "Снести"
demolish.toggle_mode = true
demolish.button_group = group
demolish.button_group = _tool_group
demolish.add_theme_font_size_override("font_size", 18)
demolish.pressed.connect(_on_demolish_selected)
demolish.toggled.connect(_on_demolish_toggled)
_tools.add_child(demolish)
func _on_kind_selected(kind: String) -> void:
_selected_kind = kind
_demolish = false
## Возврат к выбору: ни один инструмент не нажат.
func _reset_to_select() -> void:
if _mode == Mode.SELECT:
return
_mode = Mode.SELECT
_build_kind = ""
var active := _tool_group.get_pressed_button()
if active != null:
active.set_pressed_no_signal(false)
_status.text = HINT
func _on_demolish_selected() -> void:
_demolish = true
_status.text = "Режим сноса: кликните по комнате."
func _on_kind_toggled(is_on: bool, kind: String) -> void:
if not is_on:
# Инструмент отжали — если он и был активным, возвращаемся к выбору.
if _mode == Mode.BUILD and _build_kind == kind:
_reset_to_select()
return
_mode = Mode.BUILD
_build_kind = kind
_set_selection(Vector2i(-1, -1))
_status.text = "Стройка: %s · ЛКМ — поставить · ПКМ — отмена" % VaultGrid.KINDS[kind]["label"]
func _on_demolish_toggled(is_on: bool) -> void:
if not is_on:
if _mode == Mode.DEMOLISH:
_reset_to_select()
return
_mode = Mode.DEMOLISH
_build_kind = ""
_set_selection(Vector2i(-1, -1))
_status.text = "Снос: кликните по комнате · ПКМ — отмена"
# --- Сохранение -------------------------------------------------------------
@@ -178,6 +293,7 @@ func load_data(data: Dictionary) -> void:
var level := float(camera_state.get("zoom", 1.0))
_camera.zoom = Vector2(level, level)
_set_selection(Vector2i(-1, -1))
_vault.queue_redraw()
_refresh_stats()
_status.text = "Убежище загружено."