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);
+
///
/// 世界坐标转换为网格坐标
///