技能选取/执行归 EnemyAttackSelector + EnemyAbilityBase(阶段一已就位), BossBase 不再持有 BossSkillExecutor。阶段切换改用能力注册表打断在跑的招。 弹反反制整套删除:它订阅的是全局弹反成功频道,无来源过滤—— 玩家弹反任意小怪都会让场上 Boss 硬直。正确形态是 EnemyBase.ReceiveParry (谁被弹反谁硬直),其接线为独立后续任务。 调试面板的 BossSkillExec 行换为阶段与过渡状态。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
153 lines
5.8 KiB
C#
153 lines
5.8 KiB
C#
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||
using UnityEngine;
|
||
using BaseGames.Enemies.Abilities;
|
||
using BaseGames.Enemies.StatusEffects;
|
||
|
||
namespace BaseGames.Enemies
|
||
{
|
||
/// <summary>
|
||
/// 运行时 AI 状态调试叠加层(仅 Editor)。
|
||
/// 挂到敌人 Prefab 根节点,运行时在场景视图 / GameView 上显示:
|
||
/// - AI 决策状态 / EnemyStateType
|
||
/// - 当前激活的能力名称及运行状态
|
||
/// - LOS 状态 / 到玩家距离
|
||
/// - Boss:当前阶段与阶段过渡状态
|
||
/// </summary>
|
||
[AddComponentMenu("BaseGames/Debug/Enemy Debug Overlay")]
|
||
public sealed class EnemyDebugOverlay : MonoBehaviour
|
||
{
|
||
[Header("显示选项")]
|
||
[Tooltip("在 Scene 视图中绘制 GUI 标签")]
|
||
[SerializeField] private bool _showInSceneView = true;
|
||
[Tooltip("在 Game 视图中绘制 GUI 标签(需要 OnGUI)")]
|
||
[SerializeField] private bool _showInGameView = false;
|
||
[Tooltip("文字背景透明度")]
|
||
[SerializeField, Range(0f, 1f)] private float _bgAlpha = 0.65f;
|
||
|
||
private EnemyBase _enemy;
|
||
private BossBase _boss;
|
||
private EnemyStatusEffectManager _statusMgr;
|
||
|
||
private GUIStyle _labelStyle;
|
||
private GUIStyle _boxStyle;
|
||
private bool _stylesInit;
|
||
|
||
private void Awake()
|
||
{
|
||
_enemy = GetComponent<EnemyBase>();
|
||
_boss = GetComponent<BossBase>();
|
||
_statusMgr = GetComponent<EnemyStatusEffectManager>();
|
||
}
|
||
|
||
// ── Scene 视图标签(UnityEditor.Handles.Label)─────────────────────
|
||
private void OnDrawGizmos()
|
||
{
|
||
if (!_showInSceneView || !Application.isPlaying || _enemy == null) return;
|
||
|
||
var lines = BuildLines();
|
||
var label = string.Join("\n", lines);
|
||
var pos = transform.position + Vector3.up * 2.2f;
|
||
|
||
UnityEditor.Handles.Label(pos, label);
|
||
}
|
||
|
||
// ── Game 视图 GUI(OnGUI)──────────────────────────────────────────
|
||
private void OnGUI()
|
||
{
|
||
if (!_showInGameView || !Application.isPlaying || _enemy == null) return;
|
||
|
||
InitStyles();
|
||
|
||
var cam = UnityEngine.Camera.main;
|
||
if (cam == null) return;
|
||
|
||
Vector3 worldPos = transform.position + Vector3.up * 2.2f;
|
||
Vector3 screen = cam.WorldToScreenPoint(worldPos);
|
||
if (screen.z < 0f) return;
|
||
|
||
float screenY = Screen.height - screen.y;
|
||
var lines = BuildLines();
|
||
var text = string.Join("\n", lines);
|
||
var size = _labelStyle.CalcSize(new GUIContent(text));
|
||
var rect = new Rect(screen.x - size.x * 0.5f, screenY - size.y, size.x + 8f, size.y + 4f);
|
||
|
||
GUI.Box(rect, GUIContent.none, _boxStyle);
|
||
GUI.Label(rect, text, _labelStyle);
|
||
}
|
||
|
||
// ── 内部 ──────────────────────────────────────────────────────────
|
||
|
||
private System.Collections.Generic.List<string> BuildLines()
|
||
{
|
||
|
||
var list = new System.Collections.Generic.List<string>(8);
|
||
|
||
// 名称
|
||
list.Add($"<b>{gameObject.name}</b>");
|
||
|
||
// AI 决策状态 + EnemyState
|
||
list.Add($"Phase: <color=#ffcc44>{(_enemy.Brain != null ? _enemy.Brain.CurrentStateName : "-")}</color> State: <color=#88ddff>{_enemy.CurrentState}</color>");
|
||
|
||
// 距离 + LOS
|
||
float dist = _enemy.Stats != null ? Mathf.Sqrt(_enemy.Stats.SqrDistanceToPlayer) : -1f;
|
||
bool los = _enemy.IsPlayerVisible();
|
||
var losColor = los ? "#44ff88" : "#ff6644";
|
||
list.Add($"Dist: {dist:F1}m LOS: <color={losColor}>{los}</color>");
|
||
|
||
// 当前激活能力
|
||
if (_enemy.Abilities != null)
|
||
{
|
||
foreach (var ab in _enemy.Abilities.All)
|
||
{
|
||
if (ab.IsRunning)
|
||
{
|
||
string abilityId = ab.Config?.abilityId ?? ab.GetType().Name;
|
||
list.Add($"Ability: <color=#ffaa44>{abilityId}</color> [{ab.Phase}]");
|
||
}
|
||
}
|
||
}
|
||
|
||
// Boss 信息
|
||
if (_boss != null)
|
||
{
|
||
// 上面第 2 行已用 "Phase:" 表示 AI 决策状态名,这里用 "BossPhase:" 区分,避免同名两行
|
||
list.Add($"BossPhase: {_boss.CurrentPhase}" +
|
||
(_boss.IsPhaseTransitioning ? " <color=#ffcc44>(transitioning)</color>" : ""));
|
||
}
|
||
|
||
// 状态效果
|
||
if (_statusMgr != null)
|
||
{
|
||
var effects = _statusMgr.ActiveEffects;
|
||
for (int i = 0; i < effects.Count; i++)
|
||
list.Add($"FX: <color=#dd88ff>{effects[i].Type}</color>");
|
||
}
|
||
|
||
return list;
|
||
}
|
||
|
||
private void InitStyles()
|
||
{
|
||
if (_stylesInit) return;
|
||
_stylesInit = true;
|
||
|
||
_labelStyle = new GUIStyle(GUI.skin.label)
|
||
{
|
||
richText = true,
|
||
fontSize = 11,
|
||
alignment = TextAnchor.UpperLeft,
|
||
padding = new RectOffset(4, 4, 2, 2),
|
||
};
|
||
_labelStyle.normal.textColor = Color.white;
|
||
|
||
var bgTex = new Texture2D(1, 1);
|
||
bgTex.SetPixel(0, 0, new Color(0.05f, 0.05f, 0.1f, _bgAlpha));
|
||
bgTex.Apply();
|
||
|
||
_boxStyle = new GUIStyle(GUI.skin.box);
|
||
_boxStyle.normal.background = bgTex;
|
||
}
|
||
}
|
||
}
|
||
#endif
|