Files
zeling_v2/Assets/_Game/Scripts/Editor/Tools/GMToolWindow.cs
Joywayer 534de11e5d Refactor ability types and update tags
- Updated AbilityTypeDrawer to replace "Dive" with "DownDash" in the movement abilities section.
- Modified GMToolWindow to reflect the change from "Dive" to "DownDash" in the ability list.
- Changed AbilityType enum to rename "Dive" to "DownDash" with updated description.
- Adjusted AllMovement mask in AbilityType to include "DownDash" instead of "Dive".
- Corrected tag from "OneWayPlatforms" to "OneWayPlatform" in TagManager settings.
2026-05-21 22:37:38 +08:00

494 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using 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.DownDash,
}),
("法术能力", 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.DownDash => "下冲刺",
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();
}
}