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";
|
||
}
|
||
} |