363 lines
13 KiB
C#
363 lines
13 KiB
C#
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";
|
|||
|
|
}
|
|||
|
|
}
|