NLDClient-yudde/ProjectNLD/Assets/Editor/PVPDebugBattle/PVPDebugBattleWindow.cs

1249 lines
46 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Collections.Generic;
using Framework;
using Framework.Wrapper;
using Gameplay;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
namespace NLD.Editor.PVPDebugBattle
{
public static class PVPDebugBattleCellPickerUtility
{
public static HashSet<int> GetUsedCells(List<PVPDebugBattleUnit> units, int currentIndex)
{
var usedCells = new HashSet<int>();
if (units == null)
return usedCells;
for (int i = 0; i < units.Count; i++)
{
if (i == currentIndex)
continue;
var unit = units[i];
if (unit == null || unit.cellIndex < 0)
continue;
usedCells.Add(unit.cellIndex);
}
return usedCells;
}
public static Vector2 GetFlatTopHexCenter(int row, int column, float hexWidth, float hexHeight)
{
var x = hexWidth * 0.5f + column * hexWidth * 0.75f;
var y = hexHeight * 0.5f + row * hexHeight;
if ((column & 1) != 0)
y += hexHeight * 0.5f;
return new Vector2(x, y);
}
}
public class PVPDebugBattleWindow : OdinEditorWindow
{
private const string DefaultPresetFolder = "Assets/Config/PVPDebugBattle";
private const string LevelConfigFormatPath = "Assets/Config/Levels/{0}.json";
private const string MapConfigFormatPath = "Assets/Config/Maps/{0}.json";
private const float UnitIconSize = 36f;
[SerializeField]
private PVPDebugBattlePreset preset;
private Vector2 _scrollPosition;
private string _status = "请选择或创建 PVP 调试预设";
[MenuItem("Tools/*战斗工具/PVP调试战斗")]
public static void ShowWindow()
{
var window = GetWindow<PVPDebugBattleWindow>("PVP调试战斗");
window.minSize = new Vector2(760, 640);
window.Show();
}
protected override void OnGUI()
{
EditorGUILayout.Space(6f);
DrawToolbar();
EditorGUILayout.Space(4f);
EditorGUILayout.HelpBox(_status, MessageType.Info);
if (preset == null)
{
EditorGUILayout.HelpBox("请先选择或创建一个 PVPDebugBattlePreset。", MessageType.Warning);
return;
}
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
DrawPresetFields();
EditorGUILayout.EndScrollView();
}
private void DrawToolbar()
{
EditorGUILayout.BeginHorizontal();
preset = (PVPDebugBattlePreset)EditorGUILayout.ObjectField("调试预设", preset,
typeof(PVPDebugBattlePreset), false);
if (GUILayout.Button("创建新预设", GUILayout.Width(100f)))
CreatePreset();
if (GUILayout.Button("校验", GUILayout.Width(64f)))
ValidatePreset();
GUI.enabled = EditorApplication.isPlaying;
if (GUILayout.Button("进入PVP调试", GUILayout.Width(116f)))
EnterPVPDebugBattle();
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
if (!EditorApplication.isPlaying)
EditorGUILayout.HelpBox("进入战斗按钮仅在 Play Mode 可用;预设编辑可以在非运行时完成。", MessageType.None);
}
private void DrawPresetFields()
{
EditorGUI.BeginChangeCheck();
var levelId = EditorGUILayout.IntField("关卡ID覆盖", preset.levelIdOverride);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(preset, "Edit PVP Debug Level");
preset.levelIdOverride = Math.Max(0, levelId);
EditorUtility.SetDirty(preset);
}
EditorGUILayout.Space(8f);
DrawUnitList("己方阵容", preset.selfUnits);
EditorGUILayout.Space(8f);
DrawUnitList("敌方阵容", preset.enemyUnits);
}
private void DrawUnitList(string title, List<PVPDebugBattleUnit> units)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(title, EditorStyles.boldLabel);
if (GUILayout.Button("添加角色", GUILayout.Width(86f)))
OpenPicker(Unit.UnitType.Charactor, unitId => AddUnit(units, PVPDebugBattleUnit.CreateCharacter(unitId)));
if (GUILayout.Button("添加载具", GUILayout.Width(86f)))
OpenPicker(Unit.UnitType.Vehicle, unitId => AddUnit(units, PVPDebugBattleUnit.CreateVehicle(unitId)));
EditorGUILayout.EndHorizontal();
if (units == null || units.Count == 0)
{
EditorGUILayout.HelpBox("当前阵容为空。", MessageType.None);
EditorGUILayout.EndVertical();
return;
}
for (int i = 0; i < units.Count; i++)
{
DrawUnitItem(units, i);
if (i < units.Count - 1)
EditorGUILayout.Space(4f);
}
EditorGUILayout.EndVertical();
}
private void DrawUnitItem(List<PVPDebugBattleUnit> units, int index)
{
var unit = units[index];
if (unit == null)
{
EditorGUILayout.HelpBox("单位数据为空。", MessageType.Error);
if (GUILayout.Button("移除空单位"))
RemoveUnit(units, index);
return;
}
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"{index + 1}. {GetUnitTypeName(unit.unitType)} {unit.unitID} {GetUnitName(unit)}",
EditorStyles.boldLabel);
if (GUILayout.Button("选择", GUILayout.Width(58f)))
OpenPicker(unit.unitType, unitId => SetUnitId(unit, unitId));
if (GUILayout.Button("删除", GUILayout.Width(58f)))
{
RemoveUnit(units, index);
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
return;
}
EditorGUILayout.EndHorizontal();
if (!IsSupportedUnitType(unit.unitType))
{
EditorGUILayout.HelpBox("仅支持角色和载具,请删除后重新添加。", MessageType.Error);
EditorGUILayout.EndVertical();
return;
}
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginChangeCheck();
var nextCellIndex = EditorGUILayout.IntField("Cell Index", unit.cellIndex);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(preset, "Edit PVP Debug Cell");
unit.cellIndex = nextCellIndex;
EditorUtility.SetDirty(preset);
}
if (GUILayout.Button("地图选择", GUILayout.Width(78f)))
OpenCellPicker(units, index, unit);
EditorGUILayout.EndHorizontal();
if (unit.unitType == Unit.UnitType.Charactor)
DrawCharacterCultivate(unit);
else
DrawVehicleCultivate(unit);
EditorGUILayout.EndVertical();
}
private void DrawCharacterCultivate(PVPDebugBattleUnit unit)
{
EditorGUILayout.LabelField("角色养成", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
var level = UIntField("Level", unit.level);
var breakLevel = UIntField("Break Level", unit.breakLevel);
var awakeLevel = UIntField("Awake Level", unit.awakeLevel);
var skillLevel0 = UIntField("Skill Level 0", unit.skillLevel0);
var skillLevel1 = UIntField("Skill Level 1", unit.skillLevel1);
var skillLevel2 = UIntField("Skill Level 2", unit.skillLevel2);
EditorGUILayout.BeginHorizontal();
var skinId = UIntField("Skin Id", unit.skinId);
if (GUILayout.Button("选择", GUILayout.Width(58f)))
OpenSkinPicker(unit);
EditorGUILayout.EndHorizontal();
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(preset, "Edit PVP Debug Character");
unit.level = level;
unit.breakLevel = breakLevel;
unit.awakeLevel = awakeLevel;
unit.skillLevel0 = skillLevel0;
unit.skillLevel1 = skillLevel1;
unit.skillLevel2 = skillLevel2;
unit.skinId = skinId;
EditorUtility.SetDirty(preset);
}
}
private void OpenSkinPicker(PVPDebugBattleUnit unit)
{
if (unit == null || unit.unitType != Unit.UnitType.Charactor)
return;
if (unit.unitID == Team.NoneID)
{
EditorUtility.DisplayDialog("PVP调试战斗", "请先选择角色,再选择皮肤。", "确定");
return;
}
PVPDebugBattleSkinPickerWindow.Open(unit.unitID, skinId => SetCharacterSkin(unit, skinId));
}
private void DrawVehicleCultivate(PVPDebugBattleUnit unit)
{
EditorGUILayout.LabelField("载具养成", EditorStyles.boldLabel);
DrawLongList("Vehicle Components", unit.vehicleComponents);
DrawUIntList("Paint Set", unit.paintSet);
}
private void DrawLongList(string label, List<long> values)
{
if (values == null)
return;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, EditorStyles.boldLabel);
if (GUILayout.Button("+", GUILayout.Width(24f)))
{
Undo.RecordObject(preset, "Add PVP Debug Vehicle Value");
values.Add(0);
EditorUtility.SetDirty(preset);
}
EditorGUILayout.EndHorizontal();
for (int i = 0; i < values.Count; i++)
{
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginChangeCheck();
var next = EditorGUILayout.LongField(i.ToString(), values[i]);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(preset, "Edit PVP Debug Vehicle Value");
values[i] = next;
EditorUtility.SetDirty(preset);
}
if (GUILayout.Button("-", GUILayout.Width(24f)))
{
Undo.RecordObject(preset, "Remove PVP Debug Vehicle Value");
values.RemoveAt(i);
EditorUtility.SetDirty(preset);
EditorGUILayout.EndHorizontal();
break;
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
}
private void DrawUIntList(string label, List<uint> values)
{
if (values == null)
return;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(label, EditorStyles.boldLabel);
if (GUILayout.Button("+", GUILayout.Width(24f)))
{
Undo.RecordObject(preset, "Add PVP Debug Paint");
values.Add(0);
EditorUtility.SetDirty(preset);
}
EditorGUILayout.EndHorizontal();
for (int i = 0; i < values.Count; i++)
{
EditorGUILayout.BeginHorizontal();
EditorGUI.BeginChangeCheck();
var next = UIntField(i.ToString(), values[i]);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(preset, "Edit PVP Debug Paint");
values[i] = next;
EditorUtility.SetDirty(preset);
}
if (GUILayout.Button("-", GUILayout.Width(24f)))
{
Undo.RecordObject(preset, "Remove PVP Debug Paint");
values.RemoveAt(i);
EditorUtility.SetDirty(preset);
EditorGUILayout.EndHorizontal();
break;
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
}
private void OpenPicker(Unit.UnitType unitType, Action<uint> onSelected)
{
if (!IsSupportedUnitType(unitType))
{
EditorUtility.DisplayDialog("PVP调试战斗", "仅支持角色和载具。", "确定");
return;
}
PVPDebugBattleUnitPickerWindow.Open(unitType, onSelected);
}
private void AddUnit(List<PVPDebugBattleUnit> units, PVPDebugBattleUnit unit)
{
if (units == null || unit == null)
return;
Undo.RecordObject(preset, "Add PVP Debug Unit");
units.Add(unit);
EditorUtility.SetDirty(preset);
Repaint();
}
private void RemoveUnit(List<PVPDebugBattleUnit> units, int index)
{
if (units == null || index < 0 || index >= units.Count)
return;
Undo.RecordObject(preset, "Remove PVP Debug Unit");
units.RemoveAt(index);
EditorUtility.SetDirty(preset);
Repaint();
}
private void SetUnitId(PVPDebugBattleUnit unit, uint unitId)
{
if (unit == null)
return;
Undo.RecordObject(preset, "Select PVP Debug Unit");
unit.unitID = unitId;
if (unit.unitType == Unit.UnitType.Charactor)
unit.skinId = 1;
EditorUtility.SetDirty(preset);
Repaint();
}
private void SetCharacterSkin(PVPDebugBattleUnit unit, uint skinId)
{
if (unit == null)
return;
Undo.RecordObject(preset, "Select PVP Debug Skin");
unit.skinId = skinId;
EditorUtility.SetDirty(preset);
Repaint();
}
private void OpenCellPicker(List<PVPDebugBattleUnit> units, int index, PVPDebugBattleUnit unit)
{
if (preset == null || unit == null)
return;
var levelId = ResolvePresetLevelId();
var usedCells = PVPDebugBattleCellPickerUtility.GetUsedCells(units, index);
PVPDebugBattleCellPickerWindow.Open(levelId, unit.cellIndex, usedCells, cellIndex => SetCellIndex(unit, cellIndex));
}
private int ResolvePresetLevelId()
{
if (preset != null && preset.levelIdOverride > 0)
return preset.levelIdOverride;
try
{
return EditorTableManager.instance.tables.GlobalConfig.PVPLevelID;
}
catch (Exception)
{
return 0;
}
}
private void SetCellIndex(PVPDebugBattleUnit unit, int cellIndex)
{
if (unit == null)
return;
Undo.RecordObject(preset, "Select PVP Debug Cell");
unit.cellIndex = cellIndex;
EditorUtility.SetDirty(preset);
Repaint();
}
private void CreatePreset()
{
EnsureFolder(DefaultPresetFolder);
var asset = ScriptableObject.CreateInstance<PVPDebugBattlePreset>();
asset.name = "PVPDebugBattlePreset";
var path = AssetDatabase.GenerateUniqueAssetPath(DefaultPresetFolder + "/PVPDebugBattlePreset.asset");
AssetDatabase.CreateAsset(asset, path);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
preset = asset;
Selection.activeObject = asset;
_status = "已创建预设: " + path;
}
private void ValidatePreset()
{
if (preset == null)
{
_status = "未选择预设";
EditorUtility.DisplayDialog("PVP调试战斗", _status, "确定");
return;
}
if (preset.Validate(out var error))
{
_status = "预设校验通过";
EditorUtility.DisplayDialog("PVP调试战斗", _status, "确定");
}
else
{
_status = error;
EditorUtility.DisplayDialog("PVP调试战斗", "预设校验失败:\n" + error, "确定");
}
}
private void EnterPVPDebugBattle()
{
if (!EditorApplication.isPlaying)
{
_status = "请先运行游戏,再进入 PVP 调试战斗";
EditorUtility.DisplayDialog("PVP调试战斗", _status, "确定");
return;
}
if (preset == null)
{
_status = "未选择预设";
EditorUtility.DisplayDialog("PVP调试战斗", _status, "确定");
return;
}
if (PVPDebugBattleLauncher.TryEnter(preset, out var error))
{
_status = "已进入 PVP 调试战斗";
return;
}
_status = error;
EditorUtility.DisplayDialog("PVP调试战斗", "进入失败:\n" + error, "确定");
}
private static bool IsSupportedUnitType(Unit.UnitType unitType)
{
return unitType == Unit.UnitType.Charactor || unitType == Unit.UnitType.Vehicle;
}
private static uint UIntField(string label, uint value)
{
var next = EditorGUILayout.LongField(label, value);
if (next < 0)
return 0;
if (next > uint.MaxValue)
return uint.MaxValue;
return (uint)next;
}
private static string GetUnitTypeName(Unit.UnitType unitType)
{
switch (unitType)
{
case Unit.UnitType.Charactor:
return "角色";
case Unit.UnitType.Vehicle:
return "载具";
default:
return "不支持";
}
}
private static string GetUnitName(PVPDebugBattleUnit unit)
{
if (unit == null || TableManager.Instance?.Tables == null)
return string.Empty;
try
{
if (unit.unitType == Unit.UnitType.Charactor)
{
var cfg = TableManager.Instance.Tables.CharacterAttri.GetOrDefault((int)unit.unitID);
return cfg == null ? string.Empty : GetCharacterDisplayName(cfg);
}
if (unit.unitType == Unit.UnitType.Vehicle)
{
var cfg = TableManager.Instance.Tables.VehicleConfig.GetOrDefault((int)unit.unitID);
return cfg == null ? string.Empty : cfg.Name;
}
}
catch (Exception)
{
return string.Empty;
}
return string.Empty;
}
private static string GetCharacterDisplayName(cfg.CharacterCfg.DataCharacterAttri cfg)
{
if (cfg == null)
return string.Empty;
var name = SafeGetCharacterName(cfg.RoleID);
if (!string.IsNullOrEmpty(name))
return name;
if (!string.IsNullOrEmpty(cfg.NameRead))
return cfg.NameRead;
return cfg.NameID;
}
private static string SafeGetCharacterName(int roleId)
{
try
{
return CommonUtils.GetCharacterName(roleId);
}
catch (Exception)
{
return string.Empty;
}
}
private static void EnsureFolder(string folder)
{
if (AssetDatabase.IsValidFolder(folder))
return;
var parts = folder.Split('/');
var current = parts[0];
for (int i = 1; i < parts.Length; i++)
{
var next = current + "/" + parts[i];
if (!AssetDatabase.IsValidFolder(next))
AssetDatabase.CreateFolder(current, parts[i]);
current = next;
}
}
private sealed class PVPDebugBattleCellPickerWindow : EditorWindow
{
private const float MinHexWidth = 28f;
private const float MaxHexWidth = 48f;
private const float GridPadding = 12f;
private const float HexHeightScale = 0.8660254f;
private readonly Color _normalColor = new Color(0.24f, 0.24f, 0.24f, 1f);
private readonly Color _prepareColor = new Color(0.22f, 0.46f, 0.24f, 1f);
private readonly Color _defendColor = new Color(0.22f, 0.42f, 0.58f, 1f);
private readonly Color _sharedRegionColor = new Color(0.24f, 0.46f, 0.48f, 1f);
private readonly Color _usedColor = new Color(0.72f, 0.43f, 0.16f, 1f);
private readonly Color _selectedColor = new Color(0.96f, 0.78f, 0.22f, 1f);
private readonly Color _borderColor = new Color(0.08f, 0.08f, 0.08f, 1f);
private int _levelId;
private int _currentCellIndex;
private HashSet<int> _usedCells = new HashSet<int>();
private Action<int> _onSelected;
private Vector2 _scrollPosition;
private string _error = string.Empty;
private MapData _mapData;
private Gameplay.Level.LevelData _levelData;
private HashSet<int> _prepareCells = new HashSet<int>();
private HashSet<int> _defendCells = new HashSet<int>();
private GUIStyle _cellLabelStyle;
private GUIStyle _legendLabelStyle;
public static void Open(int levelId, int currentCellIndex, HashSet<int> usedCells, Action<int> onSelected)
{
var window = CreateInstance<PVPDebugBattleCellPickerWindow>();
window._levelId = levelId;
window._currentCellIndex = currentCellIndex;
window._usedCells = usedCells ?? new HashSet<int>();
window._onSelected = onSelected;
window.titleContent = new GUIContent("选择 CellIndex");
window.minSize = new Vector2(560f, 560f);
window.LoadData();
window.ShowUtility();
}
private void OnGUI()
{
EnsureStyles();
EditorGUILayout.Space(6f);
DrawHeader();
if (!string.IsNullOrEmpty(_error))
{
EditorGUILayout.HelpBox(_error, MessageType.Error);
if (GUILayout.Button("刷新"))
LoadData();
return;
}
DrawLegend();
DrawGrid();
}
private void DrawHeader()
{
EditorGUILayout.LabelField("关卡ID", _levelId.ToString());
if (_mapData != null)
{
EditorGUILayout.LabelField("地图尺寸", _mapData.Width + " x " + _mapData.Height);
EditorGUILayout.LabelField("当前 Cell", _currentCellIndex.ToString());
}
}
private void DrawLegend()
{
EditorGUILayout.Space(4f);
EditorGUILayout.BeginHorizontal();
DrawLegendItem(_selectedColor, "当前");
DrawLegendItem(_usedColor, "同阵营已占用");
DrawLegendItem(_prepareColor, "准备区");
DrawLegendItem(_defendColor, "PVP防守区");
DrawLegendItem(_normalColor, "普通");
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(4f);
}
private void DrawLegendItem(Color color, string label)
{
var rect = GUILayoutUtility.GetRect(14f, 14f, GUILayout.Width(14f), GUILayout.Height(14f));
EditorGUI.DrawRect(rect, color);
GUILayout.Label(label, _legendLabelStyle, GUILayout.Width(92f));
}
private void DrawGrid()
{
if (_mapData == null)
return;
var width = _mapData.Width;
var height = _mapData.Height;
if (width <= 0 || height <= 0)
return;
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
var availableWidth = Mathf.Max(position.width - GridPadding * 2f, width * MinHexWidth);
var hexWidth = Mathf.Clamp(availableWidth / (1f + (width - 1) * 0.75f), MinHexWidth, MaxHexWidth);
var hexHeight = hexWidth * HexHeightScale;
var gridWidth = hexWidth * (1f + (width - 1) * 0.75f);
var gridHeight = hexHeight * (height + 0.5f);
var gridRect = GUILayoutUtility.GetRect(gridWidth, gridHeight, GUILayout.ExpandWidth(false));
for (int row = 0; row < height; row++)
{
for (int column = 0; column < width; column++)
{
var cellIndex = row * width + column;
var center = PVPDebugBattleCellPickerUtility.GetFlatTopHexCenter(row, column, hexWidth, hexHeight);
center += gridRect.position;
DrawCell(center, cellIndex, hexWidth, hexHeight);
}
}
EditorGUILayout.EndScrollView();
}
private void DrawCell(Vector2 center, int cellIndex, float hexWidth, float hexHeight)
{
var points = GetFlatTopHexPoints(center, hexWidth, hexHeight);
var previousColor = Handles.color;
Handles.color = GetCellColor(cellIndex);
Handles.DrawAAConvexPolygon(points);
Handles.color = _borderColor;
Handles.DrawAAPolyLine(1.5f, points[0], points[1], points[2], points[3], points[4], points[5], points[0]);
Handles.color = previousColor;
if (hexWidth >= 34f)
{
var labelRect = new Rect(center.x - hexWidth * 0.3f, center.y - 8f, hexWidth * 0.6f, 16f);
GUI.Label(labelRect, cellIndex.ToString(), _cellLabelStyle);
}
var evt = Event.current;
if (evt.type == EventType.MouseDown && evt.button == 0 && ContainsPoint(points, evt.mousePosition))
{
_onSelected?.Invoke(cellIndex);
evt.Use();
Close();
}
}
private static Vector3[] GetFlatTopHexPoints(Vector2 center, float hexWidth, float hexHeight)
{
var halfWidth = hexWidth * 0.5f;
var quarterWidth = hexWidth * 0.25f;
var halfHeight = hexHeight * 0.5f;
return new[]
{
new Vector3(center.x - halfWidth, center.y, 0f),
new Vector3(center.x - quarterWidth, center.y - halfHeight, 0f),
new Vector3(center.x + quarterWidth, center.y - halfHeight, 0f),
new Vector3(center.x + halfWidth, center.y, 0f),
new Vector3(center.x + quarterWidth, center.y + halfHeight, 0f),
new Vector3(center.x - quarterWidth, center.y + halfHeight, 0f)
};
}
private static bool ContainsPoint(Vector3[] polygon, Vector2 point)
{
var inside = false;
for (int i = 0, j = polygon.Length - 1; i < polygon.Length; j = i++)
{
var pi = polygon[i];
var pj = polygon[j];
if (((pi.y > point.y) != (pj.y > point.y)) &&
point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x)
{
inside = !inside;
}
}
return inside;
}
private Color GetCellColor(int cellIndex)
{
if (cellIndex == _currentCellIndex)
return _selectedColor;
if (_usedCells.Contains(cellIndex))
return _usedColor;
var inPrepare = _prepareCells.Contains(cellIndex);
var inDefend = _defendCells.Contains(cellIndex);
if (inPrepare && inDefend)
return _sharedRegionColor;
if (inPrepare)
return _prepareColor;
if (inDefend)
return _defendColor;
return _normalColor;
}
private void LoadData()
{
_error = string.Empty;
_mapData = null;
_levelData = null;
_prepareCells.Clear();
_defendCells.Clear();
if (_levelId <= 0)
{
_error = "PVP关卡ID无效无法加载地图。";
return;
}
try
{
var levelCfg = EditorTableManager.instance.tables.Level.GetOrDefault(_levelId);
if (levelCfg == null)
{
_error = "找不到关卡配置Level ID: " + _levelId;
return;
}
if (string.IsNullOrEmpty(levelCfg.LevelData) || string.IsNullOrEmpty(levelCfg.MapData))
{
_error = "关卡配置缺少 LevelData 或 MapData 文件名Level ID: " + _levelId;
return;
}
var levelPath = string.Format(LevelConfigFormatPath, levelCfg.LevelData);
var mapPath = string.Format(MapConfigFormatPath, levelCfg.MapData);
var levelAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(levelPath);
var mapAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(mapPath);
if (levelAsset == null)
{
_error = "加载 LevelData 失败: " + levelPath;
return;
}
if (mapAsset == null)
{
_error = "加载 MapData 失败: " + mapPath;
return;
}
_levelData = JsonUtility.FromJson<Gameplay.Level.LevelData>(levelAsset.text);
_mapData = JsonUtility.FromJson<MapData>(mapAsset.text);
if (_levelData == null)
{
_error = "解析 LevelData 失败: " + levelPath;
return;
}
if (_mapData == null || _mapData.Width <= 0 || _mapData.Height <= 0)
{
_error = "解析 MapData 失败或地图尺寸无效: " + mapPath;
_mapData = null;
return;
}
_prepareCells = BuildRegionSet(_mapData, _levelData.preparingRegionId);
_defendCells = BuildRegionSet(_mapData, _levelData.pvpDefendRegionId);
}
catch (Exception e)
{
_error = "加载地图数据异常: " + e.Message;
_mapData = null;
_levelData = null;
}
}
private static HashSet<int> BuildRegionSet(MapData mapData, int regionId)
{
var result = new HashSet<int>();
if (mapData == null || mapData.regions == null || regionId < 0 || regionId >= mapData.regions.Count)
return result;
var region = mapData.regions[regionId];
if (region == null || region.positions == null)
return result;
for (int i = 0; i < region.positions.Count; i++)
{
var cellIndex = region.positions[i];
if (cellIndex >= 0)
result.Add(cellIndex);
}
return result;
}
private void EnsureStyles()
{
if (_cellLabelStyle == null)
{
_cellLabelStyle = new GUIStyle(EditorStyles.miniLabel)
{
alignment = TextAnchor.MiddleCenter,
normal = { textColor = Color.white }
};
}
if (_legendLabelStyle == null)
{
_legendLabelStyle = new GUIStyle(EditorStyles.miniLabel)
{
alignment = TextAnchor.MiddleLeft
};
}
}
}
private sealed class PVPDebugBattleSkinPickerWindow : EditorWindow
{
private readonly List<SkinChoice> _choices = new List<SkinChoice>(16);
private uint _roleId;
private Action<uint> _onSelected;
private Vector2 _scrollPosition;
private string _searchText = string.Empty;
private string _error = string.Empty;
public static void Open(uint roleId, Action<uint> onSelected)
{
var window = CreateInstance<PVPDebugBattleSkinPickerWindow>();
window._roleId = roleId;
window._onSelected = onSelected;
window.titleContent = new GUIContent("选择皮肤");
window.minSize = new Vector2(520f, 520f);
window.BuildChoices();
window.ShowUtility();
}
private void OnGUI()
{
EditorGUILayout.Space(6f);
EditorGUILayout.LabelField("角色ID", _roleId.ToString());
_searchText = EditorGUILayout.TextField("搜索", _searchText);
if (!string.IsNullOrEmpty(_error))
{
EditorGUILayout.HelpBox(_error, MessageType.Warning);
if (GUILayout.Button("刷新"))
BuildChoices();
return;
}
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
for (int i = 0; i < _choices.Count; i++)
{
var choice = _choices[i];
if (!IsMatch(choice))
continue;
DrawChoice(choice);
}
EditorGUILayout.EndScrollView();
}
private void BuildChoices()
{
_choices.Clear();
_error = string.Empty;
if (TableManager.Instance?.Tables == null)
{
_error = "TableManager 尚未加载配置表。请在 Play Mode 且主流程初始化完成后再打开选择窗口。";
return;
}
var list = CommonUtils.GetCharacterSkinCfgList((int)_roleId);
for (int i = 0; i < list.Count; i++)
{
var cfg = list[i];
_choices.Add(new SkinChoice((uint)cfg.SkinID, GetSkinDisplayName(cfg),
SafeGetSkinIconPath(cfg.RoleID, cfg.SkinID)));
}
if (_choices.Count == 0)
_error = "找不到该角色的皮肤配置角色ID: " + _roleId;
}
private void DrawChoice(SkinChoice choice)
{
var rect = GUILayoutUtility.GetRect(0f, 48f, GUILayout.ExpandWidth(true));
if (Event.current.type == EventType.Repaint)
EditorStyles.helpBox.Draw(rect, GUIContent.none, false, false, false, false);
var iconRect = new Rect(rect.x + 6f, rect.y + 6f, UnitIconSize, UnitIconSize);
var icon = choice.GetIcon();
if (icon != null)
GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit);
else
GUI.Box(iconRect, GUIContent.none);
var textRect = new Rect(iconRect.xMax + 8f, rect.y + 5f, rect.width - iconRect.width - 22f, 18f);
EditorGUI.LabelField(textRect, "Skin " + choice.SkinId + " " + choice.Name, EditorStyles.boldLabel);
var pathRect = new Rect(textRect.x, textRect.yMax + 2f, textRect.width, 18f);
EditorGUI.LabelField(pathRect, choice.IconPath, EditorStyles.miniLabel);
if (GUI.Button(rect, GUIContent.none, GUIStyle.none))
{
_onSelected?.Invoke(choice.SkinId);
Close();
}
}
private bool IsMatch(SkinChoice choice)
{
if (string.IsNullOrEmpty(_searchText))
return true;
return choice.SkinId.ToString().Contains(_searchText) ||
(!string.IsNullOrEmpty(choice.Name) &&
choice.Name.IndexOf(_searchText, StringComparison.OrdinalIgnoreCase) >= 0);
}
private static string GetSkinDisplayName(cfg.CharacterCfg.DataCharacterSkin cfg)
{
if (cfg == null)
return string.Empty;
if (!string.IsNullOrEmpty(cfg.NickName))
return cfg.NickName;
if (!string.IsNullOrEmpty(cfg.Name))
return cfg.Name;
return cfg.NameID;
}
private static string SafeGetSkinIconPath(int roleId, int skinId)
{
try
{
return CommonUtils.GetCharacterPicPath(roleId, CommonUtils.ECharacterPicPathType.HeadPic, skinId);
}
catch (Exception)
{
return string.Empty;
}
}
private sealed class SkinChoice
{
public readonly uint SkinId;
public readonly string Name;
public readonly string IconPath;
private Texture2D _icon;
private bool _iconLoaded;
public SkinChoice(uint skinId, string name, string iconPath)
{
SkinId = skinId;
Name = name;
IconPath = iconPath;
}
public Texture2D GetIcon()
{
if (_iconLoaded)
return _icon;
_iconLoaded = true;
if (!string.IsNullOrEmpty(IconPath))
_icon = AssetDatabase.LoadAssetAtPath<Texture2D>(IconPath);
return _icon;
}
}
}
private sealed class PVPDebugBattleUnitPickerWindow : EditorWindow
{
private readonly List<UnitChoice> _choices = new List<UnitChoice>(256);
private Unit.UnitType _unitType;
private Action<uint> _onSelected;
private Vector2 _scrollPosition;
private string _searchText = string.Empty;
private string _error = string.Empty;
public static void Open(Unit.UnitType unitType, Action<uint> onSelected)
{
var window = CreateInstance<PVPDebugBattleUnitPickerWindow>();
window._unitType = unitType;
window._onSelected = onSelected;
window.titleContent = new GUIContent(unitType == Unit.UnitType.Charactor ? "选择角色" : "选择载具");
window.minSize = new Vector2(520f, 520f);
window.BuildChoices();
window.ShowUtility();
}
private void OnGUI()
{
EditorGUILayout.Space(6f);
_searchText = EditorGUILayout.TextField("搜索", _searchText);
if (!string.IsNullOrEmpty(_error))
{
EditorGUILayout.HelpBox(_error, MessageType.Warning);
if (GUILayout.Button("刷新"))
BuildChoices();
return;
}
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
for (int i = 0; i < _choices.Count; i++)
{
var choice = _choices[i];
if (!IsMatch(choice))
continue;
DrawChoice(choice);
}
EditorGUILayout.EndScrollView();
}
private void BuildChoices()
{
_choices.Clear();
_error = string.Empty;
if (TableManager.Instance?.Tables == null)
{
_error = "TableManager 尚未加载配置表。请在 Play Mode 且主流程初始化完成后再打开选择窗口。";
return;
}
if (_unitType == Unit.UnitType.Charactor)
{
var list = TableManager.Instance.Tables.CharacterAttri.DataList;
for (int i = 0; i < list.Count; i++)
{
var cfg = list[i];
_choices.Add(new UnitChoice((uint)cfg.RoleID, GetCharacterDisplayName(cfg),
SafeGetCharacterIconPath((uint)cfg.RoleID)));
}
}
else if (_unitType == Unit.UnitType.Vehicle)
{
var list = TableManager.Instance.Tables.VehicleConfig.DataList;
for (int i = 0; i < list.Count; i++)
{
var cfg = list[i];
_choices.Add(new UnitChoice((uint)cfg.ID, cfg.Name, SafeGetVehicleIconPath((uint)cfg.ID)));
}
}
}
private void DrawChoice(UnitChoice choice)
{
var rect = GUILayoutUtility.GetRect(0f, 48f, GUILayout.ExpandWidth(true));
if (Event.current.type == EventType.Repaint)
EditorStyles.helpBox.Draw(rect, GUIContent.none, false, false, false, false);
var iconRect = new Rect(rect.x + 6f, rect.y + 6f, UnitIconSize, UnitIconSize);
var icon = choice.GetIcon();
if (icon != null)
GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit);
else
GUI.Box(iconRect, GUIContent.none);
var textRect = new Rect(iconRect.xMax + 8f, rect.y + 5f, rect.width - iconRect.width - 22f, 18f);
EditorGUI.LabelField(textRect, choice.Id + " " + choice.Name, EditorStyles.boldLabel);
var pathRect = new Rect(textRect.x, textRect.yMax + 2f, textRect.width, 18f);
EditorGUI.LabelField(pathRect, choice.IconPath, EditorStyles.miniLabel);
if (GUI.Button(rect, GUIContent.none, GUIStyle.none))
{
_onSelected?.Invoke(choice.Id);
Close();
}
}
private bool IsMatch(UnitChoice choice)
{
if (string.IsNullOrEmpty(_searchText))
return true;
return choice.Id.ToString().Contains(_searchText) ||
(!string.IsNullOrEmpty(choice.Name) &&
choice.Name.IndexOf(_searchText, StringComparison.OrdinalIgnoreCase) >= 0);
}
private static string SafeGetCharacterIconPath(uint unitId)
{
try
{
return CommonUtils.GetDefaultHeadPathByUid(unitId);
}
catch (Exception)
{
return string.Empty;
}
}
private static string SafeGetVehicleIconPath(uint unitId)
{
try
{
return CommonUtils.GetDefaultVehicleHeadPath(unitId);
}
catch (Exception)
{
return string.Empty;
}
}
private sealed class UnitChoice
{
public readonly uint Id;
public readonly string Name;
public readonly string IconPath;
private Texture2D _icon;
private bool _iconLoaded;
public UnitChoice(uint id, string name, string iconPath)
{
Id = id;
Name = name;
IconPath = iconPath;
}
public Texture2D GetIcon()
{
if (_iconLoaded)
return _icon;
_iconLoaded = true;
if (!string.IsNullOrEmpty(IconPath))
_icon = AssetDatabase.LoadAssetAtPath<Texture2D>(IconPath);
return _icon;
}
}
}
}
}