593 lines
22 KiB
C#
593 lines
22 KiB
C#
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;
|
||
LoadVisualPrefab();
|
||
ShapeDragger.IdleLayerOrder = _sortingGroup != null ? _sortingGroup.sortingOrder : 0;
|
||
// 更新碰撞体
|
||
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();
|
||
ShapeDragger.IdleLayerOrder = _sortingGroup != null ? _sortingGroup.sortingOrder : 0;
|
||
// 更新碰撞体形状
|
||
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 && EditorKit.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);
|
||
}
|
||
}
|
||
}
|
||
} |