Files
aibis-dream/Assets/Scripts/MiniGame/BlockPuzzle/Class/PlacementValidator.cs
T
2026-02-04 23:53:16 +08:00

200 lines
7.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
isDraggable = shape.GetComponent<ShapeDragger>().IsDraggable
};
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]
};
};
}