feat(combat): 投射物接入伤害目标层过滤,弹反阵营/目标同步翻转
HitBox 暴露 TargetLayers 运行时读写。Projectile 缓存预制体初始目标层并在 Initialize 还原(对象池复用不被上一发弹反污染);ReflectAsPlayerProjectile 时目标层随阵营翻转(PlayerHurtBox→EnemyHurtBox,保留可破坏物等其他位)。 ParryableProjectile 绕过 HitBox 自行判定,补上同样的目标层过滤;并修复其弹反分支不切 PlayerProjectile 层的问题——原先反射后仍留在 EnemyProjectile 层,碰撞矩阵 EnemyProjectile↔EnemyHurtBox 不碰撞,反射弹永远打不中敌人。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -299,6 +299,16 @@ namespace BaseGames.Combat
|
|||||||
// Update 轮询时的临时缓冲(避免遍历字典时修改 + 降低 GC)
|
// Update 轮询时的临时缓冲(避免遍历字典时修改 + 降低 GC)
|
||||||
private readonly List<Collider2D> _intervalTickBuffer = new(8);
|
private readonly List<Collider2D> _intervalTickBuffer = new(8);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 伤害目标层掩码(实例级过滤,叠加在 Physics2D 碰撞矩阵之上)。
|
||||||
|
/// 运行时可写:投射物弹反换阵营时由 Projectile 翻转目标侧。
|
||||||
|
/// </summary>
|
||||||
|
public LayerMask TargetLayers
|
||||||
|
{
|
||||||
|
get => _targetLayers;
|
||||||
|
set => _targetLayers = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 配置为周期接触伤害模式(BodyContactDamage 等持续接触源在 OnEnable 时调用)。
|
/// 配置为周期接触伤害模式(BodyContactDamage 等持续接触源在 OnEnable 时调用)。
|
||||||
/// 使持续接触的判定盒按 <paramref name="interval"/> 对停留目标重复造成伤害,
|
/// 使持续接触的判定盒按 <paramref name="interval"/> 对停留目标重复造成伤害,
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ namespace BaseGames.Combat
|
|||||||
Direction = -Direction;
|
Direction = -Direction;
|
||||||
_rb.velocity = Direction * _config.Speed * _config.ParrySpeedMultiplier;
|
_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)
|
if (_reflectSource != null)
|
||||||
DamageInfo = DamageInfo.From(_reflectSource);
|
DamageInfo = DamageInfo.From(_reflectSource);
|
||||||
return;
|
return;
|
||||||
@@ -41,6 +48,8 @@ namespace BaseGames.Combat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── 正常命中 ─────────────────────────────────────────────────
|
// ── 正常命中 ─────────────────────────────────────────────────
|
||||||
|
// 本类绕过 HitBox 自行判定,需自行套用其伤害目标层过滤
|
||||||
|
if ((_hitBox.TargetLayers.value & (1 << other.gameObject.layer)) == 0) return;
|
||||||
var hurtBox = other.GetComponent<HurtBox>();
|
var hurtBox = other.GetComponent<HurtBox>();
|
||||||
if (hurtBox != null)
|
if (hurtBox != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ namespace BaseGames.Combat
|
|||||||
private int _hitsRemaining;
|
private int _hitsRemaining;
|
||||||
// 弹反帧标记:弹反命中不计入命中预算(避免反射后立即被回收)
|
// 弹反帧标记:弹反命中不计入命中预算(避免反射后立即被回收)
|
||||||
private bool _justReflected;
|
private bool _justReflected;
|
||||||
|
// 预制体上配置的初始伤害目标层:对象池复用时还原,避免上一发弹反翻转后的掩码污染下一发
|
||||||
|
private LayerMask _initialTargetLayers;
|
||||||
|
|
||||||
private PooledObject _pooledObject;
|
private PooledObject _pooledObject;
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ namespace BaseGames.Combat
|
|||||||
_rb = GetComponent<Rigidbody2D>();
|
_rb = GetComponent<Rigidbody2D>();
|
||||||
_hitBox = GetComponent<HitBox>();
|
_hitBox = GetComponent<HitBox>();
|
||||||
_pooledObject = GetComponent<PooledObject>();
|
_pooledObject = GetComponent<PooledObject>();
|
||||||
|
_initialTargetLayers = _hitBox.TargetLayers;
|
||||||
// 订阅命中确认:按命中预算决定何时回收(穿透 / 命中即消失)
|
// 订阅命中确认:按命中预算决定何时回收(穿透 / 命中即消失)
|
||||||
_hitBox.OnHitConfirmed += HandleHitConfirmed;
|
_hitBox.OnHitConfirmed += HandleHitConfirmed;
|
||||||
}
|
}
|
||||||
@@ -49,6 +52,8 @@ namespace BaseGames.Combat
|
|||||||
_maxHits = config.MaxHits;
|
_maxHits = config.MaxHits;
|
||||||
_hitsRemaining = config.MaxHits;
|
_hitsRemaining = config.MaxHits;
|
||||||
_justReflected = false;
|
_justReflected = false;
|
||||||
|
// 还原预制体配置的伤害目标层(清除上一次弹反翻转的影响)
|
||||||
|
_hitBox.TargetLayers = _initialTargetLayers;
|
||||||
|
|
||||||
SetFactionLayer(ownerLayer);
|
SetFactionLayer(ownerLayer);
|
||||||
_hitBox.Activate(config.DamageSource);
|
_hitBox.Activate(config.DamageSource);
|
||||||
@@ -82,12 +87,29 @@ namespace BaseGames.Combat
|
|||||||
// 重置 HitBox 命中记录,确保反射后可命中新目标
|
// 重置 HitBox 命中记录,确保反射后可命中新目标
|
||||||
_hitBox.Deactivate();
|
_hitBox.Deactivate();
|
||||||
_hitBox.Activate(_config?.DamageSource);
|
_hitBox.Activate(_config?.DamageSource);
|
||||||
|
// 伤害目标随阵营翻转:玩家侧 → 敌人侧
|
||||||
|
RetargetToEnemyFaction();
|
||||||
|
|
||||||
// 反射后重置命中预算,并跳过本次(弹反)命中的扣减,避免反射后立即被回收
|
// 反射后重置命中预算,并跳过本次(弹反)命中的扣减,避免反射后立即被回收
|
||||||
_hitsRemaining = _maxHits;
|
_hitsRemaining = _maxHits;
|
||||||
_justReflected = true;
|
_justReflected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 弹反换阵营后,把伤害目标从玩家侧切到敌人侧
|
||||||
|
/// (仅交换 PlayerHurtBox→EnemyHurtBox 位,保留可破坏物等其他目标层)。
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>HitBox 命中确认回调:按命中预算决定是否回收(穿透 / 命中即消失)。</summary>
|
/// <summary>HitBox 命中确认回调:按命中预算决定是否回收(穿透 / 命中即消失)。</summary>
|
||||||
private void HandleHitConfirmed(DamageInfo _)
|
private void HandleHitConfirmed(DamageInfo _)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user