feat: Add HurtBoxOwnerGuard to prevent multiple damage registrations from the same HitBox activation
- 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.
This commit is contained in:
@@ -39,6 +39,10 @@ namespace BaseGames.Enemies
|
||||
[SerializeField] protected EnemyFeedback _feedback;
|
||||
[SerializeField] protected HurtBox _hurtBox;
|
||||
|
||||
[Header("区域(可选)")]
|
||||
[Tooltip("地图固定巡逻/追击区域;配置后 BD_ChasePlayer 以区域边界替代 MaxChaseDistance,BD_ReturnToHome 归位至区域中心。留空则沿用出生点 + MaxChaseDistance 旧逻辑。")]
|
||||
[SerializeField] private EnemyPatrolZone _patrolZone;
|
||||
|
||||
[Header("事件频道")]
|
||||
[SerializeField] private BaseGames.Core.Events.StringEventChannelSO _onEnemyDied;
|
||||
/// <summary>
|
||||
@@ -101,6 +105,13 @@ namespace BaseGames.Enemies
|
||||
/// </summary>
|
||||
public Vector2 LastKnownPlayerPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 地图固定巡逻/追击区域(可选)。
|
||||
/// 配置后 BD_ChasePlayer 以区域边界为追击上限;BD_ReturnToHome 归位至区域中心。
|
||||
/// 未配置时退回旧逻辑(HomePosition + MaxChaseDistance)。
|
||||
/// </summary>
|
||||
public EnemyPatrolZone PatrolZone => _patrolZone;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[Header("── 运行时调试(仅 Editor)──")]
|
||||
[SerializeField] private EnemyStateType _dbg_CurrentState;
|
||||
@@ -212,9 +223,9 @@ namespace BaseGames.Enemies
|
||||
private readonly EnemyAbilityRegistry _abilities = new EnemyAbilityRegistry();
|
||||
/// <summary>由 _onPlayerSpawned 事件缓存的玩家 Transform,供 BD 任务读取。</summary>
|
||||
public Transform PlayerTransform => _playerTransform;
|
||||
/// <summary>感知 Hub(SensorToolkit);供 QuotaManager 暂停/恢复 Sensor 使用。</summary>
|
||||
/// <summary>感知 Hub;供 BD 任务及 QuotaManager 暂停/恢复感知使用。</summary>
|
||||
public Perception.IPerceptionSystem SensorHub => _sensorHub;
|
||||
private Perception.EnemySensorHub _sensorHub;
|
||||
private Perception.IPerceptionSystem _sensorHub;
|
||||
/// <summary>威胁评估器(可选):为原始 LOS 结果叠加反应延迟,使感知更自然。</summary>
|
||||
public Perception.EnemyThreatAssessor ThreatAssessor => _threatAssessor;
|
||||
private Perception.EnemyThreatAssessor _threatAssessor;
|
||||
@@ -275,26 +286,6 @@ namespace BaseGames.Enemies
|
||||
public virtual bool IsPlayerInRange(float range)
|
||||
=> _stats != null && _stats.SqrDistanceToPlayer <= range * range;
|
||||
|
||||
/// <summary>
|
||||
/// 检查玩家是否在感知范围内。若 <see cref="EnemyStatsSO.DetectAngleDeg"/> > 0,
|
||||
/// 同时验证玩家是否在自身朝向的扇形角度内。
|
||||
/// </summary>
|
||||
public virtual bool IsPlayerInDetectRange()
|
||||
{
|
||||
if (_stats == null || _playerTransform == null) return false;
|
||||
float detectRange = _statsSO != null ? _statsSO.DetectRange : 6f;
|
||||
if (_stats.SqrDistanceToPlayer > detectRange * detectRange) return false;
|
||||
|
||||
float angleDeg = _statsSO?.DetectAngleDeg ?? 0f;
|
||||
if (angleDeg <= 0f) return true; // 0 = 关闭方向限制
|
||||
|
||||
Vector2 toPlayer = ((Vector2)_playerTransform.position - (Vector2)transform.position).normalized;
|
||||
float facingDir = _movement != null ? _movement.FacingDirection : 1f;
|
||||
var forward = new Vector2(facingDir, 0f);
|
||||
float angle = Vector2.Angle(forward, toPlayer);
|
||||
return angle <= angleDeg;
|
||||
}
|
||||
|
||||
/// <summary>原始视线检测结果(BatchLOSSystem 写入,无感知延迟修正)。</summary>
|
||||
public bool HasLineOfSight => _losResult;
|
||||
|
||||
@@ -309,6 +300,23 @@ namespace BaseGames.Enemies
|
||||
_movement.PendingInput.FaceDir = 0;
|
||||
}
|
||||
|
||||
/// <summary>朝向世界坐标点(通过输入信号,下一 FixedUpdate 消费)。</summary>
|
||||
public void FaceTarget(Vector2 worldPos)
|
||||
{
|
||||
if (_movement == null) return;
|
||||
_movement.PendingInput.WantFace = true;
|
||||
_movement.PendingInput.FaceTargetPos = worldPos;
|
||||
_movement.PendingInput.FaceDir = 0;
|
||||
}
|
||||
|
||||
/// <summary>直接指定朝向方向(+1 右 / -1 左,通过输入信号)。</summary>
|
||||
public void FaceDirection(int dir)
|
||||
{
|
||||
if (_movement == null) return;
|
||||
_movement.PendingInput.WantFace = true;
|
||||
_movement.PendingInput.FaceDir = dir;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜查"环顾"子步骤:停止移动,播放原地环顾动画。
|
||||
/// 由搜查行为触发;动画细节由角色自己决定,外部无需感知 AnimConfig。
|
||||
@@ -542,7 +550,7 @@ namespace BaseGames.Enemies
|
||||
_nav = GetComponent<IPathAgent>() ?? new NullPathAgent();
|
||||
if (_movement == null) _movement = GetComponent<EnemyMovement>();
|
||||
_poiseSource = GetComponent<IPoiseSource>();
|
||||
_sensorHub = GetComponentInChildren<Perception.EnemySensorHub>();
|
||||
_sensorHub = GetComponentInChildren<Perception.IPerceptionSystem>();
|
||||
_statusEffects = GetComponent<StatusEffects.EnemyStatusEffectManager>();
|
||||
_threatAssessor = GetComponent<Perception.EnemyThreatAssessor>();
|
||||
_pooledObject = GetComponent<PooledObject>();
|
||||
@@ -782,43 +790,8 @@ namespace BaseGames.Enemies
|
||||
#if UNITY_EDITOR
|
||||
if (_statsSO == null) return;
|
||||
|
||||
// ── 侦测范围(淡橙;若配置扇形角则绘制扇形弧)+ 攻击范围(淡红圆)────
|
||||
{
|
||||
var c = new Vector3(transform.position.x, transform.position.y, 0f);
|
||||
var prevM = UnityEditor.Handles.matrix;
|
||||
UnityEditor.Handles.matrix = Matrix4x4.identity;
|
||||
|
||||
// 攻击范围(全圆)
|
||||
UnityEditor.Handles.color = new Color(1f, 0.15f, 0.15f, 0.15f);
|
||||
UnityEditor.Handles.DrawSolidDisc(c, Vector3.back, _statsSO.AttackRange);
|
||||
UnityEditor.Handles.color = new Color(1f, 0.15f, 0.15f, 0.55f);
|
||||
UnityEditor.Handles.DrawWireDisc(c, Vector3.back, _statsSO.AttackRange);
|
||||
|
||||
// 侦测范围
|
||||
float angleDeg = _statsSO.DetectAngleDeg;
|
||||
if (angleDeg > 0f)
|
||||
{
|
||||
// 扇形感知:绘制弧形扇区
|
||||
float facing = Application.isPlaying && _movement != null ? _movement.FacingDirection : 1f;
|
||||
Vector3 forward3 = new Vector3(facing, 0f, 0f);
|
||||
UnityEditor.Handles.color = new Color(1f, 0.6f, 0.1f, 0.12f);
|
||||
UnityEditor.Handles.DrawSolidArc(c, Vector3.back, forward3, angleDeg, _statsSO.DetectRange);
|
||||
UnityEditor.Handles.DrawSolidArc(c, Vector3.back, forward3, -angleDeg, _statsSO.DetectRange);
|
||||
UnityEditor.Handles.color = new Color(1f, 0.6f, 0.1f, 0.6f);
|
||||
UnityEditor.Handles.DrawWireArc(c, Vector3.back, forward3, angleDeg, _statsSO.DetectRange);
|
||||
UnityEditor.Handles.DrawWireArc(c, Vector3.back, forward3, -angleDeg, _statsSO.DetectRange);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 全圆感知
|
||||
UnityEditor.Handles.color = new Color(1f, 0.6f, 0.1f, 0.15f);
|
||||
UnityEditor.Handles.DrawSolidDisc(c, Vector3.back, _statsSO.DetectRange);
|
||||
UnityEditor.Handles.color = new Color(1f, 0.6f, 0.1f, 0.55f);
|
||||
UnityEditor.Handles.DrawWireDisc(c, Vector3.back, _statsSO.DetectRange);
|
||||
}
|
||||
|
||||
UnityEditor.Handles.matrix = prevM;
|
||||
}
|
||||
// 感知范围圆形 Gizmo 由 PhysicsPerceptionSystemEditor [DrawGizmo] 统一绘制,
|
||||
// 此处不重复绘制,避免叠加覆盖导致 gizmoColor 设置无效。
|
||||
|
||||
// ── 运行时:AI 状态标签(常态可见,无需选中)────────────────
|
||||
if (Application.isPlaying)
|
||||
@@ -843,10 +816,14 @@ namespace BaseGames.Enemies
|
||||
// ── 运行时:LOS 连线 ────────────────────────────────────────
|
||||
if (!Application.isPlaying || _playerTransform == null) return;
|
||||
|
||||
float drawDetectRange = _sensorHub != null
|
||||
? _sensorHub.GetSensorRadius(Perception.SensorSlotNames.Aggro)
|
||||
: -1f;
|
||||
|
||||
Vector3 eyeWorld = transform.position + new Vector3(_statsSO.EyeOffset.x, _statsSO.EyeOffset.y, 0f);
|
||||
Vector3 playerPos = _playerTransform.position;
|
||||
float sqrDist = (playerPos - transform.position).sqrMagnitude;
|
||||
bool inRange = sqrDist <= _statsSO.DetectRange * _statsSO.DetectRange;
|
||||
bool inRange = drawDetectRange >= 0f && sqrDist <= drawDetectRange * drawDetectRange;
|
||||
|
||||
// 眼睛位置小圆点(金黄)
|
||||
Gizmos.color = new Color(1f, 0.9f, 0.2f, 0.85f);
|
||||
@@ -867,24 +844,8 @@ namespace BaseGames.Enemies
|
||||
#if UNITY_EDITOR
|
||||
if (_statsSO == null) return;
|
||||
|
||||
// 选中时加亮范围圆
|
||||
{
|
||||
var c = new Vector3(transform.position.x, transform.position.y, 0f);
|
||||
var prevM = UnityEditor.Handles.matrix;
|
||||
UnityEditor.Handles.matrix = Matrix4x4.identity;
|
||||
|
||||
UnityEditor.Handles.color = new Color(1f, 0.6f, 0.1f, 0.25f);
|
||||
UnityEditor.Handles.DrawSolidDisc(c, Vector3.back, _statsSO.DetectRange);
|
||||
UnityEditor.Handles.color = new Color(1f, 0.6f, 0.1f, 0.90f);
|
||||
UnityEditor.Handles.DrawWireDisc(c, Vector3.back, _statsSO.DetectRange);
|
||||
|
||||
UnityEditor.Handles.color = new Color(1f, 0.2f, 0.2f, 0.25f);
|
||||
UnityEditor.Handles.DrawSolidDisc(c, Vector3.back, _statsSO.AttackRange);
|
||||
UnityEditor.Handles.color = new Color(1f, 0.2f, 0.2f, 0.90f);
|
||||
UnityEditor.Handles.DrawWireDisc(c, Vector3.back, _statsSO.AttackRange);
|
||||
|
||||
UnityEditor.Handles.matrix = prevM;
|
||||
}
|
||||
// 感知范围圆形 Gizmo 由 PhysicsPerceptionSystemEditor [DrawGizmo] 统一绘制,
|
||||
// 此处不重复绘制。
|
||||
|
||||
// 运行时:选中时绘制 AiPhase 彩色外圆(突出显示当前状态)
|
||||
if (Application.isPlaying)
|
||||
|
||||
Reference in New Issue
Block a user