494 lines
22 KiB
C#
494 lines
22 KiB
C#
using UnityEditor;
|
||
using UnityEngine;
|
||
using BaseGames.Player;
|
||
using BaseGames.Player.States;
|
||
|
||
namespace BaseGames.Editor
|
||
{
|
||
/// <summary>
|
||
/// 开发阶段 GM 调试工具窗口(仅 Play Mode 有效)。
|
||
/// 功能:资源快速填充(灵铢/灵力/魄元)、能力解锁/锁定、形态切换、调试辅助。
|
||
/// 菜单:BaseGames / Tools / GM Debug Tool
|
||
/// </summary>
|
||
public class GMToolWindow : EditorWindow
|
||
{
|
||
// ── 菜单 ──────────────────────────────────────────────────────────────
|
||
[MenuItem("BaseGames/Tools/GM Debug Tool", priority = 2)]
|
||
public static void Open()
|
||
{
|
||
var wnd = GetWindow<GMToolWindow>();
|
||
wnd.titleContent = new GUIContent("GM Debug Tool");
|
||
wnd.minSize = new Vector2(320, 500);
|
||
}
|
||
|
||
// ── 资源输入字段 ──────────────────────────────────────────────────────
|
||
private int _lingZhuAmount = 9999;
|
||
private int _soulPowerAmount = 100;
|
||
private int _spiritAmount = 100;
|
||
|
||
// ── 折叠状态 ──────────────────────────────────────────────────────────
|
||
private bool _foldResources = true;
|
||
private bool _foldJump = true;
|
||
private bool _foldForms = true;
|
||
private bool _foldAbilities = false;
|
||
private bool _foldDebug = true;
|
||
|
||
// ── 缓存(避免每帧 FindObjectOfType)─────────────────────────────────
|
||
private PlayerStats _stats;
|
||
private FormController _formCtrl;
|
||
private PlayerController _playerCtrl;
|
||
private double _lastCacheTime = -10;
|
||
|
||
// ── 能力分组定义(与 AbilityTypeDrawer 保持一致)────────────────────
|
||
private static readonly (string label, AbilityType[] flags)[] AbilityGroups =
|
||
{
|
||
("移动能力", new[]
|
||
{
|
||
AbilityType.WallCling, AbilityType.WallJump,
|
||
AbilityType.Dash, AbilityType.Dash,
|
||
AbilityType.DoubleJump, AbilityType.SuperJump,
|
||
AbilityType.Swim, AbilityType.Dive,
|
||
}),
|
||
("法术能力", new[] { AbilityType.Spell1, AbilityType.Spell2, AbilityType.Spell3 }),
|
||
("灵魄形态", new[] { AbilityType.SpiritForm, AbilityType.SpiritDash }),
|
||
("战斗能力", new[] { AbilityType.Parry, AbilityType.ChargeAttack, AbilityType.DownSlash }),
|
||
("互动能力", new[] { AbilityType.Interact, AbilityType.FastTravel }),
|
||
("能力强化", new[] { AbilityType.InvincibleDash }),
|
||
};
|
||
|
||
private static readonly string[] AbilityFlagNames =
|
||
{
|
||
"贴墙悬挂", "墙跳", "地面冲刺", "空中冲刺", "二段跳", "超级跳", "游泳", "下劈",
|
||
"法术槽 1", "法术槽 2", "法术槽 3",
|
||
"灵魄形态", "灵魄冲刺",
|
||
"弹反", "蓄力攻击", "下斩",
|
||
"互动", "快速旅行",
|
||
"无敌冲刺",
|
||
};
|
||
|
||
// ── 样式(懒初始化)──────────────────────────────────────────────────
|
||
private GUIStyle _headerStyle;
|
||
private GUIStyle _boxStyle;
|
||
|
||
// ── 滚动 ──────────────────────────────────────────────────────────────
|
||
private Vector2 _scroll;
|
||
|
||
// ── EditorWindow 回调 ─────────────────────────────────────────────────
|
||
|
||
private void OnEnable() => EditorApplication.playModeStateChanged += OnPlayModeChanged;
|
||
private void OnDisable() => EditorApplication.playModeStateChanged -= OnPlayModeChanged;
|
||
|
||
private void OnPlayModeChanged(PlayModeStateChange state)
|
||
{
|
||
_stats = null;
|
||
_formCtrl = null;
|
||
_playerCtrl = null;
|
||
Repaint();
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
EnsureStyles();
|
||
|
||
if (!Application.isPlaying)
|
||
{
|
||
EditorGUILayout.HelpBox("GM 工具仅在 Play Mode 下有效。\n请先运行游戏。", MessageType.Info);
|
||
return;
|
||
}
|
||
|
||
RefreshCache();
|
||
|
||
if (_stats == null)
|
||
{
|
||
EditorGUILayout.HelpBox("场景中未找到 PlayerStats 组件。\n请确认玩家已生成。", MessageType.Warning);
|
||
if (GUILayout.Button("重新扫描")) _lastCacheTime = -10;
|
||
return;
|
||
}
|
||
|
||
_scroll = EditorGUILayout.BeginScrollView(_scroll);
|
||
|
||
DrawResourceSection();
|
||
DrawJumpSection();
|
||
DrawFormSection();
|
||
DrawAbilitySection();
|
||
DrawDebugSection();
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
}
|
||
|
||
// ── 分区:资源 ────────────────────────────────────────────────────────
|
||
|
||
private void DrawResourceSection()
|
||
{
|
||
_foldResources = DrawFoldout(_foldResources, "资源快速填充");
|
||
if (!_foldResources) return;
|
||
|
||
EditorGUILayout.BeginVertical(_boxStyle);
|
||
|
||
// 灵铢
|
||
EditorGUILayout.LabelField("灵铢", EditorStyles.boldLabel);
|
||
EditorGUILayout.LabelField($"当前:{_stats.CurrentLingZhu}", EditorStyles.miniLabel);
|
||
EditorGUILayout.BeginHorizontal();
|
||
_lingZhuAmount = EditorGUILayout.IntField(_lingZhuAmount, GUILayout.Width(80));
|
||
if (GUILayout.Button("增加")) _stats.AddLingZhu(_lingZhuAmount);
|
||
if (GUILayout.Button("设为 9999")) _stats.AddLingZhu(Mathf.Max(0, 9999 - _stats.CurrentLingZhu));
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space(6);
|
||
|
||
// 灵力(SoulPower)
|
||
EditorGUILayout.LabelField("灵力(技能用)", EditorStyles.boldLabel);
|
||
EditorGUILayout.LabelField($"当前:{_stats.CurrentSoulPower} / {_stats.MaxSoulPower}", EditorStyles.miniLabel);
|
||
EditorGUILayout.BeginHorizontal();
|
||
_soulPowerAmount = EditorGUILayout.IntField(_soulPowerAmount, GUILayout.Width(80));
|
||
if (GUILayout.Button("增加")) _stats.AddSoulPower(_soulPowerAmount);
|
||
if (GUILayout.Button("填满")) _stats.AddSoulPower(_stats.MaxSoulPower);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space(6);
|
||
|
||
// 魄元(SpiritPower)
|
||
EditorGUILayout.LabelField("魄元(魄技能用)", EditorStyles.boldLabel);
|
||
EditorGUILayout.LabelField($"当前:{_stats.CurrentSpiritPower} / {_stats.MaxSpiritPower}", EditorStyles.miniLabel);
|
||
EditorGUILayout.BeginHorizontal();
|
||
_spiritAmount = EditorGUILayout.IntField(_spiritAmount, GUILayout.Width(80));
|
||
if (GUILayout.Button("增加")) _stats.AddSpiritPower(_spiritAmount);
|
||
if (GUILayout.Button("填满")) _stats.AddSpiritPower(_stats.MaxSpiritPower);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space(4);
|
||
|
||
if (GUILayout.Button("▶ 全部资源填满"))
|
||
{
|
||
_stats.AddLingZhu(Mathf.Max(0, 9999 - _stats.CurrentLingZhu));
|
||
_stats.AddSoulPower(_stats.MaxSoulPower);
|
||
_stats.AddSpiritPower(_stats.MaxSpiritPower);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
// ── 分区:跳跃快捷 ────────────────────────────────────────────────────
|
||
|
||
private void DrawJumpSection()
|
||
{
|
||
_foldJump = DrawFoldout(_foldJump, "跳跃能力快捷");
|
||
if (!_foldJump) return;
|
||
|
||
EditorGUILayout.BeginVertical(_boxStyle);
|
||
|
||
// ── 当前状态 ──
|
||
bool hasDoubleJump = _stats.HasAbility(AbilityType.DoubleJump);
|
||
bool hasDash = _stats.HasAbility(AbilityType.Dash);
|
||
bool hasWallJump = _stats.HasAbility(AbilityType.WallJump);
|
||
bool hasWallCling = _stats.HasAbility(AbilityType.WallCling);
|
||
|
||
int airJumpsLeft = _playerCtrl != null ? _playerCtrl.AirJumpsLeft : -1;
|
||
int maxAirJumps = _playerCtrl != null && _playerCtrl.MovConfig != null
|
||
? _playerCtrl.MovConfig.MaxAirJumps : -1;
|
||
string airStr = airJumpsLeft >= 0
|
||
? $"{airJumpsLeft} / {maxAirJumps}"
|
||
: "N/A";
|
||
|
||
EditorGUILayout.LabelField(
|
||
$"二段跳:{(hasDoubleJump ? "✔ 已解锁" : "✘ 未解锁")} | 腾空剩余:{airStr}",
|
||
EditorStyles.miniLabel);
|
||
|
||
// ── MaxAirJumps 控制 ──
|
||
if (_playerCtrl != null && _playerCtrl.MovConfig != null)
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
EditorGUILayout.LabelField("最大空中跳跃次数", GUILayout.Width(120));
|
||
int newMax = EditorGUILayout.IntSlider(
|
||
_playerCtrl.MovConfig.MaxAirJumps, 1, 5, GUILayout.ExpandWidth(true));
|
||
if (newMax != _playerCtrl.MovConfig.MaxAirJumps)
|
||
{
|
||
_playerCtrl.MovConfig.MaxAirJumps = newMax;
|
||
EditorUtility.SetDirty(_playerCtrl.MovConfig);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
EditorGUILayout.LabelField(
|
||
$" 1=二段跳 2=三段跳 3=四段跳…(需先解锁 DoubleJump 能力)",
|
||
EditorStyles.miniLabel);
|
||
}
|
||
|
||
EditorGUILayout.Space(4);
|
||
|
||
// ── 跳跃系列快捷按钮(每行 2 个)──
|
||
EditorGUILayout.BeginHorizontal();
|
||
DrawToggleAbilityBtn(hasDoubleJump, AbilityType.DoubleJump, "二段跳");
|
||
DrawToggleAbilityBtn(hasDash, AbilityType.Dash, "冲刺(地面+空中)");
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
DrawToggleAbilityBtn(hasWallJump, AbilityType.WallJump, "墙跳");
|
||
DrawToggleAbilityBtn(hasWallCling, AbilityType.WallCling, "贴墙悬挂");
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
DrawToggleAbilityBtn(_stats.HasAbility(AbilityType.SuperJump), AbilityType.SuperJump, "超级跳");
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space(4);
|
||
|
||
// ── 批量快捷 ──
|
||
EditorGUILayout.BeginHorizontal();
|
||
if (GUILayout.Button("解锁全部移动能力"))
|
||
{
|
||
foreach (var f in new[]
|
||
{
|
||
AbilityType.Dash,
|
||
AbilityType.DoubleJump, AbilityType.SuperJump,
|
||
AbilityType.WallCling, AbilityType.WallJump,
|
||
})
|
||
_stats.UnlockAbility(f);
|
||
}
|
||
if (GUILayout.Button("锁定全部移动能力"))
|
||
{
|
||
foreach (var f in new[]
|
||
{
|
||
AbilityType.Dash,
|
||
AbilityType.DoubleJump, AbilityType.SuperJump,
|
||
AbilityType.WallCling, AbilityType.WallJump,
|
||
})
|
||
_stats.LockAbility(f);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.HelpBox(
|
||
"MaxAirJumps 修改立即写入 ScriptableObject(持久化)。\n" +
|
||
"AirJumpsLeft 在角色下次落地时按新值重置。",
|
||
MessageType.None);
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 绘制一个「已解锁 → 锁定 / 未解锁 → 解锁」的切换按钮。
|
||
/// </summary>
|
||
private void DrawToggleAbilityBtn(bool hasIt, AbilityType flag, string label)
|
||
{
|
||
GUI.backgroundColor = hasIt ? new Color(0.6f, 1.0f, 0.6f) : new Color(1.0f, 0.85f, 0.6f);
|
||
string btnText = hasIt ? $"✔ {label}" : $"✘ {label}";
|
||
if (GUILayout.Button(btnText))
|
||
{
|
||
if (hasIt) _stats.LockAbility(flag);
|
||
else _stats.UnlockAbility(flag);
|
||
}
|
||
GUI.backgroundColor = Color.white;
|
||
}
|
||
|
||
// ── 分区:形态 ────────────────────────────────────────────────────────
|
||
|
||
private void DrawFormSection()
|
||
{
|
||
_foldForms = DrawFoldout(_foldForms, "形态快速切换");
|
||
if (!_foldForms) return;
|
||
|
||
EditorGUILayout.BeginVertical(_boxStyle);
|
||
|
||
if (_formCtrl == null)
|
||
{
|
||
EditorGUILayout.HelpBox("场景中未找到 FormController 组件。", MessageType.Warning);
|
||
}
|
||
else
|
||
{
|
||
string cur = _formCtrl.CurrentForm != null ? _formCtrl.CurrentForm.displayName : "未知";
|
||
EditorGUILayout.LabelField($"当前形态:{cur}", EditorStyles.boldLabel);
|
||
EditorGUILayout.Space(4);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
if (GUILayout.Button("天魂")) SwitchForm(FormType.TianHun);
|
||
if (GUILayout.Button("地魂")) SwitchForm(FormType.DiHun);
|
||
if (GUILayout.Button("命魂")) SwitchForm(FormType.MingHun);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.HelpBox("提示:切换形态前请确保已解锁灵魄形态(SpiritForm)能力。", MessageType.None);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void SwitchForm(FormType type)
|
||
{
|
||
// 确保 SpiritForm 能力已解锁(否则 FSM 可能拒绝形态切换)
|
||
_stats.UnlockAbility(AbilityType.SpiritForm);
|
||
_formCtrl.SwitchForm(type);
|
||
}
|
||
|
||
// ── 分区:能力 ────────────────────────────────────────────────────────
|
||
|
||
private void DrawAbilitySection()
|
||
{
|
||
_foldAbilities = DrawFoldout(_foldAbilities, "能力解锁 / 锁定");
|
||
if (!_foldAbilities) return;
|
||
|
||
EditorGUILayout.BeginVertical(_boxStyle);
|
||
|
||
// 快捷全选/全清
|
||
EditorGUILayout.BeginHorizontal();
|
||
if (GUILayout.Button("全部解锁"))
|
||
{
|
||
foreach (var (_, flags) in AbilityGroups)
|
||
foreach (var f in flags)
|
||
_stats.UnlockAbility(f);
|
||
}
|
||
if (GUILayout.Button("全部锁定"))
|
||
{
|
||
foreach (var (_, flags) in AbilityGroups)
|
||
foreach (var f in flags)
|
||
_stats.LockAbility(f);
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space(6);
|
||
|
||
// 各分组
|
||
foreach (var (groupLabel, flags) in AbilityGroups)
|
||
{
|
||
EditorGUILayout.LabelField(groupLabel, EditorStyles.boldLabel);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
bool allOn = true;
|
||
foreach (var f in flags) if (!_stats.HasAbility(f)) { allOn = false; break; }
|
||
if (GUILayout.Button(allOn ? "全锁" : "全解", GUILayout.Width(42)))
|
||
{
|
||
foreach (var f in flags)
|
||
{
|
||
if (allOn) _stats.LockAbility(f);
|
||
else _stats.UnlockAbility(f);
|
||
}
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
int col = 0;
|
||
foreach (var flag in flags)
|
||
{
|
||
bool has = _stats.HasAbility(flag);
|
||
bool toggled = GUILayout.Toggle(has, FlagDisplayName(flag), GUILayout.Width(128));
|
||
if (toggled != has)
|
||
{
|
||
if (toggled) _stats.UnlockAbility(flag);
|
||
else _stats.LockAbility(flag);
|
||
}
|
||
col++;
|
||
if (col == 2) { col = 0; EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); }
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
EditorGUILayout.Space(4);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
// ── 分区:调试辅助 ────────────────────────────────────────────────────
|
||
|
||
private void DrawDebugSection()
|
||
{
|
||
_foldDebug = DrawFoldout(_foldDebug, "调试辅助");
|
||
if (!_foldDebug) return;
|
||
|
||
EditorGUILayout.BeginVertical(_boxStyle);
|
||
|
||
// HP
|
||
EditorGUILayout.LabelField($"HP:{_stats.CurrentHP} / {_stats.MaxHP}", EditorStyles.miniLabel);
|
||
EditorGUILayout.BeginHorizontal();
|
||
if (GUILayout.Button("满血")) _stats.FullHeal();
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space(4);
|
||
|
||
// 弹簧充能
|
||
EditorGUILayout.LabelField($"弹力充能:{_stats.CurrentSpringCharges} / {_stats.MaxSpringCharges}", EditorStyles.miniLabel);
|
||
if (GUILayout.Button("恢复全部弹力充能")) _stats.RestoreSpringCharges();
|
||
|
||
EditorGUILayout.Space(4);
|
||
|
||
// 无敌模式(God Mode)
|
||
bool godNow = _stats.IsInvincible; // 仅作参考,GodMode 内部字段
|
||
EditorGUILayout.BeginHorizontal();
|
||
if (GUILayout.Button("开启无敌模式")) _stats.SetGodMode(true);
|
||
if (GUILayout.Button("关闭无敌模式")) _stats.SetGodMode(false);
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space(4);
|
||
|
||
// 一键全开(开发快速进入测试状态)
|
||
GUI.backgroundColor = new Color(0.7f, 1.0f, 0.7f);
|
||
if (GUILayout.Button("▶ 一键满状态(资源 + 全能力 + 满血)", GUILayout.Height(32)))
|
||
{
|
||
_stats.FullHeal();
|
||
_stats.RestoreSpringCharges();
|
||
_stats.AddLingZhu(Mathf.Max(0, 9999 - _stats.CurrentLingZhu));
|
||
_stats.AddSoulPower(_stats.MaxSoulPower);
|
||
_stats.AddSpiritPower(_stats.MaxSpiritPower);
|
||
foreach (var (_, flags) in AbilityGroups)
|
||
foreach (var f in flags)
|
||
_stats.UnlockAbility(f);
|
||
}
|
||
GUI.backgroundColor = Color.white;
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
// ── 工具方法 ──────────────────────────────────────────────────────────
|
||
|
||
private void RefreshCache()
|
||
{
|
||
if (EditorApplication.timeSinceStartup - _lastCacheTime < 2.0) return;
|
||
_lastCacheTime = EditorApplication.timeSinceStartup;
|
||
_stats = FindObjectOfType<PlayerStats>();
|
||
_formCtrl = FindObjectOfType<FormController>();
|
||
_playerCtrl = FindObjectOfType<PlayerController>();
|
||
}
|
||
|
||
private bool DrawFoldout(bool state, string label)
|
||
{
|
||
EditorGUILayout.Space(4);
|
||
bool next = EditorGUILayout.Foldout(state, label, true, _headerStyle);
|
||
return next;
|
||
}
|
||
|
||
private static string FlagDisplayName(AbilityType flag) => flag switch
|
||
{
|
||
AbilityType.WallCling => "贴墙悬挂",
|
||
AbilityType.WallJump => "墙跳",
|
||
AbilityType.Dash => "冲刺",
|
||
AbilityType.DoubleJump => "二段跳",
|
||
AbilityType.SuperJump => "超级跳",
|
||
AbilityType.Swim => "游泳",
|
||
AbilityType.Dive => "下劈",
|
||
AbilityType.Spell1 => "法术槽 1",
|
||
AbilityType.Spell2 => "法术槽 2",
|
||
AbilityType.Spell3 => "法术槽 3",
|
||
AbilityType.SpiritForm => "灵魄形态",
|
||
AbilityType.SpiritDash => "灵魄冲刺",
|
||
AbilityType.Parry => "弹反",
|
||
AbilityType.ChargeAttack => "蓄力攻击",
|
||
AbilityType.DownSlash => "下斩",
|
||
AbilityType.Interact => "互动",
|
||
AbilityType.FastTravel => "快速旅行",
|
||
AbilityType.InvincibleDash => "无敌冲刺",
|
||
_ => flag.ToString(),
|
||
};
|
||
|
||
private void EnsureStyles()
|
||
{
|
||
if (_headerStyle != null) return;
|
||
_headerStyle = new GUIStyle(EditorStyles.foldout)
|
||
{
|
||
fontStyle = FontStyle.Bold,
|
||
fontSize = 12,
|
||
};
|
||
_boxStyle = new GUIStyle(EditorStyles.helpBox)
|
||
{
|
||
padding = new RectOffset(8, 8, 6, 6),
|
||
};
|
||
}
|
||
|
||
// ── 自动刷新(每秒重绘以显示最新数值)──────────────────────────────
|
||
private void OnInspectorUpdate() => Repaint();
|
||
}
|
||
}
|