381 lines
13 KiB
C#
381 lines
13 KiB
C#
using System.Collections.Generic;
|
||
using AibisDream.Utility;
|
||
using UnityEngine;
|
||
|
||
namespace AibisDream
|
||
{
|
||
public class BlockShape : MonoBehaviour, IBlockShape
|
||
{
|
||
private SpriteRenderer[,] _cellRenderers;
|
||
|
||
// 模块数据
|
||
[Header("模块数据")]
|
||
[SerializeField] private int shapeId;
|
||
[SerializeField] private string shapeName;
|
||
[SerializeField] private RotationAngle currentRotation;
|
||
[SerializeField] private int width;
|
||
[SerializeField] private int height;
|
||
[SerializeField] private float cellSize;
|
||
[SerializeField] private string originalPatternStr;
|
||
|
||
// 接口实现
|
||
public int ShapeId => shapeId;
|
||
public string ShapeName => shapeName;
|
||
public RotationAngle CurrentRotation => currentRotation;
|
||
public int Width => width;
|
||
public int Height => height;
|
||
public int BlockCount
|
||
{
|
||
get
|
||
{
|
||
var pattern = GetOriginalPattern();
|
||
if (pattern == null) return 0;
|
||
|
||
int count = 0;
|
||
for (int i = 0; i < width; i++)
|
||
{
|
||
for (int j = 0; j < height; j++)
|
||
{
|
||
if (pattern[i, j] != 0)
|
||
{
|
||
count++;
|
||
}
|
||
}
|
||
}
|
||
return count;
|
||
}
|
||
}
|
||
|
||
public Vector2Int[] GetBlockPositions()
|
||
{
|
||
var pattern = GetOriginalPattern();
|
||
if (pattern == null) return new Vector2Int[0];
|
||
|
||
List<Vector2Int> positions = new List<Vector2Int>();
|
||
|
||
for (int i = 0; i < width; i++)
|
||
{
|
||
for (int j = 0; j < height; j++)
|
||
{
|
||
if (pattern[i, j] != 0)
|
||
{
|
||
positions.Add(new Vector2Int(i, j));
|
||
}
|
||
}
|
||
}
|
||
|
||
return positions.ToArray();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取单元格渲染器数组(供拖拽组件使用)
|
||
/// </summary>
|
||
public SpriteRenderer[,] GetCellRenderers()
|
||
{
|
||
return _cellRenderers;
|
||
}
|
||
|
||
public int[,] GetCurrentPattern()
|
||
{
|
||
throw new System.NotImplementedException();
|
||
}
|
||
|
||
public int[,] GetOriginalPattern()
|
||
{
|
||
return CommonUtil.DeserializeIntArray(originalPatternStr);
|
||
}
|
||
|
||
public bool HasBlockAt(int x, int y)
|
||
{
|
||
throw new System.NotImplementedException();
|
||
}
|
||
|
||
public void ResetRotation()
|
||
{
|
||
throw new System.NotImplementedException();
|
||
}
|
||
|
||
public void Rotate(RotationAngle angle)
|
||
{
|
||
throw new System.NotImplementedException();
|
||
}
|
||
|
||
public void RotateClockwise()
|
||
{
|
||
throw new System.NotImplementedException();
|
||
}
|
||
|
||
public void RotateCounterClockwise()
|
||
{
|
||
throw new System.NotImplementedException();
|
||
}
|
||
|
||
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[width, height];
|
||
var defaultSprite = CreateDefaultSprite();
|
||
for (int i = 0; i < width; i++)
|
||
{
|
||
for (int j = 0; j < 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 * cellSize;
|
||
var cellPosY = j * cellSize;
|
||
cell.transform.localPosition = new Vector3(cellPosX, cellPosY, 0);
|
||
}
|
||
}
|
||
|
||
// 反序列化并处理
|
||
var pattern = CommonUtil.DeserializeIntArray(originalPatternStr);
|
||
// 先判断pattern的维度是否与width和height一致
|
||
if (pattern.GetLength(0) != width || pattern.GetLength(1) != height)
|
||
{
|
||
// 如果不一致,就不处理
|
||
Debug.LogWarning($"Pattern dimension mismatch: {pattern.GetLength(0)}x{pattern.GetLength(1)} != {width}x{height}");
|
||
return;
|
||
}
|
||
// 如果一致,就读取pattern,并设置对应的cell的SpriteRenderer是否激活
|
||
for (int i = 0; i < width; i++)
|
||
{
|
||
for (int j = 0; j < height; j++)
|
||
{
|
||
_cellRenderers[i, j].enabled = pattern[i, j] != 0;
|
||
}
|
||
}
|
||
|
||
// 更新碰撞体形状
|
||
UpdatePolygonCollider();
|
||
}
|
||
|
||
public void SaveShape()
|
||
{
|
||
// 如果_cellRenderers不为空,则序列化并保存
|
||
if (_cellRenderers != null)
|
||
{
|
||
var pattern = new int[width, height];
|
||
for (int i = 0; i < width; i++)
|
||
{
|
||
for (int j = 0; j < height; j++)
|
||
{
|
||
pattern[i, j] = _cellRenderers[i, j].enabled ? 1 : 0;
|
||
}
|
||
}
|
||
Debug.Log($"SaveShape: {CommonUtil.SerializeIntArray(pattern)}");
|
||
originalPatternStr = CommonUtil.SerializeIntArray(pattern);
|
||
|
||
// 保存后更新碰撞体
|
||
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 || width <= 0 || height <= 0 || 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 <= width; i++)
|
||
{
|
||
for (int j = 0; j <= 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) * cellSize;
|
||
float y = (j - 0.5f) * 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 < width && cornerY > 0) ? pattern[cornerX, cornerY - 1] != 0 : false;
|
||
bool bottomLeft = (cornerX > 0 && cornerY < height) ? pattern[cornerX - 1, cornerY] != 0 : false;
|
||
bool bottomRight = (cornerX < width && cornerY < 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 = width, maxX = -1, minY = height, maxY = -1;
|
||
bool hasValidCell = false;
|
||
|
||
for (int i = 0; i < width; i++)
|
||
{
|
||
for (int j = 0; j < 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 = cellSize / 2f;
|
||
float minXWorld = minX * cellSize - width * cellSize / 2f - halfSize;
|
||
float maxXWorld = (maxX + 1) * cellSize - width * cellSize / 2f + halfSize;
|
||
float minYWorld = minY * cellSize - height * cellSize / 2f - halfSize;
|
||
float maxYWorld = (maxY + 1) * cellSize - height * 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;
|
||
}
|
||
}
|
||
} |