From 1866f323e419d9820ee8258f34641f18ea29e4df Mon Sep 17 00:00:00 2001 From: Joywayer Date: Fri, 12 Jun 2026 10:34:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(combat):=20=E6=8A=95=E5=B0=84=E7=89=A9?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E4=BC=A4=E5=AE=B3=E7=9B=AE=E6=A0=87=E5=B1=82?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=EF=BC=8C=E5=BC=B9=E5=8F=8D=E9=98=B5=E8=90=A5?= =?UTF-8?q?/=E7=9B=AE=E6=A0=87=E5=90=8C=E6=AD=A5=E7=BF=BB=E8=BD=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HitBox 暴露 TargetLayers 运行时读写。Projectile 缓存预制体初始目标层并在 Initialize 还原(对象池复用不被上一发弹反污染);ReflectAsPlayerProjectile 时目标层随阵营翻转(PlayerHurtBox→EnemyHurtBox,保留可破坏物等其他位)。 ParryableProjectile 绕过 HitBox 自行判定,补上同样的目标层过滤;并修复其弹反分支不切 PlayerProjectile 层的问题——原先反射后仍留在 EnemyProjectile 层,碰撞矩阵 EnemyProjectile↔EnemyHurtBox 不碰撞,反射弹永远打不中敌人。 Co-Authored-By: Claude Opus 4.8 (1M context) --- Assets/_Game/Scripts/Combat/HitBox.cs | 10 +++++++++ .../Scripts/Combat/ParryableProjectile.cs | 9 ++++++++ Assets/_Game/Scripts/Combat/Projectile.cs | 22 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/Assets/_Game/Scripts/Combat/HitBox.cs b/Assets/_Game/Scripts/Combat/HitBox.cs index 6a6568f..5de485f 100644 --- a/Assets/_Game/Scripts/Combat/HitBox.cs +++ b/Assets/_Game/Scripts/Combat/HitBox.cs @@ -299,6 +299,16 @@ namespace BaseGames.Combat // Update 轮询时的临时缓冲(避免遍历字典时修改 + 降低 GC) private readonly List _intervalTickBuffer = new(8); + /// + /// 伤害目标层掩码(实例级过滤,叠加在 Physics2D 碰撞矩阵之上)。 + /// 运行时可写:投射物弹反换阵营时由 Projectile 翻转目标侧。 + /// + public LayerMask TargetLayers + { + get => _targetLayers; + set => _targetLayers = value; + } + /// /// 配置为周期接触伤害模式(BodyContactDamage 等持续接触源在 OnEnable 时调用)。 /// 使持续接触的判定盒按 对停留目标重复造成伤害, diff --git a/Assets/_Game/Scripts/Combat/ParryableProjectile.cs b/Assets/_Game/Scripts/Combat/ParryableProjectile.cs index ae9e8c7..5541db2 100644 --- a/Assets/_Game/Scripts/Combat/ParryableProjectile.cs +++ b/Assets/_Game/Scripts/Combat/ParryableProjectile.cs @@ -34,6 +34,13 @@ namespace BaseGames.Combat Direction = -Direction; _rb.velocity = Direction * _config.Speed * _config.ParrySpeedMultiplier; + // 阵营随弹反翻转:切到 PlayerProjectile 层(否则碰撞矩阵 + // EnemyProjectile↔EnemyHurtBox 不碰撞,反射后永远打不中敌人), + // 伤害目标层同步从玩家侧切到敌人侧。 + int playerProjLayer = LayerMask.NameToLayer("PlayerProjectile"); + if (playerProjLayer >= 0) gameObject.layer = playerProjLayer; + RetargetToEnemyFaction(); + if (_reflectSource != null) DamageInfo = DamageInfo.From(_reflectSource); return; @@ -41,6 +48,8 @@ namespace BaseGames.Combat } // ── 正常命中 ───────────────────────────────────────────────── + // 本类绕过 HitBox 自行判定,需自行套用其伤害目标层过滤 + if ((_hitBox.TargetLayers.value & (1 << other.gameObject.layer)) == 0) return; var hurtBox = other.GetComponent(); if (hurtBox != null) { diff --git a/Assets/_Game/Scripts/Combat/Projectile.cs b/Assets/_Game/Scripts/Combat/Projectile.cs index 7f5e993..a08fc0a 100644 --- a/Assets/_Game/Scripts/Combat/Projectile.cs +++ b/Assets/_Game/Scripts/Combat/Projectile.cs @@ -26,6 +26,8 @@ namespace BaseGames.Combat private int _hitsRemaining; // 弹反帧标记:弹反命中不计入命中预算(避免反射后立即被回收) private bool _justReflected; + // 预制体上配置的初始伤害目标层:对象池复用时还原,避免上一发弹反翻转后的掩码污染下一发 + private LayerMask _initialTargetLayers; private PooledObject _pooledObject; @@ -34,6 +36,7 @@ namespace BaseGames.Combat _rb = GetComponent(); _hitBox = GetComponent(); _pooledObject = GetComponent(); + _initialTargetLayers = _hitBox.TargetLayers; // 订阅命中确认:按命中预算决定何时回收(穿透 / 命中即消失) _hitBox.OnHitConfirmed += HandleHitConfirmed; } @@ -49,6 +52,8 @@ namespace BaseGames.Combat _maxHits = config.MaxHits; _hitsRemaining = config.MaxHits; _justReflected = false; + // 还原预制体配置的伤害目标层(清除上一次弹反翻转的影响) + _hitBox.TargetLayers = _initialTargetLayers; SetFactionLayer(ownerLayer); _hitBox.Activate(config.DamageSource); @@ -82,12 +87,29 @@ namespace BaseGames.Combat // 重置 HitBox 命中记录,确保反射后可命中新目标 _hitBox.Deactivate(); _hitBox.Activate(_config?.DamageSource); + // 伤害目标随阵营翻转:玩家侧 → 敌人侧 + RetargetToEnemyFaction(); // 反射后重置命中预算,并跳过本次(弹反)命中的扣减,避免反射后立即被回收 _hitsRemaining = _maxHits; _justReflected = true; } + /// + /// 弹反换阵营后,把伤害目标从玩家侧切到敌人侧 + /// (仅交换 PlayerHurtBox→EnemyHurtBox 位,保留可破坏物等其他目标层)。 + /// + protected void RetargetToEnemyFaction() + { + int playerHurt = LayerMask.NameToLayer("PlayerHurtBox"); + int enemyHurt = LayerMask.NameToLayer("EnemyHurtBox"); + if (playerHurt < 0 || enemyHurt < 0) return; // Layer 尚未创建,保留现有掩码 + int mask = _hitBox.TargetLayers.value; + mask &= ~(1 << playerHurt); + mask |= 1 << enemyHurt; + _hitBox.TargetLayers = mask; + } + /// HitBox 命中确认回调:按命中预算决定是否回收(穿透 / 命中即消失)。 private void HandleHitConfirmed(DamageInfo _) {