199 lines
7.6 KiB
C#
199 lines
7.6 KiB
C#
using UnityEngine;
|
||
|
||
namespace AibisDream
|
||
{
|
||
/// <summary>
|
||
/// 放置验证器实现
|
||
/// 负责验证形状是否可以放置在指定位置
|
||
/// </summary>
|
||
public static class PlacementValidator
|
||
{
|
||
public static SnapResult CheckSnapToGrid(this IBlockPuzzleGrid grid, BlockShape shape)
|
||
{
|
||
// 预制错误结果作为返回值
|
||
SnapResult result = new SnapResult
|
||
{
|
||
canSnap = false,
|
||
snapPosition = shape.transform.position,
|
||
gridX = 0,
|
||
gridY = 0,
|
||
distance = float.MaxValue,
|
||
gridPositions = new Vector2Int[0]
|
||
};
|
||
|
||
// 将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);
|
||
result.gridPositions = gridCells;
|
||
|
||
// 如果不在网格中,则返回结果
|
||
if (!inGrid) return result;
|
||
// 验证形状的限制条件
|
||
if (!shape.CheckExtraLimits(new Vector2Int(gridX, gridY))) return result;
|
||
|
||
// 逐个坐标验证是否可以放置
|
||
var canPlace = grid.CanPlace(gridCells);
|
||
if (!canPlace) return result;
|
||
result.canSnap = true;
|
||
result.distance = Vector3.Distance(shape.transform.position, result.snapPosition);
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将形状的相对坐标转换为网格坐标
|
||
/// 已知形状的 (0, 0) 点在网格中的位置为 (gridX, gridY),将形状的其他格子坐标转换为网格坐标
|
||
/// 注意:GetBlockPositions() 返回的坐标是基于旋转后的pattern的索引坐标
|
||
/// 旋转后pattern的坐标轴方向可能改变,需要使用GetAxis()来处理坐标轴的翻转
|
||
/// </summary>
|
||
/// <param name="shape">形状</param>
|
||
/// <param name="gridX">网格X坐标(形状左下角位置,即形状的 (0, 0) 点对应的网格坐标)</param>
|
||
/// <param name="gridY">网格Y坐标(形状左下角位置,即形状的 (0, 0) 点对应的网格坐标)</param>
|
||
/// <returns>形状所有格子在网格中的坐标数组</returns>
|
||
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)
|
||
// 旋转后pattern的坐标轴方向可能改变,需要使用GetAxis()来处理坐标轴的翻转
|
||
// 例如:Rotate90时,X轴方向翻转(axis.x = -1),Y轴方向不变(axis.y = 1)
|
||
gridCells[i] = new Vector2Int(
|
||
gridX + blockPositions[i].x,
|
||
gridY + blockPositions[i].y
|
||
);
|
||
}
|
||
|
||
return gridCells;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 为吸附制作预览
|
||
/// </summary>
|
||
/// <param name="shapeCells">转换后的网格坐标组</param>
|
||
public static void ShowSnapPreview(this IBlockPuzzleGrid grid, SnapResult snapResult)
|
||
{
|
||
grid.ResetPreviewGrid();
|
||
// 如果可以吸附
|
||
if (snapResult.canSnap)
|
||
{
|
||
foreach (var cell in snapResult.gridPositions)
|
||
{
|
||
grid.SetPreviewCellState(cell.x, cell.y, GridCellState.PreviewVaild);
|
||
}
|
||
}
|
||
// 如果不能吸附
|
||
else
|
||
{
|
||
foreach (var cell in snapResult.gridPositions)
|
||
{
|
||
if (grid.IsValidPosition(cell.x, cell.y))
|
||
{
|
||
grid.SetPreviewCellState(cell.x, cell.y, GridCellState.PreviewInvalid);
|
||
}
|
||
}
|
||
}
|
||
|
||
grid.GridVisualizer.UpdateAllCellsVisual();
|
||
}
|
||
|
||
public static void TrySnapToGrid(this IBlockPuzzleGrid grid, BlockShape shape, Vector2Int rootCell)
|
||
{
|
||
// 判断RootCell的位置
|
||
var rootWorldPos = grid.GridToWorld(rootCell.x, rootCell.y);
|
||
// 已经判断是否可吸附,直接吸就好了
|
||
shape.transform.position = rootWorldPos;
|
||
// 设置网格状态
|
||
var shapeCells = ConvertShapeToGridCells(shape, rootCell.x, rootCell.y);
|
||
foreach (var cell in shapeCells)
|
||
{
|
||
grid.SetCellState(cell.x, cell.y, GridCellState.Occupied);
|
||
}
|
||
// 加入网格
|
||
var gridShapeData = new GridShapeData {
|
||
shapeKey = shape.ShapeName,
|
||
rootCell = rootCell,
|
||
rotationAngle = shape.CurrentRotation
|
||
};
|
||
grid.ShapeDict.Add(shape, gridShapeData);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试从网格中移除形状
|
||
/// </summary>
|
||
/// <param name="grid">网格</param>
|
||
/// <param name="shape">形状</param>
|
||
public static void TryRemoveShapeFromGrid(this IBlockPuzzleGrid grid, BlockShape shape)
|
||
{
|
||
if (grid.ShapeDict.TryGetValue(shape, out var gridShapeData))
|
||
{
|
||
var rootCell = gridShapeData.rootCell;
|
||
// 设置网格状态
|
||
var shapeCells = ConvertShapeToGridCells(shape, rootCell.x, rootCell.y);
|
||
foreach (var cell in shapeCells)
|
||
{
|
||
grid.SetCellState(cell.x, cell.y, GridCellState.Empty);
|
||
}
|
||
grid.ShapeDict.Remove(shape);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 放置验证结果
|
||
/// </summary>
|
||
public enum PlacementResult
|
||
{
|
||
Success, // 可以放置
|
||
OutOfBounds, // 超出边界
|
||
Overlap, // 与已有块重叠
|
||
InvalidPosition // 无效位置
|
||
}
|
||
|
||
/// <summary>
|
||
/// 吸附检测结果
|
||
/// </summary>
|
||
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); // 吸附后的根网格坐标
|
||
|
||
public static SnapResult RecycleResult => new()
|
||
{
|
||
canSnap = false,
|
||
snapPosition = Vector3.zero,
|
||
gridX = -10,
|
||
gridY = -10,
|
||
distance = float.MaxValue,
|
||
gridPositions = new Vector2Int[0]
|
||
};
|
||
};
|
||
}
|
||
|
||
|