diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleGrid.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleGrid.cs index 6f61d4257..bb939ed1d 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleGrid.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleGrid.cs @@ -129,6 +129,26 @@ namespace AibisDream return IsValidPosition(x, y) && GetCellState(x, y) == GridCellState.Empty; } + public bool CanPlace(Vector2Int[] gridCells, out bool[] results) + { + results = new bool[gridCells.Length]; + for (int i = 0; i < gridCells.Length; i++) + { + // 先验证是否在有效范围内 + if (!IsValidPosition(gridCells[i].x, gridCells[i].y)) results[i] = false; + // 再验证是否为空 + else if (GetCellState(gridCells[i].x, gridCells[i].y) == GridCellState.Occupied) results[i] = false; + else results[i] = true; + } + + // 如果所有位置都可以放置,则返回true + for (int i = 0; i < results.Length; i++) + { + if (!results[i]) return false; + } + return true; + } + public bool WorldToGrid(Vector3 worldPos, out int gridX, out int gridY) { if (!_isInitialized) @@ -142,11 +162,11 @@ namespace AibisDream gridX = Mathf.FloorToInt(localPos.x / CellSize); gridY = Mathf.FloorToInt(localPos.y / CellSize); - // 检查是否在有效范围内 - if (!IsValidPosition(gridX, gridY)) - { - return false; - } + // TODO: 不检查是否在有效范围内,留给后续检查 + // if (!IsValidPosition(gridX, gridY)) + // { + // return false; + // } return true; } diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleGridEx.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleGridEx.cs new file mode 100644 index 000000000..359760bd3 --- /dev/null +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleGridEx.cs @@ -0,0 +1,76 @@ +using UnityEngine; + +namespace AibisDream +{ + public static class BlockPuzzleGridEx + { + public static SnapResult TrySnapToGrid(this IBlockPuzzleGrid grid, BlockShape shape) + { + // 预制错误结果作为返回值 + SnapResult result = new SnapResult + { + canSnap = false, + snapPosition = shape.transform.position, + gridX = 0, + gridY = 0, + distance = float.MaxValue + }; + + // 将Shape的世界坐标转换为网格坐标(可以是虚拟坐标) + bool inGrid = grid.WorldToGrid(shape.transform.position, out int gridX, out int gridY); + if (!inGrid) return result; + + // 将形状的所有格子转换为网格坐标 + // gridX, gridY 是形状的 (0, 0) 点在网格中的位置 + Vector2Int[] gridCells = ConvertShapeToGridCells(shape, gridX, gridY); + result.gridX = gridX; + result.gridY = gridY; + result.snapPosition = grid.GridToWorld(gridX, gridY); + + // 逐个坐标验证是否可以放置 + var canPlace = grid.CanPlace(gridCells, out bool[] results); + if (!canPlace) return result; + result.canSnap = true; + result.distance = Vector3.Distance(shape.transform.position, result.snapPosition); + + return result; + } + + /// + /// 将形状的相对坐标转换为网格坐标 + /// 已知形状的 (0, 0) 点在网格中的位置为 (gridX, gridY),将形状的其他格子坐标转换为网格坐标 + /// + /// 形状 + /// 网格X坐标(形状左下角位置,即形状的 (0, 0) 点对应的网格坐标) + /// 网格Y坐标(形状左下角位置,即形状的 (0, 0) 点对应的网格坐标) + /// 形状所有格子在网格中的坐标数组 + public static Vector2Int[] ConvertShapeToGridCells(IBlockShape shape, int gridX, int gridY) + { + if (shape == null) + { + return new Vector2Int[0]; + } + + Vector2Int[] blockPositions = shape.GetBlockPositions(); + if (blockPositions == null || blockPositions.Length == 0) + { + return new Vector2Int[0]; + } + + Vector2Int[] gridCells = new Vector2Int[blockPositions.Length]; + + for (int i = 0; i < blockPositions.Length; i++) + { + // 将形状的相对坐标转换为网格坐标 + // 形状的 (0, 0) 对应网格的 (gridX, gridY) + // 形状的 (blockPos.x, blockPos.y) 对应网格的 (gridX + blockPos.x, gridY + blockPos.y) + gridCells[i] = new Vector2Int( + gridX + blockPositions[i].x, + gridY + blockPositions[i].y + ); + } + + return gridCells; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleGridEx.cs.meta b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleGridEx.cs.meta new file mode 100644 index 000000000..49962fd0d --- /dev/null +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockPuzzleGridEx.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab4b77b210903a6428113bbc4e0fac09 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShapeDragger.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShapeDragger.cs index 6ccdce72c..158cfd95a 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShapeDragger.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShapeDragger.cs @@ -127,39 +127,18 @@ namespace AibisDream Vector3 mouseWorldPos = GetMouseWorldPosition(Input.mousePosition); Vector3 targetPosition = mouseWorldPos + _dragOffset; - // 检测是否靠近网格 if (targetGrid != null) { - bool canSnap = CheckSnapToGrid(targetPosition, out Vector3 snapPos, out int gridX, out int gridY); - - if (canSnap) + // 检查吸附可能 + var snapResult = targetGrid.TrySnapToGrid(_blockShape); + if (snapResult.canSnap) { - // 吸附到网格 - transform.position = snapPos; - _isSnapping = true; - _snapGridX = gridX; - _snapGridY = gridY; - _currentState = DragState.Snapping; - - // 重要:更新拖拽偏移量,使鼠标继续移动时能正确计算位置 - _dragOffset = snapPos - mouseWorldPos; - - // 检查是否可以放置 - bool canPlace = CanPlaceAt(gridX, gridY); - ShowSnapPreview(true, canPlace); - - OnSnap?.Invoke(_blockShape, snapPos, gridX, gridY); + // 可以吸附,保存吸附内容 } else { - // 自由拖拽 + // 没有吸附,自由拖拽 transform.position = targetPosition; - if (_isSnapping) - { - _isSnapping = false; - ShowSnapPreview(false, false); - OnUnsnap?.Invoke(_blockShape); - } _currentState = DragState.Dragging; } } diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Class/PlacementValidator.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Class/PlacementValidator.cs index f5342a87a..0285ef491 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Class/PlacementValidator.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Class/PlacementValidator.cs @@ -109,17 +109,19 @@ namespace AibisDream return occupiedCells.ToArray(); } + /// - /// 吸附检测结果 + /// 将形状的单个相对坐标转换为网格坐标 /// - public struct SnapResult + /// 形状内的X坐标(相对坐标,相对于形状左下角) + /// 形状内的Y坐标(相对坐标,相对于形状左下角) + /// 网格X坐标(形状左下角位置) + /// 网格Y坐标(形状左下角位置) + /// 对应的网格坐标 + public static Vector2Int ConvertShapeLocalToGrid(int shapeLocalX, int shapeLocalY, int gridX, int gridY) { - public bool canSnap; // 是否可以吸附 - public Vector3 snapPosition; // 吸附后的世界坐标位置 - public int gridX; // 吸附到的网格X坐标(形状左下角位置) - public int gridY; // 吸附到的网格Y坐标(形状左下角位置) - public float distance; // 到网格单元格的距离 + return new Vector2Int(gridX + shapeLocalX, gridY + shapeLocalY); } /// @@ -297,5 +299,17 @@ namespace AibisDream Overlap, // 与已有块重叠 InvalidPosition // 无效位置 } + + /// + /// 吸附检测结果 + /// + public struct SnapResult + { + public bool canSnap; // 是否可以吸附 + public Vector3 snapPosition; // 吸附后的世界坐标位置 + public int gridX; // 吸附到的网格X坐标(形状左下角位置) + public int gridY; // 吸附到的网格Y坐标(形状左下角位置) + public float distance; // 到网格单元格的距离 + } } diff --git a/Assets/Scripts/MiniGame/BlockPuzzle/Interface/IBlockPuzzleGrid.cs b/Assets/Scripts/MiniGame/BlockPuzzle/Interface/IBlockPuzzleGrid.cs index 33fef4914..3ed817396 100644 --- a/Assets/Scripts/MiniGame/BlockPuzzle/Interface/IBlockPuzzleGrid.cs +++ b/Assets/Scripts/MiniGame/BlockPuzzle/Interface/IBlockPuzzleGrid.cs @@ -84,6 +84,14 @@ namespace AibisDream /// 是否为空 bool IsCellEmpty(int x, int y); + /// + /// 检查指定位置是否可以放置 + /// + /// 网格坐标数组 + /// 结果集合 + /// 是否可以放置 + bool CanPlace(Vector2Int[] gridCells, out bool[] results); + /// /// 世界坐标转换为网格坐标 ///