Files
ArrowBeatTap-Gp/Assets/Editor/ArrowLevelAutoGenerator.cs
T

363 lines
13 KiB
C#
Raw Normal View History

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