Files
zeling_v2/Assets/_Game/Scripts/Combat/HurtBox.cs
2026-05-19 23:20:44 +08:00

137 lines
6.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using BaseGames.Parry;
namespace BaseGames.Combat
{
/// <summary>
/// 受击盒组件。实现完整 8 步伤害流水线(架构 06_CombatModule §5
/// 挂载在角色根节点或指定子节点上Collider2D 需设 IsTrigger = true
/// Layer = PlayerHurtBox 或 EnemyHurtBox。
/// </summary>
[RequireComponent(typeof(Collider2D))]
public class HurtBox : MonoBehaviour
{
// ── 伤害接受方Awake 注入)──────────────────────────────────────────
private IDamageable _owner;
private IShieldable _shieldable; // 由 PlayerController.Awake() 注入
private ParrySystem _parrySystem; // 由 PlayerController.Awake() 注入
private IPoiseSource _poiseSource; // 由 EnemyBase.Awake() 注入
private IStatusEffectable _statusEffectable; // Awake 缓存,避免每次受击调用 GetComponent
private bool _isHurtBoxInvincible;
private bool _isActive = true;
// ── 事件频道 ──────────────────────────────────────────────────────────
[SerializeField] private DamageInfoEventChannelSO _onDamageDealt;
[SerializeField] private HitConfirmedEventChannelSO _onHitConfirmed;
// ── 注入接口 ──────────────────────────────────────────────────────────
public void SetShieldable(IShieldable shieldable) => _shieldable = shieldable;
public void SetParrySystem(ParrySystem ps) => _parrySystem = ps;
public void SetPoiseSource(IPoiseSource src) => _poiseSource = src;
public void SetInvincible(bool value) => _isHurtBoxInvincible = value;
public void SetActive(bool value) => _isActive = value;
#if UNITY_EDITOR
// 付给编辑器的只读属性——避免反射并限制编辑器与运行时字段名耐合性。
public object EditorOwner => _owner;
public object EditorShieldable => _shieldable;
public object EditorParrySystem => _parrySystem;
public object EditorPoiseSource => _poiseSource;
public object EditorStatusEffectable => _statusEffectable;
#endif
private void Awake()
{
_owner = GetComponentInParent<IDamageable>();
_statusEffectable = GetComponentInParent<IStatusEffectable>();
if (_owner == null)
Debug.LogWarning($"[HurtBox] {name}: 父节点中未找到 IDamageable 实现。", this);
}
/// <summary>
/// 接受伤害(由 HitBox.OnTriggerEnter2D 直接调用)。
/// <param name="hitPoint">弹击点世界坐标;不传则默认使用 HurtBox 节点中心。</param>
/// ⚠️ 方法名必须为 ReceiveDamage。
/// </summary>
public void ReceiveDamage(DamageInfo info, Vector3? hitPoint = null)
{
Vector3 resolvedHitPoint = hitPoint ?? transform.position;
if (!_isActive || _owner == null) return;
// 1. 无敌帧检查
if ((_owner.IsInvincible || _isHurtBoxInvincible)
&& !info.Flags.HasFlag(DamageFlags.IgnoreIFrame)) return;
// 2. 弹反检查_parrySystem == null 时跳过)
// ParrySystem 只暴露窗口状态,伤害数据留在 Combat 层,无跨程序集数据依赖。
if (_parrySystem != null && info.Flags.HasFlag(DamageFlags.CanBeParried))
{
if (_parrySystem.ConsumeParry())
{
// 若攻击来源是投射物,翻转其阵营 Layer 与飞行方向
info.SourceProjectile?.ReflectAsPlayerProjectile();
return;
}
}
// 3. 霸体检查_poiseSource == null 时跳过)
if (!info.Flags.HasFlag(DamageFlags.ForceBreak) && _poiseSource != null)
{
PoiseLevel curPoise = _poiseSource.GetCurrentPoiseLevel();
if (curPoise == PoiseLevel.Unbreakable) return;
if ((int)info.Break < (int)curPoise)
{
_onHitConfirmed?.Raise(new HitInfo
{
DamageInfo = info,
HitPoint = resolvedHitPoint,
});
return;
}
}
// 4. 护盾层拦截(玩家专属,在防御减免前)
if (_shieldable != null && _shieldable.HasShield)
{
int passThrough = _shieldable.AbsorbDamage(info.Amount);
if (passThrough <= 0) return;
info.Amount = passThrough;
}
// 5. 计算 FinalDamage防御减免最低 1
int finalDamage = UnityEngine.Mathf.Max(1, info.Amount - _owner.Defense);
info.Amount = finalDamage;
info.FinalDamage = finalDamage;
// 6. 调用 _owner.TakeDamage
_owner.TakeDamage(info);
// 7. 全局广播
_onDamageDealt?.Raise(info);
_onHitConfirmed?.Raise(new HitInfo
{
DamageInfo = info,
HitPoint = resolvedHitPoint,
});
// 8. 状态效果触发DoT — Fire / Poison
// _statusEffectable 已在 Awake 中缓存,无需每次受击调用 GetComponent
_statusEffectable?.ApplyStatusEffect(info.Type);
}
#if UNITY_EDITOR
private void OnDrawGizmos()
{
var col = GetComponent<Collider2D>();
if (col == null) return;
// HurtBox正常激活 = 青色,无敌 = 黄色,非激活 = 极淡青
Gizmos.color = !_isActive
? new Color(0f, 0.85f, 1f, 0.15f)
: _isHurtBoxInvincible
? new Color(1f, 1f, 0f, 1f )
: new Color(0f, 0.85f, 1f, 1f );
HitBox.DrawCollider2DWire(col);
}
#endif
}
}