fix:1、更换关卡获取,改为单关一个json文件
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ChillConnect.Editor
|
||||
{
|
||||
[System.Serializable]
|
||||
public class EditorLevelConfig
|
||||
{
|
||||
public int levelId;
|
||||
public string levelName;
|
||||
public int gridRows;
|
||||
public int gridCols;
|
||||
public int pointSpacing;
|
||||
public List<EditorArrowConfig> arrows;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class EditorArrowConfig
|
||||
{
|
||||
public int id;
|
||||
public int startPoint;
|
||||
public List<string> path;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class EditorAllLevelRoot
|
||||
{
|
||||
public List<EditorLevelConfig> levels;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9188e4d5fc904730b169ca235b32903b
|
||||
timeCreated: 1781601766
|
||||
@@ -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
|
||||
@@ -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} 个关卡", "确定");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a75099f55ed545f9b5c1fe0aedc9c82c
|
||||
timeCreated: 1781769832
|
||||
@@ -1,271 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
using ChillConnect;
|
||||
|
||||
namespace ChillConnect.Editor
|
||||
{
|
||||
public static class ArrowLevelGenerator
|
||||
{
|
||||
private const int MaxGenerateAttempts = 500; // 单关最大尝试次数
|
||||
private static readonly string[] Directions = { "up", "down", "left", "right" };
|
||||
|
||||
/// <summary>
|
||||
/// 生成单个关卡
|
||||
/// </summary>
|
||||
public static EditorLevelConfig GenerateLevel(int levelId, LevelDifficultyPreset preset)
|
||||
{
|
||||
for (int attempt = 0; attempt < MaxGenerateAttempts; attempt++)
|
||||
{
|
||||
EditorLevelConfig level = new EditorLevelConfig
|
||||
{
|
||||
levelId = levelId,
|
||||
levelName = $"第{levelId - 1000}关",
|
||||
gridRows = preset.gridRows,
|
||||
gridCols = preset.gridCols,
|
||||
pointSpacing = 60,
|
||||
arrows = new List<EditorArrowConfig>()
|
||||
};
|
||||
|
||||
bool[,] occupied = new bool[preset.gridRows, preset.gridCols];
|
||||
int arrowId = 1;
|
||||
int failedCount = 0;
|
||||
|
||||
// 逐个生成箭头,直到达到目标数量或多次失败
|
||||
while (level.arrows.Count < preset.arrowCount && failedCount < 100)
|
||||
{
|
||||
if (TryGenerateArrow(preset, occupied, level.gridRows, level.gridCols,
|
||||
out EditorArrowConfig arrow))
|
||||
{
|
||||
arrow.id = arrowId++;
|
||||
level.arrows.Add(arrow);
|
||||
|
||||
// 标记占用
|
||||
MarkOccupied(arrow, occupied, level.gridCols);
|
||||
}
|
||||
else
|
||||
{
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 箭头数太少,放弃本次
|
||||
if (level.arrows.Count < preset.minArrowCount)
|
||||
continue;
|
||||
|
||||
// 可解性校验
|
||||
if (CheckSolvable(level))
|
||||
{
|
||||
return level;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogWarning($"关卡 {levelId} 达到最大尝试次数仍未生成合格关卡");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试生成单个合法箭头
|
||||
/// </summary>
|
||||
private static bool TryGenerateArrow(LevelDifficultyPreset preset, bool[,] occupied,
|
||||
int rows, int cols, out EditorArrowConfig arrow)
|
||||
{
|
||||
arrow = null;
|
||||
|
||||
// 随机起点
|
||||
int startRow = Random.Range(0, rows);
|
||||
int startCol = Random.Range(0, cols);
|
||||
if (occupied[startRow, startCol])
|
||||
return false;
|
||||
|
||||
// 随机路径长度和折数
|
||||
int pathLength = Random.Range(preset.minPathLength, preset.maxPathLength + 1);
|
||||
int maxTurns = preset.maxTurnCount;
|
||||
int turns = 0;
|
||||
|
||||
List<string> path = new List<string>();
|
||||
List<(int r, int c)> pathPoints = new List<(int r, int c)> { (startRow, startCol) };
|
||||
|
||||
string lastDir = Directions[Random.Range(0, Directions.Length)];
|
||||
int curRow = startRow;
|
||||
int curCol = startCol;
|
||||
|
||||
for (int i = 0; i < pathLength; i++)
|
||||
{
|
||||
string curDir = lastDir;
|
||||
|
||||
// 有概率转向
|
||||
if (i > 0 && turns < maxTurns && Random.value < preset.turnProbability)
|
||||
{
|
||||
// 随机换一个方向(不能回头)
|
||||
List<string> validDirs = new List<string>(Directions);
|
||||
validDirs.Remove(GetOppositeDir(lastDir));
|
||||
curDir = validDirs[Random.Range(0, validDirs.Count)];
|
||||
turns++;
|
||||
}
|
||||
|
||||
// 计算下一点
|
||||
(int nr, int nc) = MoveDir(curRow, curCol, curDir);
|
||||
|
||||
// 越界或已占用,终止
|
||||
if (nr < 0 || nr >= rows || nc < 0 || nc >= cols || occupied[nr, nc])
|
||||
break;
|
||||
|
||||
path.Add(curDir);
|
||||
pathPoints.Add((nr, nc));
|
||||
curRow = nr;
|
||||
curCol = nc;
|
||||
lastDir = curDir;
|
||||
}
|
||||
|
||||
// 路径太短,无效
|
||||
if (path.Count < 1)
|
||||
return false;
|
||||
|
||||
// 构造箭头
|
||||
arrow = new EditorArrowConfig
|
||||
{
|
||||
startPoint = startRow * cols + startCol,
|
||||
path = path
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记箭头占用的网格点
|
||||
/// </summary>
|
||||
private static void MarkOccupied(EditorArrowConfig arrow, bool[,] occupied, int totalCols)
|
||||
{
|
||||
int pointId = arrow.startPoint;
|
||||
int row = pointId / totalCols;
|
||||
int col = pointId % totalCols;
|
||||
occupied[row, col] = true;
|
||||
|
||||
foreach (string dir in arrow.path)
|
||||
{
|
||||
(row, col) = MoveDir(row, col, dir);
|
||||
occupied[row, col] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可解性校验:贪心模拟消除法
|
||||
/// </summary>
|
||||
public static bool CheckSolvable(EditorLevelConfig level)
|
||||
{
|
||||
int rows = level.gridRows;
|
||||
int cols = level.gridCols;
|
||||
|
||||
// 复制占用矩阵
|
||||
bool[,] occupied = new bool[rows, cols];
|
||||
foreach (var arrow in level.arrows)
|
||||
{
|
||||
MarkOccupied(arrow, occupied, cols);
|
||||
}
|
||||
|
||||
List<EditorArrowConfig> remaining = new List<EditorArrowConfig>(level.arrows);
|
||||
int roundCount = 0;
|
||||
const int maxRounds = 200;
|
||||
|
||||
while (remaining.Count > 0 && roundCount < maxRounds)
|
||||
{
|
||||
roundCount++;
|
||||
List<EditorArrowConfig> canRemove = new List<EditorArrowConfig>();
|
||||
|
||||
foreach (var arrow in remaining)
|
||||
{
|
||||
if (CanArrowFlyOut(arrow, occupied, rows, cols))
|
||||
{
|
||||
canRemove.Add(arrow);
|
||||
}
|
||||
}
|
||||
|
||||
if (canRemove.Count == 0)
|
||||
return false; // 死局
|
||||
|
||||
// 消除第一个可消除的箭头,释放占用
|
||||
var toRemove = canRemove[0];
|
||||
ReleaseOccupied(toRemove, occupied, cols);
|
||||
remaining.Remove(toRemove);
|
||||
}
|
||||
|
||||
return remaining.Count == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断箭头是否能直接飞出(前方无阻挡)
|
||||
/// </summary>
|
||||
private static bool CanArrowFlyOut(EditorArrowConfig arrow, bool[,] occupied, int rows, int cols)
|
||||
{
|
||||
// 获取最终射出方向
|
||||
string finalDir = arrow.path[arrow.path.Count - 1];
|
||||
|
||||
// 获取箭头头部位置(路径终点)
|
||||
int pointId = arrow.startPoint;
|
||||
int row = pointId / cols;
|
||||
int col = pointId % cols;
|
||||
|
||||
foreach (string dir in arrow.path)
|
||||
{
|
||||
(row, col) = MoveDir(row, col, dir);
|
||||
}
|
||||
|
||||
// 沿射出方向一路检查到边界
|
||||
while (true)
|
||||
{
|
||||
(row, col) = MoveDir(row, col, finalDir);
|
||||
|
||||
if (row < 0 || row >= rows || col < 0 || col >= cols)
|
||||
return true; // 飞出边界
|
||||
|
||||
if (occupied[row, col])
|
||||
return false; // 前方有阻挡
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放箭头占用
|
||||
/// </summary>
|
||||
private static void ReleaseOccupied(EditorArrowConfig arrow, bool[,] occupied, int totalCols)
|
||||
{
|
||||
int pointId = arrow.startPoint;
|
||||
int row = pointId / totalCols;
|
||||
int col = pointId % totalCols;
|
||||
occupied[row, col] = false;
|
||||
|
||||
foreach (string dir in arrow.path)
|
||||
{
|
||||
(row, col) = MoveDir(row, col, dir);
|
||||
occupied[row, col] = false;
|
||||
}
|
||||
}
|
||||
|
||||
#region 工具方法
|
||||
private static (int r, int c) MoveDir(int row, int col, string dir)
|
||||
{
|
||||
switch (dir)
|
||||
{
|
||||
case "up": return (row - 1, col);
|
||||
case "down": return (row + 1, col);
|
||||
case "left": return (row, col - 1);
|
||||
case "right": return (row, col + 1);
|
||||
default: return (row, col);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetOppositeDir(string dir)
|
||||
{
|
||||
switch (dir)
|
||||
{
|
||||
case "up": return "down";
|
||||
case "down": return "up";
|
||||
case "left": return "right";
|
||||
case "right": return "left";
|
||||
default: return dir;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1de2afcf128468783594983d832f7a2
|
||||
timeCreated: 1781601376
|
||||
@@ -1,106 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using ChillConnect;
|
||||
namespace ChillConnect.Editor
|
||||
{
|
||||
public class ArrowLevelGeneratorWindow : EditorWindow
|
||||
{
|
||||
private int _startLevelId = 1001;
|
||||
private int _generateCount = 100;
|
||||
private string _outputPath = "Assets/StreamingAssets/all_levels.json";
|
||||
private Vector2 _scrollPos;
|
||||
private List<EditorLevelConfig> _generatedLevels = new List<EditorLevelConfig>();
|
||||
|
||||
[MenuItem("Tools/箭头游戏/关卡生成器")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
GetWindow<ArrowLevelGeneratorWindow>("箭头关卡生成器");
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
GUILayout.Label("基础设置", EditorStyles.boldLabel);
|
||||
_startLevelId = EditorGUILayout.IntField("起始关卡ID", _startLevelId);
|
||||
_generateCount = EditorGUILayout.IntSlider("生成关卡数量", _generateCount, 1, 200);
|
||||
_outputPath = EditorGUILayout.TextField("输出路径", _outputPath);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (GUILayout.Button("一键生成 100 关(含预设难度+图案关)", GUILayout.Height(40)))
|
||||
{
|
||||
GenerateAllLevels();
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (GUILayout.Button("导出到 JSON 文件"))
|
||||
{
|
||||
ExportToJson();
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUILayout.Label($"已生成关卡数:{_generatedLevels.Count}", EditorStyles.boldLabel);
|
||||
|
||||
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
|
||||
foreach (var level in _generatedLevels)
|
||||
{
|
||||
EditorGUILayout.LabelField(
|
||||
$"关卡 {level.levelId} | {level.gridRows}x{level.gridCols} | 箭头数:{level.arrows.Count}");
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void GenerateAllLevels()
|
||||
{
|
||||
_generatedLevels.Clear();
|
||||
int levelId = _startLevelId;
|
||||
|
||||
for (int i = 0; i < _generateCount; i++)
|
||||
{
|
||||
// 获取当前关卡难度预设
|
||||
var preset = LevelDifficultyPreset.GetPreset(i + 1);
|
||||
|
||||
// 生成关卡
|
||||
var level = ArrowLevelGenerator.GenerateLevel(levelId, preset);
|
||||
|
||||
if (level != null)
|
||||
{
|
||||
_generatedLevels.Add(level);
|
||||
Debug.Log($"生成成功:关卡 {levelId},箭头数 {level.arrows.Count}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"关卡 {levelId} 生成失败,已跳过");
|
||||
}
|
||||
|
||||
levelId++;
|
||||
}
|
||||
|
||||
EditorUtility.DisplayDialog("生成完成",
|
||||
$"成功生成 {_generatedLevels.Count} 个关卡", "确定");
|
||||
}
|
||||
|
||||
private void ExportToJson()
|
||||
{
|
||||
if (_generatedLevels.Count == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("提示", "请先生成关卡", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
var root = new EditorAllLevelRoot { levels = _generatedLevels };
|
||||
string json = JsonUtility.ToJson(root, true);
|
||||
|
||||
string fullPath = Path.Combine(Application.dataPath,
|
||||
_outputPath.Replace("Assets/", ""));
|
||||
|
||||
File.WriteAllText(fullPath, json);
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
EditorUtility.DisplayDialog("导出成功",
|
||||
$"已导出到:{fullPath}", "确定");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 338d551bc0c34285a64c42de35c20a1b
|
||||
timeCreated: 1781601358
|
||||
@@ -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
|
||||
@@ -1,158 +0,0 @@
|
||||
using ChillConnect;
|
||||
namespace ChillConnect.Editor
|
||||
{
|
||||
[System.Serializable]
|
||||
public class LevelDifficultyPreset
|
||||
{
|
||||
public int gridRows;
|
||||
public int gridCols;
|
||||
public int arrowCount; // 目标箭头数
|
||||
public int minArrowCount; // 最小接受箭头数
|
||||
public int minPathLength; // 单箭头最小长度(格数)
|
||||
public int maxPathLength; // 单箭头最大长度
|
||||
public int maxTurnCount; // 最大折数
|
||||
public float turnProbability; // 每步转向概率
|
||||
public bool isPatternLevel; // 是否图案关
|
||||
|
||||
/// <summary>
|
||||
/// 根据关卡序号(从1开始)获取对应难度预设
|
||||
/// </summary>
|
||||
public static LevelDifficultyPreset GetPreset(int levelIndex)
|
||||
{
|
||||
LevelDifficultyPreset preset = new LevelDifficultyPreset();
|
||||
|
||||
// ===== 第1-2关:起步适应 8x8 =====
|
||||
if (levelIndex <= 2)
|
||||
{
|
||||
preset.gridRows = 8;
|
||||
preset.gridCols = 8;
|
||||
preset.arrowCount = 7;
|
||||
preset.minArrowCount = 5;
|
||||
preset.minPathLength = 2;
|
||||
preset.maxPathLength = 4;
|
||||
preset.maxTurnCount = 0; // 纯直线
|
||||
preset.turnProbability = 0f;
|
||||
preset.isPatternLevel = false;
|
||||
}
|
||||
// ===== 第3关:难度突变 13x13 =====
|
||||
else if (levelIndex == 3)
|
||||
{
|
||||
preset.gridRows = 13;
|
||||
preset.gridCols = 13;
|
||||
preset.arrowCount = 14;
|
||||
preset.minArrowCount = 10;
|
||||
preset.minPathLength = 2;
|
||||
preset.maxPathLength = 5;
|
||||
preset.maxTurnCount = 0;
|
||||
preset.turnProbability = 0f;
|
||||
preset.isPatternLevel = false;
|
||||
}
|
||||
// ===== 第4关:第一个图案关 =====
|
||||
else if (levelIndex == 4)
|
||||
{
|
||||
preset.gridRows = 13;
|
||||
preset.gridCols = 13;
|
||||
preset.arrowCount = 12;
|
||||
preset.minArrowCount = 10;
|
||||
preset.minPathLength = 2;
|
||||
preset.maxPathLength = 6;
|
||||
preset.maxTurnCount = 0;
|
||||
preset.turnProbability = 0f;
|
||||
preset.isPatternLevel = true;
|
||||
}
|
||||
// ===== 第5-10关:快速跃升期 =====
|
||||
else if (levelIndex <= 10)
|
||||
{
|
||||
preset.gridRows = 13;
|
||||
preset.gridCols = 13;
|
||||
preset.arrowCount = 14 + (levelIndex - 5) * 1;
|
||||
preset.minArrowCount = preset.arrowCount - 3;
|
||||
preset.minPathLength = 2;
|
||||
preset.maxPathLength = 6;
|
||||
preset.maxTurnCount = 1;
|
||||
preset.turnProbability = 0.15f;
|
||||
preset.isPatternLevel = levelIndex == 8;
|
||||
}
|
||||
// ===== 第11-15关:进阶爬坡 =====
|
||||
else if (levelIndex <= 15)
|
||||
{
|
||||
preset.gridRows = 13;
|
||||
preset.gridCols = 13;
|
||||
preset.arrowCount = 20 + (levelIndex - 11);
|
||||
preset.minArrowCount = preset.arrowCount - 3;
|
||||
preset.minPathLength = 2;
|
||||
preset.maxPathLength = 7;
|
||||
preset.maxTurnCount = 1;
|
||||
preset.turnProbability = 0.2f;
|
||||
preset.isPatternLevel = levelIndex == 13;
|
||||
}
|
||||
// ===== 第15关突变:折数提升 =====
|
||||
else if (levelIndex <= 25)
|
||||
{
|
||||
preset.gridRows = 13;
|
||||
preset.gridCols = 13;
|
||||
preset.arrowCount = 22 + (levelIndex - 15);
|
||||
preset.minArrowCount = preset.arrowCount - 4;
|
||||
preset.minPathLength = 2;
|
||||
preset.maxPathLength = 7;
|
||||
preset.maxTurnCount = 2;
|
||||
preset.turnProbability = 0.25f;
|
||||
preset.isPatternLevel = levelIndex == 19;
|
||||
}
|
||||
// ===== 第26-35关 =====
|
||||
else if (levelIndex <= 35)
|
||||
{
|
||||
preset.gridRows = 14;
|
||||
preset.gridCols = 14;
|
||||
preset.arrowCount = 25 + (levelIndex - 26);
|
||||
preset.minArrowCount = preset.arrowCount - 4;
|
||||
preset.minPathLength = 2;
|
||||
preset.maxPathLength = 8;
|
||||
preset.maxTurnCount = 2;
|
||||
preset.turnProbability = 0.28f;
|
||||
preset.isPatternLevel = levelIndex == 26 || levelIndex == 33;
|
||||
}
|
||||
// ===== 第36-50关:高难起步 =====
|
||||
else if (levelIndex <= 50)
|
||||
{
|
||||
preset.gridRows = 14;
|
||||
preset.gridCols = 14;
|
||||
preset.arrowCount = 28 + (levelIndex - 36);
|
||||
preset.minArrowCount = preset.arrowCount - 5;
|
||||
preset.minPathLength = 2;
|
||||
preset.maxPathLength = 8;
|
||||
preset.maxTurnCount = 2;
|
||||
preset.turnProbability = 0.3f;
|
||||
preset.isPatternLevel = levelIndex == 41 || levelIndex == 49;
|
||||
}
|
||||
// ===== 第50关突变:15x15 =====
|
||||
else if (levelIndex <= 75)
|
||||
{
|
||||
preset.gridRows = 15;
|
||||
preset.gridCols = 15;
|
||||
preset.arrowCount = 30 + (levelIndex - 50) / 2;
|
||||
preset.minArrowCount = preset.arrowCount - 5;
|
||||
preset.minPathLength = 2;
|
||||
preset.maxPathLength = 9;
|
||||
preset.maxTurnCount = 3;
|
||||
preset.turnProbability = 0.32f;
|
||||
preset.isPatternLevel = levelIndex == 57 || levelIndex == 65 || levelIndex == 72;
|
||||
}
|
||||
// ===== 第76-100关:终极挑战 16x16 =====
|
||||
else
|
||||
{
|
||||
preset.gridRows = 16;
|
||||
preset.gridCols = 16;
|
||||
preset.arrowCount = 32 + (levelIndex - 75) / 3;
|
||||
preset.minArrowCount = preset.arrowCount - 6;
|
||||
preset.minPathLength = 2;
|
||||
preset.maxPathLength = 9;
|
||||
preset.maxTurnCount = 3;
|
||||
preset.turnProbability = 0.35f;
|
||||
preset.isPatternLevel = levelIndex == 80 || levelIndex == 87 || levelIndex == 93 || levelIndex == 100;
|
||||
}
|
||||
|
||||
return preset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67fd199fe89b44419a7b51e08285ba5a
|
||||
timeCreated: 1781601394
|
||||
Reference in New Issue
Block a user