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.
|
## Связывает три части: модель (VaultGrid), отрисовку (VaultView) и HUD.
|
||||||
## Здесь же живёт контракт сохранения — сцена лежит в группе "persistent".
|
## Здесь же живёт контракт сохранения — сцена лежит в группе "persistent".
|
||||||
##
|
##
|
||||||
## Управление: ЛКМ — построить или снести, ПКМ/СКМ — тащить вид,
|
## Управление: ЛКМ действует по режиму из нижней панели (по умолчанию — выбор
|
||||||
## колесо — зум, WASD и стрелки — прокрутка.
|
## комнаты), ПКМ/СКМ — тащить вид, колесо — зум, WASD и стрелки — прокрутка.
|
||||||
|
|
||||||
## Строки сводки ресурсов: ключ из VaultGrid.totals() → подпись и цвет.
|
## Строки сводки ресурсов: ключ из VaultGrid.totals() → подпись и цвет.
|
||||||
const STAT_ROWS := {
|
const STAT_ROWS := {
|
||||||
@@ -14,7 +14,15 @@ const STAT_ROWS := {
|
|||||||
"food": {"label": "Еда", "color": "#f2704a"},
|
"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 _saves: SaveManagerService = get_node("/root/SaveManager")
|
||||||
@onready var _vault: VaultView = $View
|
@onready var _vault: VaultView = $View
|
||||||
@@ -24,9 +32,15 @@ const HINT := "ЛКМ — строить · ПКМ — тащить вид · к
|
|||||||
@onready var _status: Label = $Hud/Root/BottomAnchor/Row/BuildBar/Panel/Status
|
@onready var _status: Label = $Hud/Root/BottomAnchor/Row/BuildBar/Panel/Status
|
||||||
|
|
||||||
var _grid := VaultGrid.new()
|
var _grid := VaultGrid.new()
|
||||||
var _selected_kind := "living"
|
var _mode := Mode.SELECT
|
||||||
var _demolish := false
|
## Тип комнаты, который построится в режиме Mode.BUILD.
|
||||||
|
var _build_kind := ""
|
||||||
|
## Якорная ячейка выбранной комнаты; (-1, -1) — ничего не выбрано.
|
||||||
|
var _selected_cell := Vector2i(-1, -1)
|
||||||
var _stat_labels := {}
|
var _stat_labels := {}
|
||||||
|
var _tool_group := ButtonGroup.new()
|
||||||
|
var _right_pressed := false
|
||||||
|
var _right_press_position := Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -43,7 +57,34 @@ func _ready() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _process(_delta: float) -> 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:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
@@ -52,7 +93,9 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||||||
var button := event as InputEventMouseButton
|
var button := event as InputEventMouseButton
|
||||||
if button.button_index != MOUSE_BUTTON_LEFT or not button.pressed:
|
if button.button_index != MOUSE_BUTTON_LEFT or not button.pressed:
|
||||||
return
|
return
|
||||||
_apply_at(_cell_under_mouse())
|
# Берём точку из самого события, а не текущее положение курсора: клик
|
||||||
|
# должен срабатывать там, где кликнули.
|
||||||
|
_apply_at(_cell_at_screen(button.position))
|
||||||
get_viewport().set_input_as_handled()
|
get_viewport().set_input_as_handled()
|
||||||
|
|
||||||
|
|
||||||
@@ -62,26 +105,68 @@ func _cell_under_mouse() -> Vector2i:
|
|||||||
return VaultGrid.cell_at(_vault.get_local_mouse_position())
|
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):
|
func _cell_at_screen(screen_position: Vector2) -> Vector2i:
|
||||||
_after_change("Комната снесена.")
|
var local := _vault.get_global_transform_with_canvas().affine_inverse() * screen_position
|
||||||
else:
|
return VaultGrid.cell_at(local)
|
||||||
_status.text = "Здесь нечего сносить."
|
|
||||||
return
|
|
||||||
|
|
||||||
if _grid.place(_selected_kind, cell.y, cell.x):
|
|
||||||
_after_change("Построено: %s" % VaultGrid.KINDS[_selected_kind]["label"])
|
func _apply_at(cell: Vector2i) -> void:
|
||||||
else:
|
match _mode:
|
||||||
_status.text = "Здесь строить нельзя: занято или вплотную к шахте."
|
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:
|
func _after_change(message: String) -> void:
|
||||||
|
# Индексы комнат после изменения съезжают — выделение сбрасываем.
|
||||||
|
_set_selection(Vector2i(-1, -1))
|
||||||
_vault.queue_redraw()
|
_vault.queue_redraw()
|
||||||
_refresh_stats()
|
_refresh_stats()
|
||||||
_status.text = message
|
_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 --------------------------------------------------------------------
|
# --- HUD --------------------------------------------------------------------
|
||||||
|
|
||||||
func _build_stats() -> void:
|
func _build_stats() -> void:
|
||||||
@@ -118,16 +203,18 @@ func _refresh_stats() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _build_tools() -> void:
|
func _build_tools() -> void:
|
||||||
var group := ButtonGroup.new()
|
# Отдельной кнопки «Выбор» нет: выбор — это состояние по умолчанию, когда
|
||||||
|
# ни один инструмент не нажат. allow_unpress даёт отжать активный
|
||||||
|
# инструмент повторным кликом и вернуться к выбору.
|
||||||
|
_tool_group.allow_unpress = true
|
||||||
|
|
||||||
for kind: String in VaultGrid.KINDS:
|
for kind: String in VaultGrid.KINDS:
|
||||||
var button := Button.new()
|
var button := Button.new()
|
||||||
button.text = String(VaultGrid.KINDS[kind]["label"])
|
button.text = String(VaultGrid.KINDS[kind]["label"])
|
||||||
button.toggle_mode = true
|
button.toggle_mode = true
|
||||||
button.button_group = group
|
button.button_group = _tool_group
|
||||||
button.add_theme_font_size_override("font_size", 18)
|
button.add_theme_font_size_override("font_size", 18)
|
||||||
button.button_pressed = kind == _selected_kind
|
button.toggled.connect(_on_kind_toggled.bind(kind))
|
||||||
button.pressed.connect(_on_kind_selected.bind(kind))
|
|
||||||
_tools.add_child(button)
|
_tools.add_child(button)
|
||||||
|
|
||||||
var separator := Control.new()
|
var separator := Control.new()
|
||||||
@@ -137,21 +224,49 @@ func _build_tools() -> void:
|
|||||||
var demolish := Button.new()
|
var demolish := Button.new()
|
||||||
demolish.text = "Снести"
|
demolish.text = "Снести"
|
||||||
demolish.toggle_mode = true
|
demolish.toggle_mode = true
|
||||||
demolish.button_group = group
|
demolish.button_group = _tool_group
|
||||||
demolish.add_theme_font_size_override("font_size", 18)
|
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)
|
_tools.add_child(demolish)
|
||||||
|
|
||||||
|
|
||||||
func _on_kind_selected(kind: String) -> void:
|
## Возврат к выбору: ни один инструмент не нажат.
|
||||||
_selected_kind = kind
|
func _reset_to_select() -> void:
|
||||||
_demolish = false
|
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
|
_status.text = HINT
|
||||||
|
|
||||||
|
|
||||||
func _on_demolish_selected() -> void:
|
func _on_kind_toggled(is_on: bool, kind: String) -> void:
|
||||||
_demolish = true
|
if not is_on:
|
||||||
_status.text = "Режим сноса: кликните по комнате."
|
# Инструмент отжали — если он и был активным, возвращаемся к выбору.
|
||||||
|
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))
|
var level := float(camera_state.get("zoom", 1.0))
|
||||||
_camera.zoom = Vector2(level, level)
|
_camera.zoom = Vector2(level, level)
|
||||||
|
|
||||||
|
_set_selection(Vector2i(-1, -1))
|
||||||
_vault.queue_redraw()
|
_vault.queue_redraw()
|
||||||
_refresh_stats()
|
_refresh_stats()
|
||||||
_status.text = "Убежище загружено."
|
_status.text = "Убежище загружено."
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const EMPTY_FILL := Color("#ffffff", 0.02)
|
|||||||
const EMPTY_LINE := Color("#ffffff", 0.06)
|
const EMPTY_LINE := Color("#ffffff", 0.06)
|
||||||
const GHOST_OK := Color("#2ee6c5")
|
const GHOST_OK := Color("#2ee6c5")
|
||||||
const GHOST_BAD := Color("#f2704a")
|
const GHOST_BAD := Color("#f2704a")
|
||||||
|
const SELECTION := Color("#ffffff")
|
||||||
|
|
||||||
const MARGIN := 1200.0
|
const MARGIN := 1200.0
|
||||||
const SKY_HEIGHT := 900.0
|
const SKY_HEIGHT := 900.0
|
||||||
@@ -36,6 +37,8 @@ var grid: VaultGrid
|
|||||||
var _ghost_kind := ""
|
var _ghost_kind := ""
|
||||||
var _ghost_cell := Vector2i(-1, -1)
|
var _ghost_cell := Vector2i(-1, -1)
|
||||||
var _demolish := false
|
var _demolish := false
|
||||||
|
## Якорная ячейка выделенной комнаты; (-1, -1) — ничего не выделено.
|
||||||
|
var _selected_cell := Vector2i(-1, -1)
|
||||||
var _pebbles: Array[Dictionary] = []
|
var _pebbles: Array[Dictionary] = []
|
||||||
|
|
||||||
|
|
||||||
@@ -54,6 +57,15 @@ func set_ghost(kind: String, cell: Vector2i, demolish: bool) -> void:
|
|||||||
queue_redraw()
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
## Выделенная комната задаётся ячейкой, а не индексом: индексы съезжают
|
||||||
|
## при сносе, а ячейка остаётся собой.
|
||||||
|
func set_selection(cell: Vector2i) -> void:
|
||||||
|
if cell == _selected_cell:
|
||||||
|
return
|
||||||
|
_selected_cell = cell
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
if grid == null:
|
if grid == null:
|
||||||
return
|
return
|
||||||
@@ -65,6 +77,7 @@ func _draw() -> void:
|
|||||||
_draw_floor_numbers()
|
_draw_floor_numbers()
|
||||||
for room: Dictionary in grid.rooms:
|
for room: Dictionary in grid.rooms:
|
||||||
_draw_room(room)
|
_draw_room(room)
|
||||||
|
_draw_selection()
|
||||||
_draw_ghost()
|
_draw_ghost()
|
||||||
|
|
||||||
|
|
||||||
@@ -216,7 +229,34 @@ func _draw_equipment(inner: Rect2, accent: Color, width: int) -> void:
|
|||||||
draw_rect(Rect2(machine.position + Vector2(6.0, 6.0), Vector2(22.0, 4.0)), glow)
|
draw_rect(Rect2(machine.position + Vector2(6.0, 6.0), Vector2(22.0, 4.0)), glow)
|
||||||
|
|
||||||
|
|
||||||
# --- Строительство ----------------------------------------------------------
|
# --- Выделение и строительство ----------------------------------------------
|
||||||
|
|
||||||
|
## Рамка с уголками вокруг выбранной комнаты. Уголки нужны, чтобы выделение
|
||||||
|
## читалось поверх любой заливки — тонкой линии на пёстром фоне мало.
|
||||||
|
func _draw_selection() -> void:
|
||||||
|
if _selected_cell.x < 0:
|
||||||
|
return
|
||||||
|
var index := grid.room_index_at(_selected_cell.y, _selected_cell.x)
|
||||||
|
if index < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var rect := VaultGrid.room_rect(grid.rooms[index]).grow(-2.0)
|
||||||
|
draw_rect(rect, Color(SELECTION, 0.08))
|
||||||
|
draw_rect(rect, Color(SELECTION, 0.75), false, 2.0)
|
||||||
|
|
||||||
|
var arm := 20.0
|
||||||
|
var corners := [
|
||||||
|
[rect.position, Vector2(1.0, 1.0)],
|
||||||
|
[Vector2(rect.end.x, rect.position.y), Vector2(-1.0, 1.0)],
|
||||||
|
[rect.end, Vector2(-1.0, -1.0)],
|
||||||
|
[Vector2(rect.position.x, rect.end.y), Vector2(1.0, -1.0)],
|
||||||
|
]
|
||||||
|
for entry: Array in corners:
|
||||||
|
var point: Vector2 = entry[0]
|
||||||
|
var direction: Vector2 = entry[1]
|
||||||
|
draw_line(point, point + Vector2(arm * direction.x, 0.0), SELECTION, 4.0)
|
||||||
|
draw_line(point, point + Vector2(0.0, arm * direction.y), SELECTION, 4.0)
|
||||||
|
|
||||||
|
|
||||||
func _draw_ghost() -> void:
|
func _draw_ghost() -> void:
|
||||||
if _ghost_cell.x < 0:
|
if _ghost_cell.x < 0:
|
||||||
|
|||||||
Reference in New Issue
Block a user