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

363 lines
13 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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";
}
}