- Implemented HurtBoxOwnerGuard to ensure that multiple HurtBoxes on the same character do not register damage multiple times during a single HitBox activation. - Added custom editor for HitBox to facilitate the creation of shape colliders with HitBoxColliderProxy. - Introduced PhysicsPerceptionSystem for enemy perception, supporting multiple detection modes including RangeCircle, BatchLOS, FanCast, and BoxCast. - Created EnemyPatrolZone to define patrol and chase areas for enemies, allowing for shared zones among multiple enemies. - Added BD_IsOutsideZone conditional task for Behavior Designer to check if an enemy or player is outside a defined patrol zone.
151 lines
5.6 KiB
C#
151 lines
5.6 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 上显示:
|
||
/// - AiPhase / 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>");
|
||
|
||
// AiPhase + EnemyState
|
||
list.Add($"Phase: <color=#ffcc44>{_enemy.CurrentAiPhase}</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)
|
||
{
|
||
list.Add($"BossSkillExec: <color={( _boss.IsBossSkillExecuting ? "#ff4444" : "#aaaaaa")}>{_boss.IsBossSkillExecuting}</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
|