fix: improve personal space layout editing

This commit is contained in:
ANG-Server
2026-07-22 14:14:43 +08:00
parent d60444d2eb
commit ce896fc1e0

View File

@@ -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
_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: