From ce896fc1e0e497791338ec6b8477c8a5e88ba6f7 Mon Sep 17 00:00:00 2001 From: ANG-Server <96008766+ANGJustinl@users.noreply.github.com> Date: Wed, 22 Jul 2026 14:14:43 +0800 Subject: [PATCH] fix: improve personal space layout editing --- scenes/Maps/PersonalSpace.gd | 94 +++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/scenes/Maps/PersonalSpace.gd b/scenes/Maps/PersonalSpace.gd index a616616..f69805d 100644 --- a/scenes/Maps/PersonalSpace.gd +++ b/scenes/Maps/PersonalSpace.gd @@ -23,7 +23,8 @@ const DECOR_TEXTURE_FILTER: int = CanvasItem.TEXTURE_FILTER_LINEAR const DECOR_COLLISION_LAYER: int = 1 const DECOR_COLLISION_MASK: int = 1 const ROOM_GRID_SIZE: float = 16.0 -const ROOM_PLACE_BOUNDS: Rect2 = Rect2(-426.0, -186.0, 852.0, 398.0) +const FLOOR_PLACE_BOUNDS: Rect2 = Rect2(-426.0, -186.0, 852.0, 398.0) +const WALL_PLACE_BOUNDS: Rect2 = Rect2(-378.0, -258.0, 756.0, 72.0) @onready var player: PlayerController = $YSortWorld/Characters/Players/Player @onready var playerCamera: Camera2D = $YSortWorld/Characters/Players/Player/Camera2D @@ -32,6 +33,7 @@ const ROOM_PLACE_BOUNDS: Rect2 = Rect2(-426.0, -186.0, 852.0, 398.0) @onready var staticCollision: Node2D = $StaticCollision var _decorLayer: Node2D +var _gridOverlay: Node2D var _inventoryRequest: HTTPRequest var _inventoryPanel: PanelContainer var _inventoryGrid: GridContainer @@ -43,6 +45,7 @@ var _layoutStatusLabel: Label var _escHintLabel: Label var _editButton: Button var _backpackButton: Button +var _gridButton: Button var _undoButton: Button var _redoButton: Button var _rotateButton: Button @@ -67,6 +70,7 @@ var _visitorMode: bool = false var _visitorOwnerId: String = "" var _visitorOwnerName: String = "" var _editMode: bool = false +var _showPlacementGrid: bool = false var _undoHistory: Array[Dictionary] = [] var _redoHistory: Array[Dictionary] = [] var _dirtyDecorIds: Dictionary = {} @@ -191,6 +195,41 @@ func _build_decor_layer() -> void: _decorLayer.y_sort_enabled = true ySortWorld.add_child(_decorLayer) ySortWorld.move_child(_decorLayer, 0) + _build_placement_grid() + +func _build_placement_grid() -> void: + _gridOverlay = Node2D.new() + _gridOverlay.name = "RoomPlacementGrid" + _gridOverlay.z_index = -30 + _gridOverlay.visible = false + ySortWorld.add_child(_gridOverlay) + ySortWorld.move_child(_gridOverlay, 0) + _add_grid_zone(FLOOR_PLACE_BOUNDS, Color(0.050, 0.560, 0.900, 0.20), Color(0.050, 0.560, 0.900, 0.62)) + _add_grid_zone(WALL_PLACE_BOUNDS, Color(0.620, 0.330, 0.860, 0.20), Color(0.620, 0.330, 0.860, 0.62)) + +func _add_grid_zone(bounds: Rect2, line_color: Color, edge_color: Color) -> void: + if _gridOverlay == null: + return + var x: float = ceilf(bounds.position.x / ROOM_GRID_SIZE) * ROOM_GRID_SIZE + while x < bounds.end.x: + _add_grid_line(Vector2(x, bounds.position.y), Vector2(x, bounds.end.y), line_color, 1.0) + x += ROOM_GRID_SIZE + var y: float = ceilf(bounds.position.y / ROOM_GRID_SIZE) * ROOM_GRID_SIZE + while y < bounds.end.y: + _add_grid_line(Vector2(bounds.position.x, y), Vector2(bounds.end.x, y), line_color, 1.0) + y += ROOM_GRID_SIZE + _add_grid_line(bounds.position, Vector2(bounds.end.x, bounds.position.y), edge_color, 2.0) + _add_grid_line(Vector2(bounds.end.x, bounds.position.y), bounds.end, edge_color, 2.0) + _add_grid_line(bounds.end, Vector2(bounds.position.x, bounds.end.y), edge_color, 2.0) + _add_grid_line(Vector2(bounds.position.x, bounds.end.y), bounds.position, edge_color, 2.0) + +func _add_grid_line(from: Vector2, to: Vector2, color: Color, width: float) -> void: + var line := Line2D.new() + line.points = PackedVector2Array([from, to]) + line.default_color = color + line.width = width + line.antialiased = true + _gridOverlay.add_child(line) func _build_room_decor_requests() -> void: _inventoryRequest = HTTPRequest.new() @@ -355,6 +394,7 @@ func _build_room_toolbar() -> void: _editButton = _create_toolbar_button("编辑布局", func() -> void: _set_edit_mode(true)) _backpackButton = _create_toolbar_button("背包", _toggle_inventory_panel) + _gridButton = _create_toolbar_button("网格:关", _toggle_placement_grid) _undoButton = _create_toolbar_button("撤销", _undo_last_change) _redoButton = _create_toolbar_button("重做", _redo_last_change) _rotateButton = _create_toolbar_button("旋转", _rotate_selected_decor) @@ -364,6 +404,7 @@ func _build_room_toolbar() -> void: _finishEditButton = _create_toolbar_button("完成编辑", _request_finish_edit) row.add_child(_editButton) row.add_child(_backpackButton) + row.add_child(_gridButton) row.add_child(_undoButton) row.add_child(_redoButton) row.add_child(_rotateButton) @@ -770,7 +811,7 @@ func _finish_decor_drag() -> void: var decorId := str(_draggedDecor.get_meta("decor_id", "")) if _decorItems.has(decorId): var item: Dictionary = _decorItems[decorId] - var snapped_position := _snap_to_room_grid(_draggedDecor.global_position) + var snapped_position := _snap_to_room_grid(_draggedDecor.global_position, item) _draggedDecor.global_position = snapped_position item["position_x"] = snapped_position.x item["position_y"] = snapped_position.y @@ -793,10 +834,10 @@ func _finish_decor_drag() -> void: func _update_dragged_decor_position() -> void: if _draggedDecor != null: - _draggedDecor.global_position = _constrain_room_position(get_global_mouse_position() + _dragOffset) var decorId := str(_draggedDecor.get_meta("decor_id", "")) if not decorId.is_empty() and _decorItems.has(decorId): var item: Dictionary = _decorItems[decorId] + _draggedDecor.global_position = _constrain_room_position(get_global_mouse_position() + _dragOffset, item) item["position_x"] = _draggedDecor.global_position.x item["position_y"] = _draggedDecor.global_position.y _sync_decor_collision(decorId, item) @@ -889,11 +930,12 @@ func _on_decor_save_succeeded(savedItem: Dictionary) -> void: if decorId.is_empty() or not _saveAwaitingIds.has(decorId): return _serverDecorItems[decorId] = item.duplicate(true) + _decorItems[decorId] = item.duplicate(true) _saveAwaitingIds.erase(decorId) - if _decorItems.has(decorId) and _placement_signature(_decorItems[decorId]) == _placement_signature(item): - _dirtyDecorIds.erase(decorId) - else: - _dirtyDecorIds[decorId] = true + _dirtyDecorIds.erase(decorId) + _saveFailedIds.erase(decorId) + _render_placed_decors() + _render_inventory_list() _finalize_save_if_ready() func _can_edit_layout() -> bool: @@ -912,6 +954,8 @@ func _set_edit_mode(enabled: bool) -> void: _undoHistory.clear() _redoHistory.clear() _resetArmed = false + _showPlacementGrid = false + _update_placement_grid_visibility() _refresh_decor_selection() _update_editor_toolbar() _show_toast("布局编辑已开启;改动会在点击保存后提交") @@ -936,6 +980,8 @@ func _end_edit_session() -> void: _redoHistory.clear() _resetArmed = false _exitAfterSave = false + _showPlacementGrid = false + _update_placement_grid_visibility() if is_instance_valid(_inventoryPanel): _inventoryPanel.visible = false _hide_unsaved_dialog() @@ -975,6 +1021,10 @@ func _update_editor_toolbar() -> void: if is_instance_valid(_backpackButton): _backpackButton.visible = _editMode _backpackButton.disabled = _saveInProgress + if is_instance_valid(_gridButton): + _gridButton.visible = _editMode + _gridButton.disabled = _saveInProgress + _gridButton.text = "网格:开" if _showPlacementGrid else "网格:关" if is_instance_valid(_finishEditButton): _finishEditButton.visible = _editMode _finishEditButton.disabled = _saveInProgress @@ -1015,6 +1065,18 @@ func _update_editor_toolbar() -> void: _escHintLabel.visible = _editMode _escHintLabel.text = "Esc 结束编辑" if _editMode else "" +func _toggle_placement_grid() -> void: + if not _can_edit_layout(): + return + _showPlacementGrid = not _showPlacementGrid + _update_placement_grid_visibility() + _update_editor_toolbar() + _show_toast("已显示摆放网格(蓝色:地面,紫色:墙面)" if _showPlacementGrid else "已隐藏摆放网格") + +func _update_placement_grid_visibility() -> void: + if is_instance_valid(_gridOverlay): + _gridOverlay.visible = _editMode and _showPlacementGrid + func _record_item_change(before: Dictionary, after: Dictionary) -> void: if before == after: return @@ -1171,6 +1233,7 @@ func _save_layout(exit_after_save: bool = false) -> void: return _saveInProgress = true _exitAfterSave = exit_after_save + _hide_unsaved_dialog() _saveAwaitingIds.clear() _saveFailedIds.clear() for decor_id in save_ids: @@ -1291,18 +1354,23 @@ func _leave_visited_room() -> void: if not SceneManager.return_from_room_visit(): SceneManager.change_scene("square") -func _constrain_room_position(position: Vector2) -> Vector2: +func _placement_bounds_for_item(item: Dictionary) -> Rect2: + return WALL_PLACE_BOUNDS if str(item.get("placement_surface", "floor")) == "wall" else FLOOR_PLACE_BOUNDS + +func _constrain_room_position(position: Vector2, item: Dictionary) -> Vector2: + var bounds := _placement_bounds_for_item(item) return Vector2( - clampf(position.x, ROOM_PLACE_BOUNDS.position.x, ROOM_PLACE_BOUNDS.end.x), - clampf(position.y, ROOM_PLACE_BOUNDS.position.y, ROOM_PLACE_BOUNDS.end.y) + clampf(position.x, bounds.position.x, bounds.end.x), + clampf(position.y, bounds.position.y, bounds.end.y) ) -func _snap_to_room_grid(position: Vector2) -> Vector2: - var constrained := _constrain_room_position(position) - return Vector2( +func _snap_to_room_grid(position: Vector2, item: Dictionary) -> Vector2: + var constrained := _constrain_room_position(position, item) + var snapped := Vector2( roundf(constrained.x / ROOM_GRID_SIZE) * ROOM_GRID_SIZE, roundf(constrained.y / ROOM_GRID_SIZE) * ROOM_GRID_SIZE ) + return _constrain_room_position(snapped, item) func _is_point_inside_sprite(node: Sprite2D, worldPosition: Vector2) -> bool: if node.texture == null: