using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using AibisDream.Utility;
using AibisDream.Framework;
using UnityEngine;
using Yarn.Unity;
namespace AibisDream
{
public static class BlockPuzzleKit
{
///
/// 保存单个 BlockShapeData 到文件,确保 Key 唯一(如果 Key 已存在则更新,不存在则添加)
///
/// 要保存的数据
/// 数据的键(用于字典存储)
/// 保存路径,如果为空则使用默认路径
public static void SaveBlockShapeData(BlockShapeData data, string key, string path = null)
{
if (string.IsNullOrEmpty(path))
{
path = ConstRef.BlockShapeDataPath;
}
// 确保目录存在
string directory = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 读取现有字典(如果文件存在)
Dictionary dataDict;
if (File.Exists(path))
{
try
{
dataDict = JsonUtil.ReadBeanDict(path) ?? new Dictionary();
}
catch (Exception e)
{
Debug.LogWarning($"读取现有 BlockShapeData 文件失败,将创建新文件: {e.Message}");
dataDict = new Dictionary();
}
}
else
{
dataDict = new Dictionary();
}
// 更新或添加数据(确保 Key 唯一)
dataDict[key] = data;
// 保存整个字典
JsonUtil.SaveBean(dataDict, path);
}
///
/// 从文件加载单个 BlockShapeData
///
/// 数据的键
/// 文件路径,如果为空则使用默认路径
/// 加载的数据
/// 是否加载成功
public static bool TryLoadBlockShapeData(string key, out BlockShapeData data, string path = null)
{
if (string.IsNullOrEmpty(path))
{
path = ConstRef.BlockShapeDataPath;
}
try
{
if (!File.Exists(path))
{
data = default;
return false;
}
var dict = JsonUtil.ReadBeanDict(path);
var infoDict = JsonUtil.ReadBeanDict(ConstRef.BlockShapeInfoPath);
if (dict != null && dict.ContainsKey(key))
{
data = dict[key];
data.blockShapeInfo = infoDict[key];
return true;
}
data = default;
return false;
}
catch (Exception e)
{
Debug.LogError($"加载 BlockShapeData 失败: {e.Message}");
data = default;
return false;
}
}
///
/// 批量从文件加载 BlockShapeData
///
/// 要加载的数据键数组
/// 文件路径,如果为空则使用默认路径
/// 成功加载的数据列表
public static Dictionary LoadBlockShapeDataBatch(string[] keys, string path = null)
{
var result = new Dictionary();
if (keys == null || keys.Length == 0)
{
return result;
}
if (string.IsNullOrEmpty(path))
{
path = ConstRef.BlockShapeDataPath;
}
try
{
if (!File.Exists(path))
{
Debug.LogWarning($"BlockShapeData 文件不存在: {path}");
return result;
}
var dict = JsonUtil.ReadBeanDict(path);
var infoDict = JsonUtil.ReadBeanDict(ConstRef.BlockShapeInfoPath);
if (dict == null)
{
Debug.LogWarning($"读取 BlockShapeData 字典失败: {path}");
return result;
}
foreach (string key in keys)
{
if (string.IsNullOrEmpty(key))
continue;
if (dict.ContainsKey(key))
{
BlockShapeData data = dict[key];
// 尝试加载对应的 BlockShapeInfo
if (infoDict != null && infoDict.ContainsKey(key))
{
data.blockShapeInfo = infoDict[key];
}
result.Add(key, data);
}
else
{
Debug.LogWarning($"未找到键为 '{key}' 的 BlockShapeData");
}
}
}
catch (Exception e)
{
Debug.LogError($"批量加载 BlockShapeData 失败: {e.Message}");
}
return result;
}
///
/// 从文件加载单个 BlockPuzzleData
///
/// 数据的键
/// 加载的数据
/// 是否加载成功
public static bool TryLoadBlockPuzzleData(string key, out BlockPuzzleData data)
{
var path = ConstRef.BlockPuzzleDataPath;
// 加载配置
try
{
if (!File.Exists(path))
{
data = default;
return false;
}
var dict = JsonUtil.ReadBeanDict(path);
if (dict != null && dict.ContainsKey(key))
{
data = dict[key];
return true;
}
data = default;
return false;
}
catch (Exception e)
{
Debug.LogError($"加载 BlockPuzzleData 失败: {e.Message}");
data = default;
return false;
}
}
///
/// 保存单个 BlockPuzzleData 到文件,确保 Key 唯一(如果 Key 已存在则更新,不存在则添加)
///
/// 要保存的数据
/// 数据的键(用于字典存储)
/// 保存路径,如果为空则使用默认路径
public static void SaveBlockPuzzleData(BlockPuzzleData data, string key, string path = null)
{
if (string.IsNullOrEmpty(path))
{
path = ConstRef.BlockPuzzleDataPath;
}
// 确保目录存在
string directory = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// 读取现有字典(如果文件存在)
Dictionary dataDict;
if (File.Exists(path))
{
try
{
dataDict = JsonUtil.ReadBeanDict(path) ?? new Dictionary();
}
catch (Exception e)
{
Debug.LogWarning($"读取现有 BlockPuzzleData 文件失败,将创建新文件: {e.Message}");
dataDict = new Dictionary();
}
}
else
{
dataDict = new Dictionary();
}
// 更新或添加数据(确保 Key 唯一)
dataDict[key] = data;
// 保存整个字典
JsonUtil.SaveBean(dataDict, path);
}
// ---------------------- 碰撞箱计算 ----------------------
///
/// 将网格数组转换为多边形顶点数组
///
/// 二维数组,值为1的格子表示填充区域
/// 按顺序排列的多边形顶点数组,可以围成闭合多边形
public static Vector2[] GridToPolygonVertices(int[,] grid)
{
if (grid == null || grid.GetLength(0) == 0 || grid.GetLength(1) == 0)
return new Vector2[0];
int width = grid.GetLength(0);
int height = grid.GetLength(1);
// 查找起始边界点
Vector2Int? startPoint = FindStartBoundaryPoint(grid, width, height);
if (!startPoint.HasValue)
return new Vector2[0];
// 边界追踪
List vertices = TraceBoundary(grid, width, height, startPoint.Value);
if (vertices.Count == 0)
return new Vector2[0];
// 顶点优化:移除共线点
vertices = OptimizeVertices(vertices);
return vertices.ToArray();
}
///
/// 查找起始边界点
/// 找到第一个值为1的格子,确定其边界上的起始追踪点
///
private static Vector2Int? FindStartBoundaryPoint(int[,] grid, int width, int height)
{
// 从左下角开始,按行扫描,找到第一个值为1的格子
for (int j = 0; j < height; j++)
{
for (int i = 0; i < width; i++)
{
if (grid[i, j] == 1)
{
// 找到第一个填充格子,检查其下边界(最可能的外边界)
// 如果下边界是外边界,返回左下角点
if (j == 0 || (j > 0 && grid[i, j - 1] == 0))
{
// 返回格子(i,j)的左下角点坐标
return new Vector2Int(i, j);
}
// 否则检查左边界
else if (i == 0 || (i > 0 && grid[i - 1, j] == 0))
{
return new Vector2Int(i, j);
}
}
}
}
return null;
}
///
/// 边界追踪:从起始点开始,沿着边界顺时针追踪,记录所有边界顶点
/// 使用边界边收集和连接的方法
/// 边长固定为2,所有坐标使用整数
///
private static List TraceBoundary(int[,] grid, int width, int height, Vector2Int startCell)
{
// 收集所有边界边:每条边用起点和终点表示(整数坐标)
List<(Vector2Int start, Vector2Int end)> boundaryEdges = CollectBoundaryEdges(grid, width, height);
if (boundaryEdges.Count == 0)
return new List();
// 构建顶点邻接表(直接使用整数坐标)
Dictionary> adjacency = BuildAdjacencyList(boundaryEdges);
if (adjacency.Count == 0)
return new List();
// 找到起始顶点(最左下角的顶点)
Vector2Int startVertex = FindStartVertex(adjacency);
// 沿着邻接表追踪,形成有序顶点序列
List vertices = TraceVertices(adjacency, startVertex);
return vertices;
}
///
/// 收集所有边界边
/// 坐标系统:格子(i,j)的中心为(i*2, j*2),边长为2
/// 格子(i,j)的四个角点为:(i*2-1, j*2-1), (i*2+1, j*2-1), (i*2+1, j*2+1), (i*2-1, j*2+1)
///
private static List<(Vector2Int start, Vector2Int end)> CollectBoundaryEdges(int[,] grid, int width, int height)
{
List<(Vector2Int, Vector2Int)> edges = new List<(Vector2Int, Vector2Int)>();
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
if (grid[i, j] != 1)
continue;
// 计算格子(i,j)的四个角点坐标(整数)
int left = i * 2 - 1;
int right = i * 2 + 1;
int bottom = j * 2 - 1;
int top = j * 2 + 1;
// 检查四个方向的边界
// 下边界(从左下角到右下角)
if (j == 0 || (j > 0 && grid[i, j - 1] == 0))
{
edges.Add((new Vector2Int(left, bottom), new Vector2Int(right, bottom)));
}
// 右边界(从右下角到右上角)
if (i == width - 1 || (i < width - 1 && grid[i + 1, j] == 0))
{
edges.Add((new Vector2Int(right, bottom), new Vector2Int(right, top)));
}
// 上边界(从右上角到左上角)
if (j == height - 1 || (j < height - 1 && grid[i, j + 1] == 0))
{
edges.Add((new Vector2Int(right, top), new Vector2Int(left, top)));
}
// 左边界(从左上角到左下角)
if (i == 0 || (i > 0 && grid[i - 1, j] == 0))
{
edges.Add((new Vector2Int(left, top), new Vector2Int(left, bottom)));
}
}
}
return edges;
}
///
/// 构建顶点邻接表
/// 直接使用整数坐标,无需量化
///
private static Dictionary> BuildAdjacencyList(List<(Vector2Int start, Vector2Int end)> edges)
{
Dictionary> adjacency = new Dictionary>();
foreach (var edge in edges)
{
Vector2Int key1 = edge.start;
Vector2Int key2 = edge.end;
if (!adjacency.ContainsKey(key1))
adjacency[key1] = new List();
if (!adjacency.ContainsKey(key2))
adjacency[key2] = new List();
if (!adjacency[key1].Contains(key2))
adjacency[key1].Add(key2);
if (!adjacency[key2].Contains(key1))
adjacency[key2].Add(key1);
}
return adjacency;
}
///
/// 找到起始顶点(最左下角的顶点)
///
private static Vector2Int FindStartVertex(Dictionary> adjacency)
{
Vector2Int start = new Vector2Int(int.MaxValue, int.MaxValue);
foreach (var key in adjacency.Keys)
{
if (key.y < start.y || (key.y == start.y && key.x < start.x))
{
start = key;
}
}
return start;
}
///
/// 沿着邻接表追踪顶点,形成有序序列(顺时针)
/// 直接使用整数坐标,最后转换为Vector2返回
///
private static List TraceVertices(Dictionary> adjacency, Vector2Int start)
{
List vertices = new List();
Vector2Int current = start;
Vector2Int? previous = null;
do
{
// 将整数坐标转换为Vector2
Vector2 currentPos = new Vector2(current.x, current.y);
// 避免重复添加相同顶点(除非是起点)
if (vertices.Count == 0 || vertices[vertices.Count - 1] != currentPos)
{
vertices.Add(currentPos);
}
// 找到下一个顶点:选择使方向变化最小的邻居(顺时针)
List neighbors = adjacency[current];
Vector2Int? next = null;
if (neighbors.Count == 1)
{
// 只有一个邻居
next = neighbors[0];
}
else if (neighbors.Count == 2)
{
// 有两个邻居,选择不是前一个的那个
foreach (var neighbor in neighbors)
{
if (!previous.HasValue || neighbor != previous.Value)
{
next = neighbor;
break;
}
}
}
else
{
// 多个邻居,选择角度最小的(顺时针方向)
Vector2 currentVec = new Vector2(current.x, current.y);
Vector2 direction;
if (previous.HasValue)
{
Vector2 prevVec = new Vector2(previous.Value.x, previous.Value.y);
direction = (currentVec - prevVec).normalized;
}
else
{
// 起始点,选择最下方的邻居(顺时针起始方向)
direction = new Vector2(1, 0); // 向右
}
float bestAngle = float.MaxValue;
foreach (var neighbor in neighbors)
{
if (previous.HasValue && neighbor == previous.Value)
continue;
Vector2 neighborVec = new Vector2(neighbor.x, neighbor.y);
Vector2 toNeighbor = (neighborVec - currentVec).normalized;
// 计算从当前方向到邻居方向的角度(顺时针)
float angle = GetClockwiseAngle(direction, toNeighbor);
if (angle < bestAngle)
{
bestAngle = angle;
next = neighbor;
}
}
}
if (!next.HasValue)
break;
previous = current;
current = next.Value;
} while (current != start && vertices.Count < adjacency.Count * 2);
return vertices;
}
///
/// 计算从向量a到向量b的顺时针角度(0到2π)
///
private static float GetClockwiseAngle(Vector2 a, Vector2 b)
{
float angleA = Mathf.Atan2(a.y, a.x);
float angleB = Mathf.Atan2(b.y, b.x);
float angle = angleB - angleA;
// 转换为0到2π范围
if (angle < 0)
angle += 2 * Mathf.PI;
return angle;
}
///
/// 优化顶点:移除共线的顶点,保留关键转折点
///
private static List OptimizeVertices(List vertices)
{
if (vertices.Count <= 3)
return vertices;
List optimized = new List();
const float epsilon = 0.0001f; // 浮点数比较阈值
for (int i = 0; i < vertices.Count; i++)
{
Vector2 prev = vertices[(i - 1 + vertices.Count) % vertices.Count];
Vector2 current = vertices[i];
Vector2 next = vertices[(i + 1) % vertices.Count];
// 计算两个向量
Vector2 v1 = current - prev;
Vector2 v2 = next - current;
// 检查是否共线(叉积接近0)且方向相同(点积大于0)
float crossProduct = v1.x * v2.y - v1.y * v2.x;
float dotProduct = Vector2.Dot(v1.normalized, v2.normalized);
// 如果共线且方向相同,跳过当前点
if (Mathf.Abs(crossProduct) < epsilon && dotProduct > 0.99f)
{
continue;
}
optimized.Add(current);
}
return optimized;
}
}
public static class BlockPuzzleYarnCommand
{
[YarnCommand("init_block_puzzle")]
public static void InitBlockPuzzle(string puzzleName)
{
if (BlockPuzzleKit.TryLoadBlockPuzzleData(puzzleName, out var blockPuzzleData))
{
BlockPuzzleSystem.Instance.Initialize(blockPuzzleData);
BlockPuzzleSystem.Instance.SetLocked(true);
}
else
{
Debug.LogError($"加载 BlockPuzzleData 失败: {puzzleName}");
}
}
[YarnCommand("load_shape_rack")]
public static void LoadShapeRack()
{
BlockPuzzleSystem.Instance.LoadShapeRack();
}
[YarnCommand("switch_block_puzzle_state")]
public static void PlayShapeAnimation(string triggerName)
{
BlockPuzzleSystem.Instance.PlayShapeAnimation(triggerName);
}
[YarnCommand("unlock_block_puzzle_system")]
public static void UnlockSystem()
{
BlockPuzzleSystem.Instance.SetLocked(false);
}
[YarnCommand("clear_block_puzzle")]
public static void ClearBlockPuzzle()
{
BlockPuzzleSystem.Instance.ClearGame();
}
}
[Serializable]
public struct BlockPuzzleData
{
public string puzzleName;
public int gridWidth;
public int gridHeight;
public float cellSize;
// 网格内Shape
public GridShapeData[] gridShapeDataInfos;
public string[] shapeInRack;
// 验证器
public string validatorName;
}
[Serializable]
public struct GridShapeData
{
public string shapeKey;
public Vector2Int rootCell;
public RotationAngle rotationAngle;
public bool isDraggable;
}
[Serializable]
public struct BlockShapeData
{
public int shapeId;
public string shapeName;
public int width;
public int height;
public float cellSize;
public string originalPatternStr;
public string prefabPath;
[Header("额外限制")]
public bool extraLimits;
public Vector2Int rootLimit;
public RotationAngle rotationAngleLimit;
[JsonIgnore]
[HideInInspector]
public BlockShapeInfo blockShapeInfo;
public GameObject GetVisualPrefab()
{
if (string.IsNullOrEmpty(prefabPath)) return null;
return ResourceKit.LoadAssetSync(prefabPath);
}
}
[Serializable]
public struct BlockShapeInfo
{
public string shapeName;
public BlockPuzzleParamValue paramValue;
public string shapeDescription;
public string[] states;
public string buttonImagePath;
public string detailImagePath;
public Sprite GetButtonImage()
{
return ResourceKit.LoadAssetSync(buttonImagePath);
}
public Sprite GetDetailImage()
{
return ResourceKit.LoadAssetSync(detailImagePath);
}
}
[Serializable]
public struct BlockPuzzleParamValue
{
public int power;
public int noise;
public int heat;
}
}