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:
+146
-30
@@ -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 = "Убежище загружено."
|
||||
|
||||
Reference in New Issue
Block a user