feat(combat): HitBox 新增 _targetLayers 实例级伤害目标过滤

Physics2D 碰撞矩阵只决定哪些层之间产生 Trigger 事件(全局门),无法表达单个判定盒的伤害目标差异。新增 _targetLayers 掩码:事件发生后仅对配置层结算伤害,拼刀检测独立于该过滤;Enter 入口同步过滤,非目标不进入命中/占用跟踪。默认 Everything,存量资产行为不变。

ENM_CaoZhi 接触伤害盒按规范配置 _targetLayers=PlayerHurtBox(不误伤友军),并清掉误配为 Player 层的 _rivalHitBoxMask(身体接触不参与拼刀)。已实机验证收紧后接触伤害正常。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 10:15:27 +08:00
parent 8515dfa7ab
commit ef7ff63b67
2 changed files with 34 additions and 12 deletions

View File

@@ -657,6 +657,11 @@ MonoBehaviour:
m_EditorClassIdentifier:
_defaultSource: {fileID: 11400000, guid: caae9c7600281fe4e8d8637fa3fd2ca1, type: 2}
_hitCooldown: 0.1
_hitMode: 0
_hitInterval: 0.5
_targetLayers:
serializedVersion: 2
m_Bits: 32768
_id:
_rivalHitBoxMask:
serializedVersion: 2

View File

@@ -34,6 +34,12 @@ namespace BaseGames.Combat
[Tooltip("Interval 模式下,对同一目标重复造成伤害的间隔(秒)。")]
[SerializeField] private float _hitInterval = 0.5f;
[Header("伤害目标")]
[Tooltip("本判定盒可造成伤害的 Layer实例级过滤叠加在 Physics2D 碰撞矩阵之上)。" +
"矩阵决定哪些层之间会产生 Trigger 事件(全局),此掩码决定事件发生后是否实际结算伤害(单盒)。" +
"默认 Everything = 行为与仅靠矩阵时一致。典型配置EnemyHitBox 盒勾 PlayerHurtBox不误伤友军则不勾 EnemyHurtBox。")]
[SerializeField] private LayerMask _targetLayers = ~0;
/// <summary>
/// HitBox 标识符,供 PlayerAnimationEvents / EnemyAnimationEvents 按名称精确激活特定判定盒。
/// 留空表示"无 Id";事件 payload 为空时将操作所有 HitBox。
@@ -203,6 +209,13 @@ namespace BaseGames.Combat
Debug.LogWarning($"[HitBox] {name}: 无 DamageSourceSO跳过命中。", this);
return;
}
// 目标层过滤:既非伤害目标层、也非拼刀对象层时直接忽略,
// 不进入命中/占用跟踪(矩阵管"事件是否发生",此掩码管"本盒是否结算"
int enterLayer = other.gameObject.layer;
bool isDamageTarget = (_targetLayers.value & (1 << enterLayer)) != 0;
bool isClashRival = (_rivalHitBoxMask.value & (1 << enterLayer)) != 0 && CanClash;
if (!isDamageTarget && !isClashRival) return;
if (_hitMode == HitMode.Interval)
{
// 周期接触模式:记录占用并立即结算一次;后续由 Update 按间隔对仍停留的目标重判。
@@ -226,18 +239,6 @@ namespace BaseGames.Combat
// 排除自身:攻击方与受击方属于同一根 GameObject 时跳过(防止近战 EnemyHitBox 命中同一敌人的 EnemyHurtBox
if (other.transform.root == _attackerTransform.root) return;
Vector2 knockDir = ((Vector2)other.bounds.center
- (Vector2)_attackerTransform.position).normalized;
// ⚡ 零 GCstruct 工厂,运行时字段内联传入
var info = DamageInfo.From(
_currentSource,
knockDir,
_attackerTransform.position,
_attackerTransform.gameObject.layer,
_ownerProjectile);
info.HitActivationId = _currentActivationId;
// ① 拼刀检测:当前 HitBox 携带 CanClash 标记,且碰到对立阵营的 HitBox 层
int otherLayer = other.gameObject.layer;
bool isRivalHitBoxLayer = (_rivalHitBoxMask.value & (1 << otherLayer)) != 0;
@@ -251,6 +252,22 @@ namespace BaseGames.Combat
}
}
// 目标层过滤:拼刀之外,仅对 _targetLayers 内的层结算伤害。
// Enter 入口已过滤,这里再守一次:覆盖 Interval 占用期间目标 Layer 被运行时改变的情况(如投射物弹反换层)。
if ((_targetLayers.value & (1 << otherLayer)) == 0) return;
Vector2 knockDir = ((Vector2)other.bounds.center
- (Vector2)_attackerTransform.position).normalized;
// ⚡ 零 GCstruct 工厂,运行时字段内联传入
var info = DamageInfo.From(
_currentSource,
knockDir,
_attackerTransform.position,
_attackerTransform.gameObject.layer,
_ownerProjectile);
info.HitActivationId = _currentActivationId;
// ② 命中 HurtBox
if (other.TryGetComponent<HurtBox>(out var hurtBox))
{