Files
aibis-dream/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShape.cs
T
2025-12-16 20:08:11 +08:00

676 lines
25 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 System;
using System.Collections.Generic;
using AibisDream.Utility;
using UnityEngine;
using AibisDream.Framework;
namespace AibisDream
{
public class BlockShape : MonoBehaviour, IBlockShape
{
private SpriteRenderer[,] _cellRenderers;
// 模块数据
[Header("模块数据")]
[SerializeField] private BlockShapeData _blockShapeData;
private RotationAngle currentRotation;
// 接口实现
public ShapeDragger ShapeDragger { get; private set; }
public int ShapeId => _blockShapeData.shapeId;
public string ShapeName => _blockShapeData.shapeName;
public RotationAngle CurrentRotation => currentRotation;
public int Width => GetCurrentWidth();
public int Height => GetCurrentHeight();
public int BlockCount
{
get
{
var pattern = GetOriginalPattern();
if (pattern == null) return 0;
int count = 0;
for (int i = 0; i < _blockShapeData.width; i++)
{
for (int j = 0; j < _blockShapeData.height; j++)
{
if (pattern[i, j] != 0)
{
count++;
}
}
}
return count;
}
}
public void Init(BlockShapeData blockShapeData)
{
ShapeDragger = GetComponent<ShapeDragger>();
_blockShapeData = blockShapeData;
gameObject.name = blockShapeData.shapeName;
// 加载Sprite
LoadSprite();
// 更新碰撞体
UpdatePolygonCollider();
}
public Vector2Int[] GetBlockPositions()
{
var pattern = GetCurrentPattern();
if (pattern == null) return new Vector2Int[0];
int currentWidth = GetCurrentWidth();
int currentHeight = GetCurrentHeight();
// 获取根单元位置(原始pattern的(0,0)在旋转后pattern中的位置)
Vector2Int rootCell = GetCurrentRootCell();
List<Vector2Int> positions = new List<Vector2Int>();
for (int i = 0; i < currentWidth; i++)
{
for (int j = 0; j < currentHeight; j++)
{
if (pattern[i, j] != 0)
{
// 将pattern中的坐标转换为以根单元为(0,0)的坐标系统
// 原来在pattern中的位置(i, j),转换为新坐标系统:(i - rootX, j - rootY)
positions.Add(new Vector2Int(i - rootCell.x, j - rootCell.y));
}
}
}
return positions.ToArray();
}
public int[,] GetCurrentPattern()
{
var originalPattern = GetOriginalPattern();
if (originalPattern == null) return null;
if (currentRotation == RotationAngle.Rotate0)
{
return originalPattern;
}
return RotatePattern(originalPattern, currentRotation);
}
public int[,] GetOriginalPattern()
{
return CommonUtil.DeserializeIntArray(_blockShapeData.originalPatternStr);
}
/// <summary>
/// 获取当前旋转后根单元的位置
/// 根单元是指原始pattern的(0,0)位置在旋转后的pattern中的位置
/// Rotate0: 左下角 (0, 0)
/// Rotate90: 右下角 (height-1, 0)
/// Rotate180: 右上角 (width-1, height-1)
/// Rotate270: 左上角 (0, width-1)
/// </summary>
/// <returns>根单元在旋转后pattern中的坐标</returns>
private Vector2Int GetCurrentRootCell()
{
int width = _blockShapeData.width;
int height = _blockShapeData.height;
return currentRotation switch
{
RotationAngle.Rotate0 => new Vector2Int(0, 0), // 左下角
RotationAngle.Rotate90 => new Vector2Int(height - 1, 0), // 右下角(旋转后pattern尺寸为height x width
RotationAngle.Rotate180 => new Vector2Int(width - 1, height - 1), // 右上角
RotationAngle.Rotate270 => new Vector2Int(0, width - 1), // 左上角(旋转后pattern尺寸为height x width
_ => new Vector2Int(0, 0),
};
}
public bool HasBlockAt(int x, int y)
{
var pattern = GetCurrentPattern();
if (pattern == null) return false;
int currentWidth = GetCurrentWidth();
int currentHeight = GetCurrentHeight();
if (x < 0 || x >= currentWidth || y < 0 || y >= currentHeight)
return false;
return pattern[x, y] != 0;
}
/// <summary>
/// 根据网格情况计算出形状的物理中心位置
/// </summary>
/// <returns>形状的物理中心位置,z为0</returns>
public Vector3 GetCenterPosition()
{
var pattern = GetCurrentPattern();
if (pattern == null) return Vector3.zero;
int currentWidth = GetCurrentWidth();
int currentHeight = GetCurrentHeight();
Vector3 centerSum = Vector3.zero;
int blockCount = 0;
// 遍历所有格子,计算填充格子的中心位置
for (int i = 0; i < currentWidth; i++)
{
for (int j = 0; j < currentHeight; j++)
{
if (pattern[i, j] != 0)
{
// 计算格子(i,j)的物理中心位置
// 格子左下角在 (i * cellSize, j * cellSize)
// 格子中心在 (i * cellSize + cellSize/2, j * cellSize + cellSize/2)
float cellCenterX = i * _blockShapeData.cellSize * GetAxis().x;
float cellCenterY = j * _blockShapeData.cellSize * GetAxis().y;
centerSum += new Vector3(cellCenterX, cellCenterY, 0);
blockCount++;
}
}
}
// 如果没有填充的格子,返回零向量
if (blockCount == 0)
return Vector3.zero;
// 返回所有填充格子的平均位置(物理中心)
return centerSum / blockCount;
}
public void ResetRotation()
{
currentRotation = RotationAngle.Rotate0;
UpdateVisualRotation();
}
public void Rotate(RotationAngle angle)
{
currentRotation = angle;
UpdateVisualRotation();
}
public void RotateClockwise()
{
// 顺时针旋转90度
switch (currentRotation)
{
case RotationAngle.Rotate0:
currentRotation = RotationAngle.Rotate90;
break;
case RotationAngle.Rotate90:
currentRotation = RotationAngle.Rotate180;
break;
case RotationAngle.Rotate180:
currentRotation = RotationAngle.Rotate270;
break;
case RotationAngle.Rotate270:
currentRotation = RotationAngle.Rotate0;
break;
}
UpdateVisualRotation();
}
public Vector2Int GetAxis()
{
// 返回旋转后坐标轴的翻转方向
// 用于将旋转后pattern的索引坐标转换为网格坐标
// 注意:这里的旋转方向与Unity的transform.rotation一致(逆时针为正方向)
return currentRotation switch
{
RotationAngle.Rotate0 => new Vector2Int(1, 1),
RotationAngle.Rotate90 => new Vector2Int(-1, 1),
RotationAngle.Rotate180 => new Vector2Int(-1, -1),
RotationAngle.Rotate270 => new Vector2Int(1, -1),
_ => new Vector2Int(1, 1),
};
}
public void RotateCounterClockwise()
{
// 逆时针旋转90度
switch (currentRotation)
{
case RotationAngle.Rotate0:
currentRotation = RotationAngle.Rotate270;
break;
case RotationAngle.Rotate90:
currentRotation = RotationAngle.Rotate0;
break;
case RotationAngle.Rotate180:
currentRotation = RotationAngle.Rotate90;
break;
case RotationAngle.Rotate270:
currentRotation = RotationAngle.Rotate180;
break;
}
UpdateVisualRotation();
}
/// <summary>
/// 获取当前旋转后的宽度
/// </summary>
private int GetCurrentWidth()
{
// 90度和270度时,宽度和高度交换
return (currentRotation == RotationAngle.Rotate90 || currentRotation == RotationAngle.Rotate270)
? _blockShapeData.height
: _blockShapeData.width;
}
/// <summary>
/// 获取当前旋转后的高度
/// </summary>
private int GetCurrentHeight()
{
// 90度和270度时,宽度和高度交换
return (currentRotation == RotationAngle.Rotate90 || currentRotation == RotationAngle.Rotate270)
? _blockShapeData.width
: _blockShapeData.height;
}
/// <summary>
/// 旋转pattern数组
/// 注意:Unity的旋转是逆时针为正方向,所以这里的旋转方向与Unity的transform.rotation一致
/// </summary>
private int[,] RotatePattern(int[,] pattern, RotationAngle angle)
{
if (pattern == null) return null;
int originalWidth = pattern.GetLength(0);
int originalHeight = pattern.GetLength(1);
int[,] rotatedPattern;
switch (angle)
{
case RotationAngle.Rotate90:
// 逆时针90度(Unity正方向):new[i][j] = old[j][height-1-i]
rotatedPattern = new int[originalHeight, originalWidth];
for (int i = 0; i < originalHeight; i++)
{
for (int j = 0; j < originalWidth; j++)
{
rotatedPattern[i, j] = pattern[j, originalHeight - 1 - i];
}
}
break;
case RotationAngle.Rotate180:
// 180度:new[i][j] = old[width-1-i][height-1-j]
rotatedPattern = new int[originalWidth, originalHeight];
for (int i = 0; i < originalWidth; i++)
{
for (int j = 0; j < originalHeight; j++)
{
rotatedPattern[i, j] = pattern[originalWidth - 1 - i, originalHeight - 1 - j];
}
}
break;
case RotationAngle.Rotate270:
// 逆时针270度(顺时针90度):new[i][j] = old[width-1-j][i]
rotatedPattern = new int[originalHeight, originalWidth];
for (int i = 0; i < originalHeight; i++)
{
for (int j = 0; j < originalWidth; j++)
{
rotatedPattern[i, j] = pattern[originalWidth - 1 - j, i];
}
}
break;
default: // Rotate0
rotatedPattern = (int[,])pattern.Clone();
break;
}
return rotatedPattern;
}
public bool CheckExtraLimits(Vector2Int gridRootCell)
{
if (!_blockShapeData.extraLimits) return true;
if (currentRotation != _blockShapeData.rotationAngleLimit) return false;
if (gridRootCell != _blockShapeData.rootLimit) return false;
return true;
}
/// <summary>
/// 更新视觉旋转(更新transform的rotation
/// </summary>
private void UpdateVisualRotation()
{
float rotationZ = (float)currentRotation;
transform.rotation = Quaternion.Euler(0, 0, rotationZ);
}
private Sprite CreateDefaultSprite()
{
Texture2D texture = new Texture2D(1, 1);
texture.SetPixel(0, 0, Color.red);
texture.Apply();
return Sprite.Create(texture, new Rect(0, 0, 1, 1), new Vector2(0.5f, 0.5f), 1f);
}
// -------------------------------- 仅限编辑器下使用 --------------------------------
public void DrawShape()
{
transform.DestroyAllChildren();
_cellRenderers = null;
// 根据Width和Height,绘制Cells,Cell为SpriteRenderer
_cellRenderers = new SpriteRenderer[_blockShapeData.width, _blockShapeData.height];
var defaultSprite = CreateDefaultSprite();
for (int i = 0; i < _blockShapeData.width; i++)
{
for (int j = 0; j < _blockShapeData.height; j++)
{
var cell = new GameObject($"Cell_{i}_{j}");
var cellRenderer = cell.AddComponent<SpriteRenderer>();
cellRenderer.sprite = defaultSprite;
_cellRenderers[i, j] = cellRenderer;
cell.transform.parent = transform;
// 计算Cell应处的位置
var cellPosX = i * _blockShapeData.cellSize;
var cellPosY = j * _blockShapeData.cellSize;
cell.transform.localPosition = new Vector3(cellPosX, cellPosY, 0);
}
}
// 反序列化并处理
var pattern = CommonUtil.DeserializeIntArray(_blockShapeData.originalPatternStr);
// 先判断pattern的维度是否与width和height一致
if (pattern.GetLength(0) != _blockShapeData.width || pattern.GetLength(1) != _blockShapeData.height)
{
// 如果不一致,就不处理
Debug.LogWarning($"Pattern dimension mismatch: {pattern.GetLength(0)}x{pattern.GetLength(1)} != {_blockShapeData.width}x{_blockShapeData.height}");
return;
}
// 如果一致,就读取pattern,并设置对应的cell的SpriteRenderer是否激活
for (int i = 0; i < _blockShapeData.width; i++)
{
for (int j = 0; j < _blockShapeData.height; j++)
{
_cellRenderers[i, j].enabled = pattern[i, j] != 0;
}
}
LoadSprite();
// 更新碰撞体形状
UpdatePolygonCollider();
}
private void LoadSprite()
{
var renderer = GetComponent<SpriteRenderer>();
if (!string.IsNullOrEmpty(_blockShapeData.spritePath))
{
var sprite = ResourceKit.LoadAssetSync<Sprite>(_blockShapeData.spritePath);
renderer.sprite = sprite;
}
else
{
renderer.sprite = null;
}
}
public void SaveShape()
{
// 如果_cellRenderers不为空,则序列化并保存
if (_cellRenderers != null)
{
var pattern = new int[_blockShapeData.width, _blockShapeData.height];
for (int i = 0; i < _blockShapeData.width; i++)
{
for (int j = 0; j < _blockShapeData.height; j++)
{
pattern[i, j] = _cellRenderers[i, j].enabled ? 1 : 0;
}
}
Debug.Log($"SaveShape: {CommonUtil.SerializeIntArray(pattern)}");
_blockShapeData.originalPatternStr = CommonUtil.SerializeIntArray(pattern);
var renderer = GetComponent<SpriteRenderer>();
if (renderer != null && renderer.sprite != null)
{
Debug.Log($"SaveShape: {renderer.sprite.name}");
if (ResourceKit.GetAssetAddressableKey(renderer.sprite, out string spritePath))
{
_blockShapeData.spritePath = spritePath;
}
}
}
BlockPuzzleKit.SaveBlockShapeData(_blockShapeData, _blockShapeData.shapeName);
// 保存后更新碰撞体
UpdatePolygonCollider();
}
/// <summary>
/// 根据originalPattern更新PolygonCollider2D的形状
/// </summary>
public void UpdatePolygonCollider()
{
var pattern = GetOriginalPattern();
if (pattern == null) return;
// 获取或添加PolygonCollider2D组件
PolygonCollider2D polygonCollider = GetComponent<PolygonCollider2D>();
if (polygonCollider == null)
{
polygonCollider = gameObject.AddComponent<PolygonCollider2D>();
}
// 生成多边形顶点
List<Vector2> vertices = GeneratePolygonVertices(pattern);
if (vertices.Count > 0)
{
// 设置多边形路径
polygonCollider.pathCount = 1;
polygonCollider.SetPath(0, vertices.ToArray());
}
else
{
// 如果没有有效格子,清除路径
polygonCollider.pathCount = 0;
}
}
/// <summary>
/// 根据pattern生成多边形顶点
/// </summary>
private List<Vector2> GeneratePolygonVertices(int[,] pattern)
{
List<Vector2> vertices = new List<Vector2>();
if (pattern == null || _blockShapeData.width <= 0 || _blockShapeData.height <= 0 || _blockShapeData.cellSize <= 0)
return vertices;
// 生成精确的外轮廓(只包含边界点)
vertices = GenerateOutlineVertices(pattern);
return vertices;
}
/// <summary>
/// 生成精确的外轮廓顶点(只包含边界点)
/// 使用Marching Squares算法的简化版本
/// </summary>
private List<Vector2> GenerateOutlineVertices(int[,] pattern)
{
List<Vector2> outlineVertices = new List<Vector2>();
HashSet<Vector2Int> visitedPoints = new HashSet<Vector2Int>();
// 遍历所有格子,找到边界点
for (int i = 0; i <= _blockShapeData.width; i++)
{
for (int j = 0; j <= _blockShapeData.height; j++)
{
// 检查这个角点是否在边界上
bool isBoundaryCorner = IsBoundaryCorner(pattern, i, j);
if (isBoundaryCorner)
{
// 计算角点的本地坐标
// cornerX 和 cornerY 是角点索引(0到width/height
// 需要转换为格子的角点坐标
// 角点 (i, j) 对应格子 (i-0.5, j-0.5) 的角点位置
float x = (i - 0.5f) * _blockShapeData.cellSize;
float y = (j - 0.5f) * _blockShapeData.cellSize;
Vector2Int key = new Vector2Int(Mathf.RoundToInt(x * 100), Mathf.RoundToInt(y * 100));
if (!visitedPoints.Contains(key))
{
outlineVertices.Add(new Vector2(x, y));
visitedPoints.Add(key);
}
}
}
}
// 如果没有找到边界点,使用简单包围盒
if (outlineVertices.Count == 0)
{
return GenerateBoundingBox(pattern);
}
// 对顶点进行排序,形成闭合多边形
return OrderVerticesForPolygon(outlineVertices);
}
/// <summary>
/// 检查角点是否在边界上
/// </summary>
private bool IsBoundaryCorner(int[,] pattern, int cornerX, int cornerY)
{
// 角点位于四个格子的交汇处
// 检查周围四个格子的状态
bool topLeft = (cornerX > 0 && cornerY > 0) ? pattern[cornerX - 1, cornerY - 1] != 0 : false;
bool topRight = (cornerX < _blockShapeData.width && cornerY > 0) ? pattern[cornerX, cornerY - 1] != 0 : false;
bool bottomLeft = (cornerX > 0 && cornerY < _blockShapeData.height) ? pattern[cornerX - 1, cornerY] != 0 : false;
bool bottomRight = (cornerX < _blockShapeData.width && cornerY < _blockShapeData.height) ? pattern[cornerX, cornerY] != 0 : false;
// 如果四个格子状态不一致,说明这个角点在边界上
int filledCount = (topLeft ? 1 : 0) + (topRight ? 1 : 0) +
(bottomLeft ? 1 : 0) + (bottomRight ? 1 : 0);
// 边界角点:不是全部填充也不是全部空
return filledCount > 0 && filledCount < 4;
}
/// <summary>
/// 生成简单的包围盒(备用方法)
/// </summary>
private List<Vector2> GenerateBoundingBox(int[,] pattern)
{
List<Vector2> vertices = new List<Vector2>();
// 找到有效格子的边界
int minX = _blockShapeData.width, maxX = -1, minY = _blockShapeData.height, maxY = -1;
bool hasValidCell = false;
for (int i = 0; i < _blockShapeData.width; i++)
{
for (int j = 0; j < _blockShapeData.height; j++)
{
if (pattern[i, j] != 0)
{
hasValidCell = true;
minX = Mathf.Min(minX, i);
maxX = Mathf.Max(maxX, i);
minY = Mathf.Min(minY, j);
maxY = Mathf.Max(maxY, j);
}
}
}
if (!hasValidCell)
return vertices;
// 计算包围盒的四个角点(本地坐标)
float halfSize = _blockShapeData.cellSize / 2f;
float minXWorld = minX * _blockShapeData.cellSize - _blockShapeData.width * _blockShapeData.cellSize / 2f - halfSize;
float maxXWorld = (maxX + 1) * _blockShapeData.cellSize - _blockShapeData.width * _blockShapeData.cellSize / 2f + halfSize;
float minYWorld = minY * _blockShapeData.cellSize - _blockShapeData.height * _blockShapeData.cellSize / 2f - halfSize;
float maxYWorld = (maxY + 1) * _blockShapeData.cellSize - _blockShapeData.height * _blockShapeData.cellSize / 2f + halfSize;
// 按逆时针顺序添加顶点(Unity的PolygonCollider2D要求)
vertices.Add(new Vector2(minXWorld, minYWorld)); // 左下
vertices.Add(new Vector2(maxXWorld, minYWorld)); // 右下
vertices.Add(new Vector2(maxXWorld, maxYWorld)); // 右上
vertices.Add(new Vector2(minXWorld, maxYWorld)); // 左上
return vertices;
}
/// <summary>
/// 对顶点进行排序,形成闭合多边形
/// </summary>
private List<Vector2> OrderVerticesForPolygon(List<Vector2> vertices)
{
if (vertices.Count <= 2)
return vertices;
// 找到中心点
Vector2 center = Vector2.zero;
foreach (var v in vertices)
{
center += v;
}
center /= vertices.Count;
// 按角度排序(相对于中心点)
vertices.Sort((a, b) =>
{
float angleA = Mathf.Atan2(a.y - center.y, a.x - center.x);
float angleB = Mathf.Atan2(b.y - center.y, b.x - center.x);
if (angleA.CompareTo(angleB) != 0)
{
return angleA.CompareTo(angleB);
}
return (a - center).sqrMagnitude.CompareTo((b - center).sqrMagnitude);
});
return vertices;
}
public void SnapToGrid()
{
var grid = FindFirstObjectByType<BlockPuzzleGrid>();
if (grid == null)
{
Debug.LogWarning("无法找到BlockPuzzleGrid");
return;
}
var snapResult = grid.CheckSnapToGrid(this);
if (snapResult.canSnap)
{
grid.TrySnapToGrid(this, snapResult.RootCell);
}
else
{
Debug.LogWarning("无法吸附到网格");
}
}
public void DeleteShape()
{
var grid = FindFirstObjectByType<BlockPuzzleGrid>();
if (grid != null)
{
grid.ShapeDict.Remove(this);
DestroyImmediate(gameObject);
}
}
}
}