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:
2026-06-02 16:10:44 +08:00
parent bcd8b0e90b
commit 06048c966a
47 changed files with 1912 additions and 1195 deletions

View File

@@ -45,6 +45,7 @@ namespace BaseGames.Enemies.Navigation
// ── 私有 ────────────────────────────────────────────────────────
private NavAgent _navAgent;
private TransformBasedMovement _movement;
private EnemyMovement _enemyMovement;
// 能力 handler 注册表NavLinkType → INavLinkHandler
private readonly Dictionary<NavLinkType, INavLinkHandler> _handlers
@@ -53,6 +54,7 @@ namespace BaseGames.Enemies.Navigation
// 连接段状态缓存
private NavLinkType _currentLinkType = NavLinkType.None;
private bool _wasNavOnSegment;
private Vector2 _currentLinkStart;
private Vector2 _currentLinkEnd;
@@ -61,6 +63,7 @@ namespace BaseGames.Enemies.Navigation
{
_navAgent = GetComponent<NavAgent>();
_movement = GetComponent<TransformBasedMovement>();
_enemyMovement = GetComponent<EnemyMovement>();
// 自动发现 INavLinkHandler 组件并注册(包含子对象)
foreach (var handler in GetComponentsInChildren<INavLinkHandler>(true))
@@ -196,6 +199,32 @@ namespace BaseGames.Enemies.Navigation
private void HandlePathFailed(NavAgent _) => OnNavPathFailed?.Invoke();
private void HandleGoalReached(NavAgent _) => OnGoalReached?.Invoke();
// ── NavDriving 信号桥 ───────────────────────────────────────────
/// <summary>
/// 每物理帧向 <see cref="EnemyMovement"/> 写入导航方向信号,并设置 NavDriving 标志。
/// NavDriving=true 时 EnemyMovement 只更新朝向TBM 保留对 transform.position 的控制权。
/// </summary>
private void FixedUpdate()
{
if (_enemyMovement == null || _navAgent == null) return;
bool onSegment = _navAgent.IsMovingOnSegment;
_enemyMovement.NavDriving = onSegment;
if (onSegment)
{
float dx = _navAgent.PathSubGoal.x - transform.position.x;
_enemyMovement.PendingInput.MoveDir = Mathf.Abs(dx) > 0.01f ? Mathf.Sign(dx) : 0f;
}
else if (_wasNavOnSegment && !_navAgent.IsFollowingAPath)
{
// 导航刚结束(到达目标 / 路径失败)→ 清除残留 MoveDir
_enemyMovement.PendingInput.WantStop = true;
}
_wasNavOnSegment = onSegment;
}
// ── 工具 ───────────────────────────────────────────────────────
private static NavLinkType ParseLinkType(string name) => name switch
{

View File

@@ -1,5 +1,6 @@
using System;
using UnityEngine;
using BaseGames.Enemies;
namespace BaseGames.Enemies.Navigation
{
@@ -48,10 +49,11 @@ namespace BaseGames.Enemies.Navigation
public event Action OnGoalReached;
// ── 状态 ───────────────────────────────────────────────────────
private Rigidbody2D _rb;
private Vector2? _destination;
private bool _isMoving;
private bool _goalFired;
private Rigidbody2D _rb;
private EnemyMovement _movement;
private Vector2? _destination;
private bool _isMoving;
private bool _goalFired;
private float _hoverTimer;
private float _hoverFlipTimer;
@@ -67,7 +69,8 @@ namespace BaseGames.Enemies.Navigation
// ── Unity 生命周期 ─────────────────────────────────────────────
private void Awake()
{
_rb = GetComponent<Rigidbody2D>();
_rb = GetComponent<Rigidbody2D>();
_movement = GetComponent<EnemyMovement>();
_rb.gravityScale = 0f;
_rb.constraints = RigidbodyConstraints2D.FreezeRotation;
}
@@ -139,13 +142,23 @@ namespace BaseGames.Enemies.Navigation
Vector2 newPos = Vector2.MoveTowards(myPos, target, _moveSpeed * Time.fixedDeltaTime);
_rb.MovePosition(newPos);
// 面向移动方向
// 面向移动方向(通过 EnemyMovement 输入信号,保持 _facingDir 与动画系统同步)
float dx = target.x - myPos.x;
if (Mathf.Abs(dx) > 0.05f)
{
var s = transform.localScale;
s.x = Mathf.Abs(s.x) * Mathf.Sign(dx);
transform.localScale = s;
int dir = dx > 0f ? 1 : -1;
if (_movement != null)
{
_movement.PendingInput.WantFace = true;
_movement.PendingInput.FaceDir = dir;
}
else
{
// 降级:没有 EnemyMovement 时直接翻转(独立飞行单位)
var s = transform.localScale;
s.x = Mathf.Abs(s.x) * dir;
transform.localScale = s;
}
}
}