1249 lines
46 KiB
C#
1249 lines
46 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|