Files
aibis-dream/Assets/Scripts/MiniGame/BlockPuzzle/Class/BlockShape.cs
T
2026-02-03 20:23:17 +08:00

593 lines
22 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 UnityEngine.Rendering;
using AibisDream.Framework;
namespace AibisDream
{
public class BlockShape : MonoBehaviour, IBlockShape
{
private const string EditorPreviewContainerName = "EditorPreview";
private const string VisualInstanceName = "Visual";
private SpriteRenderer[,] _cellRenderers;
private GameObject _visualInstance;
private SortingGroup _sortingGroup;
public SortingGroup SortingGroup => _sortingGroup;
// 模块数据
[Header("模块数据")]
[SerializeField] private BlockShapeData _blockShapeData;
private RotationAngle currentRotation;
// 接口实现
public BlockShapeData BlockShapeData => _blockShapeData;
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;
// 加载视觉Prefab
LoadVisualPrefab();
// 更新碰撞体
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 Vector3 GetGlobalCenterPosition()
{
return transform.position + GetCenterPosition();
}
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()
{
// 只销毁 EditorPreview 容器,保留 visualInstance
var editorPreview = transform.Find(EditorPreviewContainerName);
if (editorPreview != null)
{
DestroyImmediate(editorPreview.gameObject);
}
_cellRenderers = null;
// 创建 EditorPreview 容器
var container = new GameObject(EditorPreviewContainerName);
container.transform.SetParent(transform);
container.transform.localPosition = Vector3.zero;
container.transform.localRotation = Quaternion.identity;
container.transform.localScale = Vector3.one;
// 在容器下创建 CellRenderers
_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.SetParent(container.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}");
}
else
{
// 如果一致,就读取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;
}
}
}
LoadVisualPrefab();
// 更新碰撞体形状
UpdatePolygonCollider();
}
private void LoadVisualPrefab()
{
// 销毁旧的视觉实例
if (_visualInstance != null)
{
DestroyImmediate(_visualInstance);
_visualInstance = null;
_sortingGroup = null;
}
if (!string.IsNullOrEmpty(_blockShapeData.prefabPath))
{
var prefab = ResourceKit.LoadAssetSync<GameObject>(_blockShapeData.prefabPath);
if (prefab != null)
{
_visualInstance = Instantiate(prefab, transform);
_visualInstance.name = VisualInstanceName;
// 保持 Prefab 中设置的 localPosition,不强制重置
// 这样美术可以在 Prefab 中直接调整位置偏移
_visualInstance.transform.localRotation = Quaternion.identity;
// 获取或添加 SortingGroup
_sortingGroup = _visualInstance.GetComponent<SortingGroup>();
if (_sortingGroup == null)
{
_sortingGroup = _visualInstance.AddComponent<SortingGroup>();
}
}
}
}
#if UNITY_EDITOR
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);
}
// 保存 visualInstance 对应的 prefab 路径
if (_visualInstance != null)
{
// 尝试获取 prefab 的 addressable key
var prefabSource = UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(_visualInstance);
if (prefabSource != null && ResourceKit.GetAssetAddressableKey(prefabSource, out string prefabPath))
{
Debug.Log($"SaveShape prefabPath: {prefabPath}");
_blockShapeData.prefabPath = prefabPath;
}
}
BlockPuzzleKit.SaveBlockShapeData(_blockShapeData, _blockShapeData.shapeName);
// 保存后更新碰撞体
UpdatePolygonCollider();
}
public void LoadShape()
{
// 根据shapeName加载shape
if (BlockPuzzleKit.TryLoadBlockShapeData(_blockShapeData.shapeName, out var shapeData))
{
_blockShapeData = shapeData;
DrawShape();
}
else
{
Debug.LogWarning($"LoadShape: {_blockShapeData.shapeName} not found");
}
}
#endif
/// <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>();
}
// 使用GridDrawer生成多边形顶点
Vector2[] gridVertices = BlockPuzzleKit.GridToPolygonVertices(pattern);
if (gridVertices != null && gridVertices.Length > 0)
{
// 转换坐标:GridDrawer使用边长2,需要转换为BlockShape的cellSize
// GridDrawer坐标系统:格子(i,j)的中心为(i*2, j*2),角点为(i*2-1, j*2-1)等
// BlockShape坐标系统:格子(i,j)的中心为((i+0.5)*cellSize, (j+0.5)*cellSize)
// 转换公式:BlockShape坐标 = GridDrawer坐标 * (cellSize / 2)
float scale = _blockShapeData.cellSize / 2f;
List<Vector2> vertices = new List<Vector2>();
for (int i = 0; i < gridVertices.Length; i++)
{
vertices.Add(new Vector2(gridVertices[i].x * scale, gridVertices[i].y * scale));
}
// 设置多边形路径
polygonCollider.pathCount = 1;
polygonCollider.SetPath(0, vertices.ToArray());
}
else
{
// 如果没有有效格子,清除路径
polygonCollider.pathCount = 0;
}
}
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);
}
}
}
}