feat:1、创建项目

This commit is contained in:
2026-06-25 15:22:28 +08:00
commit c05a65cdc3
6577 changed files with 1168287 additions and 0 deletions
+363
View File
@@ -0,0 +1,363 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ChillConnect;
public class ArrowLevelAutoGenerator : EditorWindow
{
// ----- 参数 -----
private int _levelCount = 10;
private bool _autoSizeByDifficulty = true;
private int _gridRows = 8;
private int _gridCols = 8;
private int _pointSpacing = 60;
private Difficulty _difficulty = Difficulty.Hard;
private GenerationMode _mode = GenerationMode.Random;
private PatternType _pattern = PatternType.Heart;
private bool _randomPattern = false;
private string _outputFileName = "all_levels.json";
private enum Difficulty { Easy, Medium, Hard }
private enum GenerationMode { Random, Pattern }
private enum PatternType { Heart, Star, LetterA, Diamond, Circle, XShape, Random }
[MenuItem("Tools/Arrow Level Auto Generator")]
public static void ShowWindow()
{
GetWindow<ArrowLevelAutoGenerator>("自动生成关卡");
}
private void OnGUI()
{
GUILayout.Label("自动化关卡生成器", EditorStyles.boldLabel);
EditorGUILayout.Space();
_levelCount = EditorGUILayout.IntField("关卡数量", _levelCount);
_autoSizeByDifficulty = EditorGUILayout.Toggle("根据难度自动调整网格尺寸", _autoSizeByDifficulty);
if (!_autoSizeByDifficulty)
{
_gridRows = EditorGUILayout.IntField("网格行数", _gridRows);
_gridCols = EditorGUILayout.IntField("网格列数", _gridCols);
}
else
{
EditorGUILayout.HelpBox("网格尺寸:简单6×6,中等8×8,困难34×21", MessageType.Info);
}
_pointSpacing = EditorGUILayout.IntField("点间距", _pointSpacing);
_difficulty = (Difficulty)EditorGUILayout.EnumPopup("难度", _difficulty);
_mode = (GenerationMode)EditorGUILayout.EnumPopup("生成模式", _mode);
if (_mode == GenerationMode.Pattern)
{
_randomPattern = EditorGUILayout.Toggle("随机图案", _randomPattern);
if (!_randomPattern)
{
_pattern = (PatternType)EditorGUILayout.EnumPopup("指定图案", _pattern);
}
}
_outputFileName = EditorGUILayout.TextField("输出文件名", _outputFileName);
EditorGUILayout.Space();
if (GUILayout.Button("生成关卡", GUILayout.Height(40)))
{
GenerateLevels();
}
}
private void GenerateLevels()
{
if (_levelCount <= 0)
{
EditorUtility.DisplayDialog("错误", "关卡数量必须大于0", "确定");
return;
}
List<LevelConfig> levels = new List<LevelConfig>();
for (int i = 0; i < _levelCount; i++)
{
LevelConfig lv = null;
int rows, cols;
if (_autoSizeByDifficulty)
GetSizeByDifficulty(out rows, out cols);
else
{
rows = _gridRows;
cols = _gridCols;
}
if (_mode == GenerationMode.Pattern)
{
PatternType selectedPattern = _pattern;
if (_randomPattern)
selectedPattern = (PatternType)Random.Range(0, System.Enum.GetValues(typeof(PatternType)).Length - 1);
lv = GeneratePatternLevel(selectedPattern, i + 1, rows, cols);
if (lv == null)
{
Debug.LogWarning($"图案生成失败,回退到随机");
lv = GenerateRandomLevel(i + 1, rows, cols);
}
}
else
{
lv = GenerateRandomLevel(i + 1, rows, cols);
}
if (lv != null)
levels.Add(lv);
}
if (levels.Count == 0)
{
EditorUtility.DisplayDialog("错误", "没有生成任何有效关卡", "确定");
return;
}
AllLevelRoot root = new AllLevelRoot();
root.levels = levels;
string json = JsonUtility.ToJson(root, true);
string path = EditorUtility.SaveFilePanel("保存关卡配置", Application.dataPath, _outputFileName, "json");
if (!string.IsNullOrEmpty(path))
{
File.WriteAllText(path, json);
EditorUtility.DisplayDialog("成功", $"已生成 {levels.Count} 个关卡并保存到 {path}", "确定");
}
}
private void GetSizeByDifficulty(out int rows, out int cols)
{
switch (_difficulty)
{
case Difficulty.Easy: rows = 6; cols = 6; break;
case Difficulty.Medium: rows = 8; cols = 8; break;
case Difficulty.Hard: rows = 34; cols = 21; break;
default: rows = 8; cols = 8; break;
}
}
// ========== 改进的随机生成(无交叉) ==========
private LevelConfig GenerateRandomLevel(int levelId, int rows, int cols)
{
LevelConfig lv = new LevelConfig();
lv.levelId = levelId;
lv.levelName = $"第{levelId}关";
lv.gridRows = rows;
lv.gridCols = cols;
lv.pointSpacing = _pointSpacing;
lv.arrows = new List<ArrowConfig>();
int arrowCount = 0;
int minPathLen=0,maxPathLen=0;
int maxTurns = 0;
switch (_difficulty)
{
case Difficulty.Easy: arrowCount = Random.Range(3, 6); minPathLen = 1; maxPathLen = 3; maxTurns = 1; break;
case Difficulty.Medium: arrowCount = Random.Range(6, 10); minPathLen = 2; maxPathLen = 5; maxTurns = 2; break;
case Difficulty.Hard: arrowCount = Random.Range(25, 40); minPathLen = 4; maxPathLen = 10; maxTurns = 5; break;
}
HashSet<int> occupiedPoints = new HashSet<int>();
int maxAttemptsPerArrow = 200;
int totalAttempts = 0;
int maxTotalAttempts = arrowCount * 50;
for (int i = 0; i < arrowCount; i++)
{
bool success = false;
int attempts = 0;
while (!success && attempts < maxAttemptsPerArrow && totalAttempts < maxTotalAttempts)
{
attempts++;
totalAttempts++;
int start = Random.Range(0, rows * cols);
if (occupiedPoints.Contains(start))
continue;
int pathLen = Random.Range(minPathLen, maxPathLen + 1);
List<string> dirs = new List<string>();
int cur = start;
bool valid = true;
int turns = 0;
string lastDir = "";
List<int> pathPoints = new List<int> { start };
for (int j = 0; j < pathLen; j++)
{
List<string> validDirs = GetValidDirections(cur, rows, cols);
if (validDirs.Count == 0) { valid = false; break; }
string chosenDir = "";
if (turns < maxTurns && !string.IsNullOrEmpty(lastDir))
{
var otherDirs = validDirs.Where(d => d != lastDir).ToList();
if (otherDirs.Count > 0)
chosenDir = otherDirs[Random.Range(0, otherDirs.Count)];
else
chosenDir = validDirs[Random.Range(0, validDirs.Count)];
}
else
{
chosenDir = validDirs[Random.Range(0, validDirs.Count)];
}
if (chosenDir != lastDir && !string.IsNullOrEmpty(lastDir))
turns++;
dirs.Add(chosenDir);
cur = GetNextPoint(cur, chosenDir, cols, rows);
if (cur < 0) { valid = false; break; }
pathPoints.Add(cur);
lastDir = chosenDir;
}
if (!valid) continue;
// 检测冲突
bool conflict = false;
foreach (int p in pathPoints)
{
if (occupiedPoints.Contains(p))
{
conflict = true;
break;
}
}
if (conflict) continue;
// 无冲突,添加箭头
ArrowConfig arrow = new ArrowConfig();
arrow.id = i + 1;
arrow.startPoint = start;
arrow.path = dirs;
lv.arrows.Add(arrow);
foreach (int p in pathPoints)
occupiedPoints.Add(p);
success = true;
}
if (!success)
Debug.LogWarning($"生成第 {levelId} 关时,第 {i+1} 个箭头生成失败,跳过");
}
if (lv.arrows.Count == 0) return null;
return lv;
}
private List<string> GetValidDirections(int point, int rows, int cols)
{
List<string> dirs = new List<string>();
int row = point / cols;
int col = point % cols;
if (row > 0) dirs.Add("up");
if (row < rows - 1) dirs.Add("down");
if (col > 0) dirs.Add("left");
if (col < cols - 1) dirs.Add("right");
return dirs;
}
// ========== 图案生成(简单版) ==========
private LevelConfig GeneratePatternLevel(PatternType pattern, int levelId, int rows, int cols)
{
// 这里仅实现菱形图案,您可自行扩展
return GenerateDiamondPattern(levelId, rows, cols);
}
private LevelConfig GenerateDiamondPattern(int levelId, int rows, int cols)
{
if (rows < 5 || cols < 5) return null;
LevelConfig lv = new LevelConfig();
lv.levelId = levelId;
lv.levelName = $"图案Diamond_{levelId}";
lv.gridRows = rows;
lv.gridCols = cols;
lv.pointSpacing = _pointSpacing;
lv.arrows = new List<ArrowConfig>();
int centerRow = rows / 2;
int centerCol = cols / 2;
int radius = Mathf.Min(rows, cols) / 2 - 1;
if (radius < 2) return null;
List<int> points = new List<int>();
for (int i = 0; i <= radius; i++)
{
int r = centerRow - i;
int c = centerCol + i;
if (r >= 0 && c < cols) points.Add(r * cols + c);
}
for (int i = 1; i <= radius; i++)
{
int r = centerRow + i;
int c = centerCol + radius - i;
if (r < rows && c >= 0) points.Add(r * cols + c);
}
for (int i = 1; i <= radius; i++)
{
int r = centerRow + radius - i;
int c = centerCol - i;
if (r >= 0 && c >= 0) points.Add(r * cols + c);
}
for (int i = 1; i < radius; i++)
{
int r = centerRow - i;
int c = centerCol - radius + i;
if (r >= 0 && c >= 0) points.Add(r * cols + c);
}
if (points.Count < 3) return null;
int maxStep = 4;
int arrowId = 1;
for (int i = 0; i < points.Count - 1;)
{
int remaining = points.Count - 1 - i;
int step = Mathf.Min(maxStep, remaining);
var segment = points.Skip(i).Take(step + 1).ToList();
if (segment.Count >= 2)
{
ArrowConfig arrow = new ArrowConfig();
arrow.id = arrowId++;
arrow.startPoint = segment[0];
arrow.path = new List<string>();
for (int j = 0; j < segment.Count - 1; j++)
{
string dir = GetDirection(segment[j], segment[j + 1], cols);
arrow.path.Add(dir);
}
lv.arrows.Add(arrow);
}
i += step;
}
return lv;
}
// ========== 工具方法 ==========
private int GetNextPoint(int current, string dir, int cols, int rows)
{
int row = current / cols;
int col = current % cols;
switch (dir)
{
case "up": row--; break;
case "down": row++; break;
case "left": col--; break;
case "right": col++; break;
}
if (row < 0 || row >= rows || col < 0 || col >= cols) return -1;
return row * cols + col;
}
private string GetDirection(int from, int to, int cols)
{
int fromRow = from / cols, fromCol = from % cols;
int toRow = to / cols, toCol = to % cols;
if (toRow < fromRow) return "up";
if (toRow > fromRow) return "down";
if (toCol < fromCol) return "left";
if (toCol > fromCol) return "right";
return "up";
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1d00ed1816894c03a2aa8dcf12d00069
timeCreated: 1781774604
+495
View File
@@ -0,0 +1,495 @@
// 文件名: ArrowLevelEditor.cs
// 路径: Assets/Editor/ArrowLevelEditor.cs
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ChillConnect;
public class ArrowLevelEditor : EditorWindow
{
// ========== 多关卡数据 ==========
private List<LevelConfig> _levels = new List<LevelConfig>();
private int _selectedLevelIndex = -1;
private int _nextLevelId = 1;
// ========== 当前正在编辑的箭头路径 ==========
private List<int> _currentPathPoints = new List<int>();
private int _arrowIdCounter = 1;
// ========== 预设颜色 ==========
private readonly Color[] _colorPalette = new Color[]
{
new Color(1f, 0.3f, 0.3f),
new Color(1f, 0.6f, 0.2f),
new Color(1f, 0.9f, 0.2f),
new Color(0.3f, 0.8f, 0.4f),
new Color(0.2f, 0.8f, 0.9f),
new Color(0.3f, 0.5f, 1f),
new Color(0.7f, 0.4f, 1f),
new Color(1f, 0.5f, 0.8f)
};
private Vector2 _scrollPos;
[MenuItem("Tools/Arrow Level Editor")]
public static void ShowWindow()
{
GetWindow<ArrowLevelEditor>("箭头关卡编辑器 (多关卡)");
}
private void OnEnable()
{
if (_levels.Count == 0)
CreateNewLevel("第1关", 8, 8, 60);
if (_selectedLevelIndex < 0 || _selectedLevelIndex >= _levels.Count)
_selectedLevelIndex = 0;
OnLevelChanged();
}
private void OnGUI()
{
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
// ========== 顶部工具栏 ==========
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("导入 all_levels.json", GUILayout.Width(150))) ImportLevels();
if (GUILayout.Button("导出 all_levels.json", GUILayout.Width(150))) ExportAllLevels();
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
GUILayout.Space(10);
// ========== 关卡管理 ==========
EditorGUILayout.BeginHorizontal();
GUILayout.Label("关卡列表", EditorStyles.boldLabel);
if (GUILayout.Button("新建关卡", GUILayout.Width(80)))
CreateNewLevel($"第{_levels.Count + 1}关", 8, 8, 60);
if (GUILayout.Button("删除当前", GUILayout.Width(80)))
{
if (_selectedLevelIndex >= 0 && _selectedLevelIndex < _levels.Count)
{
if (EditorUtility.DisplayDialog("删除关卡", $"确定删除 '{_levels[_selectedLevelIndex].levelName}'", "确定", "取消"))
{
_levels.RemoveAt(_selectedLevelIndex);
if (_levels.Count == 0) CreateNewLevel("第1关", 8, 8, 60);
_selectedLevelIndex = Mathf.Clamp(_selectedLevelIndex, 0, _levels.Count - 1);
OnLevelChanged();
}
}
}
if (GUILayout.Button("复制当前", GUILayout.Width(80)))
{
if (_selectedLevelIndex >= 0)
{
var src = _levels[_selectedLevelIndex];
LevelConfig copy = new LevelConfig();
copy.levelId = _nextLevelId++;
copy.levelName = src.levelName + "_副本";
copy.gridRows = src.gridRows;
copy.gridCols = src.gridCols;
copy.pointSpacing = src.pointSpacing;
copy.arrows = new List<ArrowConfig>();
foreach (var a in src.arrows)
{
ArrowConfig newA = new ArrowConfig();
newA.id = a.id;
newA.startPoint = a.startPoint;
newA.path = new List<string>(a.path);
copy.arrows.Add(newA);
}
_levels.Add(copy);
_selectedLevelIndex = _levels.Count - 1;
OnLevelChanged();
}
}
EditorGUILayout.EndHorizontal();
// 显示关卡列表
for (int i = 0; i < _levels.Count; i++)
{
var lv = _levels[i];
EditorGUILayout.BeginHorizontal();
if (i == _selectedLevelIndex) GUI.backgroundColor = Color.green;
if (GUILayout.Button($"ID:{lv.levelId} {lv.levelName} ({lv.arrows.Count}箭头)", GUILayout.MinWidth(150)))
{
if (_selectedLevelIndex != i)
{
_selectedLevelIndex = i;
OnLevelChanged();
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
}
GUILayout.Space(10);
// ========== 当前关卡编辑区 ==========
if (_selectedLevelIndex >= 0 && _selectedLevelIndex < _levels.Count)
{
LevelConfig currentLevel = _levels[_selectedLevelIndex];
EditorGUILayout.LabelField($"编辑关卡: {currentLevel.levelName}", EditorStyles.boldLabel);
currentLevel.levelId = EditorGUILayout.IntField("关卡ID", currentLevel.levelId);
currentLevel.levelName = EditorGUILayout.TextField("关卡名称", currentLevel.levelName);
currentLevel.gridRows = EditorGUILayout.IntField("行数", currentLevel.gridRows);
currentLevel.gridCols = EditorGUILayout.IntField("列数", currentLevel.gridCols);
currentLevel.pointSpacing = EditorGUILayout.IntField("点间距", currentLevel.pointSpacing);
GUILayout.Space(10);
EditorGUILayout.LabelField("绘制当前箭头", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("撤销上一点") && _currentPathPoints.Count > 0)
_currentPathPoints.RemoveAt(_currentPathPoints.Count - 1);
if (GUILayout.Button("清空当前路径"))
_currentPathPoints.Clear();
if (GUILayout.Button("清空所有箭头"))
{
if (EditorUtility.DisplayDialog("确认", "删除当前关卡所有箭头?", "确定", "取消"))
{
currentLevel.arrows.Clear();
_arrowIdCounter = 1;
_currentPathPoints.Clear();
}
}
EditorGUILayout.EndHorizontal();
// 网格绘制(增强:显示起点/终点标记)
DrawGridWithArrows(currentLevel);
// 确认生成
if (GUILayout.Button("确认生成当前箭头 (加入列表)"))
{
if (_currentPathPoints.Count < 2)
{
EditorUtility.DisplayDialog("错误", "路径至少需要2个点", "确定");
}
else
{
ArrowConfig newArrow = new ArrowConfig();
newArrow.id = _arrowIdCounter++;
newArrow.startPoint = _currentPathPoints[0];
newArrow.path = new List<string>();
for (int i = 0; i < _currentPathPoints.Count - 1; i++)
{
newArrow.path.Add(GetDirection(_currentPathPoints[i], _currentPathPoints[i + 1], currentLevel.gridCols));
}
int colorIdx = (currentLevel.arrows.Count) % _colorPalette.Length;
newArrow.runtimeColor = _colorPalette[colorIdx];
currentLevel.arrows.Add(newArrow);
_currentPathPoints.Clear();
Debug.Log($"箭头已生成,当前箭头总数 {currentLevel.arrows.Count}");
}
}
// 箭头列表(显示起点→终点)
GUILayout.Space(10);
EditorGUILayout.LabelField($"当前箭头 ({currentLevel.arrows.Count})", EditorStyles.boldLabel);
for (int i = 0; i < currentLevel.arrows.Count; i++)
{
var arrow = currentLevel.arrows[i];
// 计算终点
int endPoint = arrow.startPoint;
if (arrow.path.Count > 0)
{
int cur = arrow.startPoint;
foreach (string dir in arrow.path)
{
int next = GetNextPoint(cur, dir, currentLevel.gridCols, currentLevel.gridRows);
if (next >= 0) cur = next;
}
endPoint = cur;
}
EditorGUILayout.BeginHorizontal();
Color arrowColor = arrow.runtimeColor;
if (arrowColor == default)
{
arrowColor = _colorPalette[i % _colorPalette.Length];
arrow.runtimeColor = arrowColor;
}
GUI.backgroundColor = arrowColor;
GUILayout.Label(" ", GUILayout.Width(20), GUILayout.Height(18));
GUI.backgroundColor = Color.white;
if (GUILayout.Button($"ID:{arrow.id} {arrow.startPoint} → {endPoint} 路径:{string.Join(",", arrow.path)}", GUILayout.MinWidth(200)))
{
// 可扩展高亮
}
if (GUILayout.Button("✕", GUILayout.Width(20)))
{
if (EditorUtility.DisplayDialog("删除箭头", $"删除箭头 ID:{arrow.id}", "确定", "取消"))
{
currentLevel.arrows.RemoveAt(i);
i--;
}
}
EditorGUILayout.EndHorizontal();
}
}
else
{
EditorGUILayout.HelpBox("请先创建或选择一个关卡", MessageType.Info);
}
EditorGUILayout.EndScrollView();
}
// ========== 辅助方法 ==========
private void CreateNewLevel(string name, int rows, int cols, int spacing)
{
LevelConfig lv = new LevelConfig();
lv.levelId = _nextLevelId++;
lv.levelName = name;
lv.gridRows = rows;
lv.gridCols = cols;
lv.pointSpacing = spacing;
lv.arrows = new List<ArrowConfig>();
_levels.Add(lv);
_selectedLevelIndex = _levels.Count - 1;
OnLevelChanged();
}
private void OnLevelChanged()
{
_currentPathPoints.Clear();
if (_selectedLevelIndex >= 0 && _selectedLevelIndex < _levels.Count)
{
var lv = _levels[_selectedLevelIndex];
_arrowIdCounter = lv.arrows.Count + 1;
for (int i = 0; i < lv.arrows.Count; i++)
{
if (lv.arrows[i].runtimeColor == default)
lv.arrows[i].runtimeColor = _colorPalette[i % _colorPalette.Length];
}
}
}
// ========== 增强的网格绘制(标记起点/终点) ==========
private void DrawGridWithArrows(LevelConfig level)
{
int totalPoints = level.gridRows * level.gridCols;
int rows = level.gridRows;
int cols = level.gridCols;
// 构建:点ID -> (箭头索引, 是否起点, 是否终点)
var pointInfo = new Dictionary<int, List<(int idx, bool isStart, bool isEnd)>>();
for (int i = 0; i < level.arrows.Count; i++)
{
var arrow = level.arrows[i];
List<int> allPoints = new List<int> { arrow.startPoint };
int cur = arrow.startPoint;
foreach (string dir in arrow.path)
{
int next = GetNextPoint(cur, dir, cols, rows);
if (next >= 0) allPoints.Add(next);
cur = next;
}
// 终点是路径最后一个点
int end = allPoints.LastOrDefault();
for (int j = 0; j < allPoints.Count; j++)
{
int pid = allPoints[j];
bool isStart = (j == 0);
bool isEnd = (j == allPoints.Count - 1);
if (!pointInfo.ContainsKey(pid))
pointInfo[pid] = new List<(int, bool, bool)>();
pointInfo[pid].Add((i, isStart, isEnd));
}
}
// 构建每个点的显示标签
var pointLabels = new Dictionary<int, string>();
foreach (var kv in pointInfo)
{
int pid = kv.Key;
var list = kv.Value;
bool hasStart = list.Any(x => x.isStart);
bool hasEnd = list.Any(x => x.isEnd);
bool multi = list.Count > 1;
string label = "";
if (hasStart && hasEnd)
label = "S/E";
else if (hasStart)
label = "S";
else if (hasEnd)
label = "E";
if (multi && (hasStart || hasEnd))
label += "+";
else if (multi && !hasStart && !hasEnd)
label = "+";
pointLabels[pid] = label;
}
// 绘制网格
for (int i = 0; i < totalPoints; i++)
{
int row = i / cols;
int col = i % cols;
if (col == 0) EditorGUILayout.BeginHorizontal();
bool isInCurrentPath = _currentPathPoints.Contains(i);
bool isInSavedPath = pointInfo.ContainsKey(i);
Color bgColor = GUI.backgroundColor;
string label = i.ToString();
if (isInSavedPath)
{
var owners = pointInfo[i];
if (owners.Count == 1)
{
int idx = owners[0].idx;
bgColor = level.arrows[idx].runtimeColor;
}
else
{
bgColor = Color.gray;
}
// 添加起点/终点标记
if (pointLabels.ContainsKey(i))
label += pointLabels[i];
}
if (isInCurrentPath)
{
bgColor = Color.green;
label = i + "●";
}
// 起点/终点特殊颜色边框(仅当无重叠时)
if (isInSavedPath && pointInfo[i].Count == 1)
{
var info = pointInfo[i][0];
if (info.isStart)
bgColor = Color.Lerp(bgColor, Color.green, 0.3f);
else if (info.isEnd)
bgColor = Color.Lerp(bgColor, Color.red, 0.3f);
}
GUI.backgroundColor = bgColor;
if (GUILayout.Button(label, GUILayout.Width(50), GUILayout.Height(30))) // 增加宽度容纳标记
{
if (_currentPathPoints.Count == 0)
_currentPathPoints.Add(i);
else
{
int last = _currentPathPoints[_currentPathPoints.Count - 1];
if (IsAdjacent(last, i, cols))
{
if (isInSavedPath)
Debug.LogWarning($"点 {i} 已被其他箭头占用,可能重叠");
_currentPathPoints.Add(i);
}
else
EditorUtility.DisplayDialog("提示", $"必须与上一个点 ({last}) 相邻", "确定");
}
}
GUI.backgroundColor = Color.white;
if (col == cols - 1) EditorGUILayout.EndHorizontal();
}
}
// ========== 工具方法 ==========
private int GetNextPoint(int current, string dir, int cols, int rows)
{
int row = current / cols;
int col = current % cols;
switch (dir)
{
case "up": row--; break;
case "down": row++; break;
case "left": col--; break;
case "right": col++; break;
}
if (row < 0 || row >= rows || col < 0 || col >= cols) return -1;
return row * cols + col;
}
private bool IsAdjacent(int a, int b, int cols)
{
int aRow = a / cols, aCol = a % cols;
int bRow = b / cols, bCol = b % cols;
return (Mathf.Abs(aRow - bRow) + Mathf.Abs(aCol - bCol)) == 1;
}
private string GetDirection(int from, int to, int cols)
{
int fromRow = from / cols, fromCol = from % cols;
int toRow = to / cols, toCol = to % cols;
if (toRow < fromRow) return "up";
if (toRow > fromRow) return "down";
if (toCol < fromCol) return "left";
if (toCol > fromCol) return "right";
return "up";
}
// ========== 导入/导出 ==========
private void ImportLevels()
{
string path = EditorUtility.OpenFilePanel("导入 all_levels.json", Application.dataPath, "json");
if (string.IsNullOrEmpty(path)) return;
try
{
string json = File.ReadAllText(path);
AllLevelRoot root = JsonUtility.FromJson<AllLevelRoot>(json);
if (root != null && root.levels != null && root.levels.Count > 0)
{
_levels = root.levels;
foreach (var lv in _levels)
for (int i = 0; i < lv.arrows.Count; i++)
lv.arrows[i].runtimeColor = _colorPalette[i % _colorPalette.Length];
int maxId = _levels.Max(l => l.levelId);
_nextLevelId = maxId + 1;
_selectedLevelIndex = 0;
OnLevelChanged();
Debug.Log($"导入成功,共 {_levels.Count} 个关卡");
}
else
EditorUtility.DisplayDialog("导入失败", "文件格式不正确或为空", "确定");
}
catch (System.Exception e)
{
EditorUtility.DisplayDialog("导入错误", e.Message, "确定");
}
}
private void ExportAllLevels()
{
if (_levels.Count == 0) { EditorUtility.DisplayDialog("错误", "没有关卡可导出", "确定"); return; }
AllLevelRoot root = new AllLevelRoot();
root.levels = new List<LevelConfig>();
foreach (var lv in _levels)
{
LevelConfig copy = new LevelConfig();
copy.levelId = lv.levelId;
copy.levelName = lv.levelName;
copy.gridRows = lv.gridRows;
copy.gridCols = lv.gridCols;
copy.pointSpacing = lv.pointSpacing;
copy.arrows = new List<ArrowConfig>();
foreach (var a in lv.arrows)
{
ArrowConfig newA = new ArrowConfig();
newA.id = a.id;
newA.startPoint = a.startPoint;
newA.path = new List<string>(a.path);
copy.arrows.Add(newA);
}
root.levels.Add(copy);
}
string json = JsonUtility.ToJson(root, true);
string path = EditorUtility.SaveFilePanel("保存 all_levels.json", Application.dataPath, "all_levels.json", "json");
if (!string.IsNullOrEmpty(path))
{
File.WriteAllText(path, json);
EditorUtility.DisplayDialog("成功", $"已保存 {root.levels.Count} 个关卡", "确定");
}
}
}
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a75099f55ed545f9b5c1fe0aedc9c82c
timeCreated: 1781769832
+261
View File
@@ -0,0 +1,261 @@
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
namespace ChillConnect.Editor
{
/// <summary>
/// 第三方关卡配置批量转换工具
/// 输入:文件夹内所有第三方单关JSON
/// 输出:对应转换后的我方格式单关JSON
/// </summary>
public class LevelBatchConverter : EditorWindow
{
#region
// 第三方单关结构(只声明需要的字段,多余自动忽略)
[System.Serializable]
private class ThirdPartyLevel
{
public Vector2Int size;
public string name;
public List<ThirdPartyArrow> arrows;
}
[System.Serializable]
private class ThirdPartyArrow
{
public List<Vector2Int> nodes;
public int color;
}
// 我方单关结构(和运行时对齐)
[System.Serializable]
public class ArrowConfig
{
public int id;
public int startPoint;
public List<string> path = new List<string>();
}
[System.Serializable]
public class LevelConfig
{
public int levelId;
public string levelName;
public int gridRows;
public int gridCols;
public int pointSpacing = 60;
public List<ArrowConfig> arrows = new List<ArrowConfig>();
}
#endregion
private string _inputFolder = "";
private string _outputFolder = "";
private int _pointSpacing = 60;
private Vector2 _scrollPos;
private List<string> _logList = new List<string>();
[MenuItem("Window/Arrow Game/批量关卡转换工具")]
public static void ShowWindow()
{
var window = GetWindow<LevelBatchConverter>("批量关卡转换");
window.minSize = new Vector2(500, 400);
}
private void OnGUI()
{
EditorGUILayout.Space(10);
// 输入文件夹
EditorGUILayout.LabelField("输入文件夹(第三方单关JSON", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
_inputFolder = EditorGUILayout.TextField(_inputFolder);
if (GUILayout.Button("选择", GUILayout.Width(60)))
{
string path = EditorUtility.OpenFolderPanel("选择输入文件夹", Application.dataPath, "");
if (!string.IsNullOrEmpty(path))
_inputFolder = path;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(8);
// 输出文件夹
EditorGUILayout.LabelField("输出文件夹(转换后JSON", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
_outputFolder = EditorGUILayout.TextField(_outputFolder);
if (GUILayout.Button("选择", GUILayout.Width(60)))
{
string path = EditorUtility.OpenFolderPanel("选择输出文件夹", Application.dataPath, "");
if (!string.IsNullOrEmpty(path))
_outputFolder = path;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(8);
// 基础配置
_pointSpacing = EditorGUILayout.IntField("默认点间距", _pointSpacing);
EditorGUILayout.Space(10);
// 转换按钮
if (GUILayout.Button("开始批量转换", GUILayout.Height(30)))
{
BatchConvert();
}
EditorGUILayout.Space(10);
// 日志区域
EditorGUILayout.LabelField("转换日志", EditorStyles.boldLabel);
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, GUILayout.ExpandHeight(true));
foreach (string log in _logList)
{
EditorGUILayout.LabelField(log);
}
EditorGUILayout.EndScrollView();
}
/// <summary>
/// 批量转换核心逻辑
/// </summary>
private void BatchConvert()
{
_logList.Clear();
if (string.IsNullOrEmpty(_inputFolder) || !Directory.Exists(_inputFolder))
{
_logList.Add("❌ 输入文件夹不存在");
return;
}
if (string.IsNullOrEmpty(_outputFolder))
{
_logList.Add("❌ 请选择输出文件夹");
return;
}
// 创建输出目录
if (!Directory.Exists(_outputFolder))
{
Directory.CreateDirectory(_outputFolder);
}
// 获取所有json文件
string[] files = Directory.GetFiles(_inputFolder, "*.json", SearchOption.TopDirectoryOnly);
if (files.Length == 0)
{
_logList.Add("⚠️ 输入文件夹内未找到JSON文件");
return;
}
_logList.Add($"找到 {files.Length} 个JSON文件,开始转换...");
int successCount = 0;
int failCount = 0;
for (int i = 0; i < files.Length; i++)
{
string filePath = files[i];
string fileName = Path.GetFileNameWithoutExtension(filePath);
try
{
// 1. 读取并解析第三方配置
string json = File.ReadAllText(filePath);
ThirdPartyLevel thirdLevel = JsonUtility.FromJson<ThirdPartyLevel>(json);
if (thirdLevel == null || thirdLevel.arrows == null)
{
_logList.Add($"❌ {fileName}:解析失败,格式不匹配");
failCount++;
continue;
}
// 2. 转换为我方格式
LevelConfig level = ConvertLevel(thirdLevel, fileName, i + 1);
// 3. 导出到输出文件夹
string outJson = JsonUtility.ToJson(level, true);
string outPath = Path.Combine(_outputFolder, $"{fileName}.json");
File.WriteAllText(outPath, outJson);
_logList.Add($"✅ {fileName}:转换成功 | {level.gridCols}×{level.gridRows} | {level.arrows.Count}个箭头");
successCount++;
}
catch (System.Exception e)
{
_logList.Add($"❌ {fileName}:转换异常 - {e.Message}");
failCount++;
}
}
_logList.Add("");
_logList.Add($"===== 转换完成 =====");
_logList.Add($"成功:{successCount} 个");
_logList.Add($"失败:{failCount} 个");
_logList.Add($"输出路径:{_outputFolder}");
EditorUtility.DisplayDialog("批量转换完成",
$"成功:{successCount}\n失败:{failCount}\n输出路径:{_outputFolder}",
"确定");
AssetDatabase.Refresh();
}
/// <summary>
/// 单个关卡转换逻辑(和之前编辑器内转换完全一致)
/// </summary>
private LevelConfig ConvertLevel(ThirdPartyLevel thirdLevel, string fileName, int index)
{
LevelConfig level = new LevelConfig
{
levelId = index,
levelName = string.IsNullOrEmpty(thirdLevel.name) ? fileName : thirdLevel.name,
gridCols = thirdLevel.size.x,
gridRows = thirdLevel.size.y,
pointSpacing = _pointSpacing,
arrows = new List<ArrowConfig>()
};
for (int i = 0; i < thirdLevel.arrows.Count; i++)
{
var thirdArrow = thirdLevel.arrows[i];
if (thirdArrow.nodes == null || thirdArrow.nodes.Count == 0)
continue;
ArrowConfig arrow = new ArrowConfig
{
id = i + 1,
path = new List<string>()
};
// 起点转换:(x,y) → 点ID = y * 列数 + x
Vector2Int startNode = thirdArrow.nodes[0];
arrow.startPoint = startNode.y * level.gridCols + startNode.x;
// 路径转换:相邻节点计算方向
for (int j = 1; j < thirdArrow.nodes.Count; j++)
{
Vector2Int prev = thirdArrow.nodes[j - 1];
Vector2Int cur = thirdArrow.nodes[j];
int dx = cur.x - prev.x;
int dy = cur.y - prev.y;
if (dx == 1) arrow.path.Add("right");
else if (dx == -1) arrow.path.Add("left");
else if (dy == 1) arrow.path.Add("down");
else if (dy == -1) arrow.path.Add("up");
}
level.arrows.Add(arrow);
}
return level;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 027b5eb354fe4e4698f807233ca6dc5d
timeCreated: 1782205667