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

@@ -100,6 +100,20 @@ namespace BaseGames.Player
// 开启连续碰撞检测CCDKinematic 移动平台通过 MovePosition 将 Dynamic 角色推向墙体时,
// CCD 会沿移动路径追踪碰撞,确保角色在物理层被墙体表面拦截,而不是在离散步骤中穿透墙体。
_rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
if (_config != null)
_config.OnConfigChanged += OnMovementConfigChanged;
}
private void OnDestroy()
{
if (_config != null)
_config.OnConfigChanged -= OnMovementConfigChanged;
}
private void OnMovementConfigChanged()
{
if (_rb != null)
_rb.gravityScale = _config.DefaultGravityScale;
}
private void FixedUpdate()

View File

@@ -1,3 +1,4 @@
using System;
using UnityEngine;
namespace BaseGames.Player
@@ -26,6 +27,11 @@ namespace BaseGames.Player
[Tooltip("松开跳跃键时速度保留比例(变高跳)。推荐 0.35,越小跳跃越低。")]
[Range(0f, 1f)]
public float JumpCutMultiplier = 0.35f;
[Tooltip("短按最小跳跃保护窗口(秒)。\n" +
"窗口内松开跳跃键不会立即截断速度;窗口结束时统一判断是否截断,\n" +
"保证无论短按多快/多慢,最小跳跃高度始终一致(消除帧级手感抖动)。\n" +
"推荐 0.08≈5帧@60fps。调高 → 最小跳更高;调低 → 最小跳更低。")]
public float MinJumpTime = 0.08f;
[Header("跳跃 — 顶点悬停")]
[Tooltip("顶点悬停触发阈值(单位/秒)。当 |垂直速度| 低于此值时,重力缩减为 ApexGravityMultiplier 倍,\n产生\"滞空感\"。推荐 3。调高 → 悬停段更长;调低 → 悬停段更短乃至消失。")]
@@ -92,5 +98,16 @@ namespace BaseGames.Player
[Header("重力")]
public float DefaultGravityScale = 3f;
// ── 运行时热更新通知 ──────────────────────────────────────────────────
/// <summary>
/// Inspector 中修改配置时触发(仅编辑器),订阅方可立即应用新值,无需重启 PlayMode。
/// </summary>
public event Action OnConfigChanged;
private void OnValidate()
{
OnConfigChanged?.Invoke();
}
}
}

View File

@@ -16,6 +16,11 @@ namespace BaseGames.Player.States
{
private bool _isDoubleJump;
// 最小跳跃窗口:窗口内松键记录 _cutPending窗口结束时统一执行 CutJump
// 保证短按始终在相同 vy 时截断 → 最小跳跃高度完全一致,消除帧级手感抖动。
private float _minJumpTimer;
private bool _cutPending;
public JumpState(PlayerController owner) : base(owner) { }
/// <summary>
@@ -40,6 +45,7 @@ namespace BaseGames.Player.States
Move.Jump();
_isDoubleJump = false; // 消耗标记
ResetMinJumpWindow();
Input.JumpCancelledEvent += OnJumpCancelled;
// 开启上升阶段贴墙 vy 保护:防止物理摩擦降低跳跃最高点
Move.SetPreserveVyOnWallContact(true);
@@ -96,6 +102,7 @@ namespace BaseGames.Player.States
{
Owner.UseAirJump();
Move.DoubleJump();
ResetMinJumpWindow(); // 二段跳重置最小跳跃窗口
// 播放空中跳跃动画,未配置时回退到 Jump
var airJumpClip = AnimCfg?.AirJump ?? AnimCfg?.Jump;
if (airJumpClip != null) Anim?.Play(airJumpClip);
@@ -134,6 +141,20 @@ namespace BaseGames.Player.States
public override void OnStateFixedUpdate()
{
// ── 最小跳跃窗口:窗口内松键不立即截断,窗口结束时统一处理 ─────────────
// 保证短按(无论在窗口内哪帧松键)都在相同 vy 时执行 CutJump → 一致的最小跳跃高度。
if (_minJumpTimer > 0f)
{
_minJumpTimer -= Time.fixedDeltaTime;
if (_minJumpTimer <= 0f)
{
// 窗口到期已收到松键事件或当前键未按住InputBuffer 延迟导致进入时键已释放)
if (_cutPending || !Input.IsJumpHeld)
Move.CutJump();
_cutPending = false;
}
}
// ── 顶点悬停:第一判断 |vy|,动态切换重力缩放系数 ────────────────
// |垂直速度| 低于顶点阈值时,重力缩减至 ApexGravityMultiplier 倍,
// 产生角色在跳跃顶点起起“滒空”的手感,属于高重力平台游戏的标志性特征。
@@ -191,6 +212,18 @@ namespace BaseGames.Player.States
Input.JumpCancelledEvent -= OnJumpCancelled;
}
private void OnJumpCancelled() => Move.CutJump();
private void OnJumpCancelled()
{
if (_minJumpTimer > 0f)
_cutPending = true; // 窗口内:推迟到窗口结束时执行,保证一致的截断时机
else
Move.CutJump(); // 窗口已过(长按):立即截断 → 变高跳
}
private void ResetMinJumpWindow()
{
_minJumpTimer = Cfg.MinJumpTime;
_cutPending = false;
}
}
}

View File

@@ -226,12 +226,12 @@ namespace BaseGames.Player.States
_overlayLayer = _animancer.Layers[1];
}
// 注入 HurtBox 依赖
if (_hurtBox != null)
// 注入 HurtBox 依赖(覆盖所有子节点 HurtBox支持多形状受击区
foreach (var hb in GetComponentsInChildren<HurtBox>(true))
{
if (_shield != null) _hurtBox.SetShieldable(_shield);
if (_parrySystem != null) _hurtBox.SetParrySystem(_parrySystem);
_hurtBox.SetPoiseSource(this);
if (_shield != null) hb.SetShieldable(_shield);
if (_parrySystem != null) hb.SetParrySystem(_parrySystem);
hb.SetPoiseSource(this);
}
// 将唯一配置点_inputReader注入到 ParrySystem。