495 lines
19 KiB
C#
495 lines
19 KiB
C#
// 文件名: 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} 个关卡", "确定");
|
||
}
|
||
}
|
||
} |