using System.Collections.Generic; using UnityEngine; namespace AibisDream { /// /// 放置验证器实现 /// 负责验证形状是否可以放置在指定位置 /// public static class PlacementValidator { /// /// 检查形状是否可以放置在指定位置 /// /// 网格系统 /// 要放置的形状 /// 网格X坐标(形状左下角位置) /// 网格Y坐标(形状左下角位置) /// 放置验证结果 public static PlacementResult CanPlace(IBlockPuzzleGrid grid, IBlockShape shape, int gridX, int gridY) { if (grid == null || shape == null) { return PlacementResult.InvalidPosition; } // 获取形状的所有格子位置(相对坐标,基于形状的本地坐标系) Vector2Int[] blockPositions = shape.GetBlockPositions(); if (blockPositions == null || blockPositions.Length == 0) { return PlacementResult.InvalidPosition; } // 形状的坐标系统: // - GetBlockPositions() 返回的坐标中,(0, 0) 是形状的左下角 // - pattern[i, j] 中,i 是 x(从左到右),j 是 y(从下到上,j=0 是底部) // - gridX, gridY 是形状左下角在网格中的位置 // - 需要将形状的相对坐标转换为网格坐标 foreach (var blockPos in blockPositions) { // 将形状的相对坐标转换为网格坐标 // 形状的 (0, 0) 对应网格的 (gridX, gridY) // 形状的 (i, j) 对应网格的 (gridX + i, gridY + j) int gridCellX = gridX + blockPos.x; int gridCellY = gridY + blockPos.y; // 检查是否超出边界 if (!grid.IsValidPosition(gridCellX, gridCellY)) { return PlacementResult.OutOfBounds; } // 检查是否与已有块重叠 if (!grid.IsCellEmpty(gridCellX, gridCellY)) { return PlacementResult.Overlap; } } return PlacementResult.Success; } /// /// 检查形状是否可以放置在指定位置(详细版本) /// /// 网格系统 /// 要放置的形状 /// 网格X坐标(形状左下角位置) /// 网格Y坐标(形状左下角位置) /// 失败原因(如果返回false) /// 是否可以放置 public static bool CanPlaceDetailed(IBlockPuzzleGrid grid, IBlockShape shape, int gridX, int gridY, out PlacementResult failureReason) { failureReason = CanPlace(grid, shape, gridX, gridY); return failureReason == PlacementResult.Success; } /// /// 获取形状在指定位置的所有占用格子坐标 /// /// 形状 /// 网格X坐标(形状左下角位置) /// 网格Y坐标(形状左下角位置) /// 占用格子的网格坐标数组 public static (int x, int y)[] GetOccupiedCells(IBlockShape shape, int gridX, int gridY) { if (shape == null) { return new (int x, int y)[0]; } Vector2Int[] blockPositions = shape.GetBlockPositions(); if (blockPositions == null || blockPositions.Length == 0) { return new (int x, int y)[0]; } List<(int x, int y)> occupiedCells = new List<(int x, int y)>(); foreach (var blockPos in blockPositions) { // 将形状的相对坐标转换为网格坐标(左下角为基准) // 形状的 (0, 0) 对应网格的 (gridX, gridY) // 形状的 (i, j) 对应网格的 (gridX + i, gridY + j) int gridCellX = gridX + blockPos.x; int gridCellY = gridY + blockPos.y; occupiedCells.Add((gridCellX, gridCellY)); } return occupiedCells.ToArray(); } /// /// 将形状的单个相对坐标转换为网格坐标 /// /// 形状内的X坐标(相对坐标,相对于形状左下角) /// 形状内的Y坐标(相对坐标,相对于形状左下角) /// 网格X坐标(形状左下角位置) /// 网格Y坐标(形状左下角位置) /// 对应的网格坐标 public static Vector2Int ConvertShapeLocalToGrid(int shapeLocalX, int shapeLocalY, int gridX, int gridY) { return new Vector2Int(gridX + shapeLocalX, gridY + shapeLocalY); } /// /// 检查世界坐标是否可以吸附到网格 /// /// 网格系统 /// 世界坐标位置 /// 吸附距离阈值 /// 吸附检测结果 public static SnapResult CheckSnapToGrid(IBlockPuzzleGrid grid, Vector3 worldPos, float snapDistance) { SnapResult result = new SnapResult { canSnap = false, snapPosition = worldPos, gridX = 0, gridY = 0, distance = float.MaxValue }; if (grid == null) { return result; } // 将世界坐标转换为网格坐标 // 注意:WorldToGrid 需要世界坐标在网格范围内才能返回true // 但我们需要检查所有附近的网格单元格,不仅仅是当前所在的单元格 // 所以先尝试直接转换,如果失败则查找最近的网格单元格 int tempGridX, tempGridY; bool inGrid = grid.WorldToGrid(worldPos, out tempGridX, out tempGridY); if (!inGrid) { // 如果不在网格内,尝试查找最近的网格单元格 // 计算最近的网格坐标 Vector3 localPos = worldPos - grid.Origin; tempGridX = Mathf.FloorToInt(localPos.x / grid.CellSize); tempGridY = Mathf.FloorToInt(localPos.y / grid.CellSize); // 检查是否在有效范围内 if (!grid.IsValidPosition(tempGridX, tempGridY)) { return result; } } // 获取网格单元格中心的世界坐标 Vector3 gridCenter = grid.GridToWorld(tempGridX, tempGridY); // 计算距离 float distance = Vector3.Distance(worldPos, gridCenter); if (distance <= snapDistance) { // 计算形状应该放置的网格位置 // 形状左下角对齐到网格单元格左下角 result.gridX = tempGridX; result.gridY = tempGridY; // 计算形状左下角的世界坐标 // 网格单元格左下角的世界坐标 = Origin + (gridX * CellSize, gridY * CellSize) Vector3 gridBottomLeft = grid.Origin + new Vector3( tempGridX * grid.CellSize, tempGridY * grid.CellSize, 0 ); result.snapPosition = gridBottomLeft; result.distance = distance; result.canSnap = true; } return result; } /// /// 检查世界坐标是否可以吸附到网格(简化版本,只返回是否可以吸附和网格坐标) /// /// 网格系统 /// 世界坐标位置 /// 吸附距离阈值 /// 吸附到的网格X坐标(形状左下角位置) /// 吸附到的网格Y坐标(形状左下角位置) /// 是否可以吸附 public static bool CheckSnapToGrid(IBlockPuzzleGrid grid, Vector3 worldPos, float snapDistance, out int gridX, out int gridY) { gridX = 0; gridY = 0; SnapResult result = CheckSnapToGrid(grid, worldPos, snapDistance); if (result.canSnap) { gridX = result.gridX; gridY = result.gridY; return true; } return false; } /// /// 检查世界坐标是否可以吸附到网格(完整版本,返回吸附位置和网格坐标) /// /// 网格系统 /// 世界坐标位置 /// 吸附距离阈值 /// 吸附后的世界坐标位置 /// 吸附到的网格X坐标(形状左下角位置) /// 吸附到的网格Y坐标(形状左下角位置) /// 是否可以吸附 public static bool CheckSnapToGrid(IBlockPuzzleGrid grid, Vector3 worldPos, float snapDistance, out Vector3 snapPos, out int gridX, out int gridY) { snapPos = worldPos; gridX = 0; gridY = 0; SnapResult result = CheckSnapToGrid(grid, worldPos, snapDistance); if (result.canSnap) { snapPos = result.snapPosition; gridX = result.gridX; gridY = result.gridY; return true; } return false; } /// /// 查找最近的网格单元格(即使不在吸附距离内) /// /// 网格系统 /// 世界坐标位置 /// 最近的网格X坐标 /// 最近的网格Y坐标 /// 是否找到有效的网格单元格 public static bool FindNearestGridCell(IBlockPuzzleGrid grid, Vector3 worldPos, out int gridX, out int gridY) { gridX = 0; gridY = 0; if (grid == null) { return false; } // 先尝试直接转换 if (grid.WorldToGrid(worldPos, out gridX, out gridY)) { return true; } // 如果不在网格内,计算最近的网格坐标 Vector3 localPos = worldPos - grid.Origin; gridX = Mathf.FloorToInt(localPos.x / grid.CellSize); gridY = Mathf.FloorToInt(localPos.y / grid.CellSize); // 限制在有效范围内 gridX = Mathf.Clamp(gridX, 0, grid.Width - 1); gridY = Mathf.Clamp(gridY, 0, grid.Height - 1); return grid.IsValidPosition(gridX, gridY); } } /// /// 放置验证结果 /// public enum PlacementResult { Success, // 可以放置 OutOfBounds, // 超出边界 Overlap, // 与已有块重叠 InvalidPosition // 无效位置 } /// /// 吸附检测结果 /// public struct SnapResult { public bool canSnap; // 是否可以吸附 public Vector3 snapPosition; // 吸附后的世界坐标位置 public int gridX; // 吸附到的网格X坐标(形状左下角位置) public int gridY; // 吸附到的网格Y坐标(形状左下角位置) public float distance; // 到网格单元格的距离 public Vector2Int[] gridPositions; // 吸附后 public Vector2Int RootCell => new Vector2Int(gridX, gridY); // 吸附后的根网格坐标 } }