140 lines
5.5 KiB
C#
140 lines
5.5 KiB
C#
using UnityEngine;
|
||
using System;
|
||
using BaseGames.Core.Events;
|
||
|
||
namespace BaseGames.Combat
|
||
{
|
||
/// <summary>
|
||
/// 护盾组件。实现 IShieldable 接口供 HurtBox 注入。
|
||
/// 护盾参数通过 ShieldConfigSO 集中配置。
|
||
/// </summary>
|
||
public class ShieldComponent : MonoBehaviour, IShieldable
|
||
{
|
||
[Header("配置资产")]
|
||
[SerializeField] private ShieldConfigSO _config;
|
||
|
||
[Header("VFX 事件频道")]
|
||
[SerializeField] private VoidEventChannelSO _onShieldBrokenChannel; // 护盾破碎 → 播放破碎 VFX
|
||
[SerializeField] private VoidEventChannelSO _onShieldRestoredChannel; // 护盾恢复 → 播放恢复 VFX
|
||
|
||
// ── 运行时属性 ────────────────────────────────────────────────────────
|
||
private int _maxShieldHP;
|
||
public int MaxShieldHP => _maxShieldHP;
|
||
public int CurrentShieldHP { get; private set; }
|
||
/// <summary>当前是否能吸收伤害(护盾 HP > 0 且不在破碎惩罚期)。</summary>
|
||
public bool HasShield => CurrentShieldHP > 0 && _brokenPenaltyTimer <= 0f;
|
||
|
||
private float AbsorptionRatio => _config.DamageAbsorptionRatio;
|
||
private float RechargeDelay => _config.RechargeDelay;
|
||
private float RechargeRate => _config.RechargeRate;
|
||
private float BrokenPenaltyDur => _config.BrokenPenaltyDuration;
|
||
private float ParryRestoreRatio => _config.ParryRestoreRatio;
|
||
|
||
public event Action<int, int> OnShieldChanged; // (current, max)
|
||
public event Action OnShieldBroken;
|
||
|
||
private float _regenDelayTimer;
|
||
private float _brokenPenaltyTimer;
|
||
|
||
private void Awake()
|
||
{
|
||
Debug.Assert(_config != null, "[ShieldComponent] _config 未赋值,请在 Inspector 中指定 ShieldConfigSO。", this);
|
||
_maxShieldHP = _config != null ? _config.MaxShieldHP : 0;
|
||
CurrentShieldHP = MaxShieldHP;
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
int maxHP = MaxShieldHP;
|
||
if (maxHP <= 0) return;
|
||
|
||
// 破碎惩罚计时
|
||
if (_brokenPenaltyTimer > 0f)
|
||
{
|
||
_brokenPenaltyTimer -= Time.deltaTime;
|
||
// 破碎惩罚结束 → 广播护盾恢复事件(VFX 钩子)
|
||
if (_brokenPenaltyTimer <= 0f)
|
||
_onShieldRestoredChannel?.Raise();
|
||
return;
|
||
}
|
||
|
||
if (CurrentShieldHP >= maxHP) return;
|
||
if (RechargeRate <= 0f) return;
|
||
|
||
if (_regenDelayTimer > 0f)
|
||
{
|
||
_regenDelayTimer -= Time.deltaTime;
|
||
return;
|
||
}
|
||
|
||
int prev = CurrentShieldHP;
|
||
CurrentShieldHP = Mathf.Min(CurrentShieldHP + Mathf.CeilToInt(RechargeRate * Time.deltaTime), maxHP);
|
||
if (CurrentShieldHP != prev)
|
||
OnShieldChanged?.Invoke(CurrentShieldHP, maxHP);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试以护盾吸收伤害。返回穿透量(0=全部吸收,>0=穿透量继续走 TakeDamage 流程)。
|
||
/// </summary>
|
||
public int AbsorbDamage(int amount)
|
||
{
|
||
if (!HasShield) return amount;
|
||
|
||
_regenDelayTimer = RechargeDelay;
|
||
|
||
int maxHP = MaxShieldHP;
|
||
// 按吸收比例计算实际由护盾承担的伤害
|
||
int toAbsorb = Mathf.FloorToInt(amount * AbsorptionRatio);
|
||
toAbsorb = Mathf.Min(toAbsorb, CurrentShieldHP);
|
||
int passthrough = amount - toAbsorb;
|
||
|
||
CurrentShieldHP -= toAbsorb;
|
||
OnShieldChanged?.Invoke(CurrentShieldHP, maxHP);
|
||
|
||
if (CurrentShieldHP <= 0)
|
||
{
|
||
CurrentShieldHP = 0;
|
||
_brokenPenaltyTimer = BrokenPenaltyDur;
|
||
OnShieldBroken?.Invoke();
|
||
_onShieldBrokenChannel?.Raise(); // VFX 钩子:播放护盾破碎特效
|
||
}
|
||
|
||
return passthrough;
|
||
}
|
||
|
||
/// <summary>存档点 / 复活时调用:完全恢复护盾并清除惩罚状态。</summary>
|
||
public void FullRecharge()
|
||
{
|
||
bool wasBroken = _brokenPenaltyTimer > 0f || CurrentShieldHP <= 0;
|
||
_brokenPenaltyTimer = 0f;
|
||
_regenDelayTimer = 0f;
|
||
CurrentShieldHP = MaxShieldHP;
|
||
OnShieldChanged?.Invoke(CurrentShieldHP, MaxShieldHP);
|
||
if (wasBroken)
|
||
_onShieldRestoredChannel?.Raise(); // VFX 钩子:护盾已满血恢复
|
||
}
|
||
|
||
/// <summary>弹反成功时调用:按 ParryRestoreRatio 恢复护盾(会清除惩罚状态)。</summary>
|
||
public void OnParrySuccess()
|
||
{
|
||
int maxHP = MaxShieldHP;
|
||
if (maxHP <= 0) return;
|
||
|
||
_brokenPenaltyTimer = 0f;
|
||
_regenDelayTimer = 0f;
|
||
int restore = Mathf.CeilToInt(maxHP * ParryRestoreRatio);
|
||
CurrentShieldHP = Mathf.Min(CurrentShieldHP + restore, maxHP);
|
||
OnShieldChanged?.Invoke(CurrentShieldHP, maxHP);
|
||
}
|
||
|
||
/// <summary>Inspector / 道具系统调用:设置最大护盾并重置当前值。</summary>
|
||
public void SetMaxShieldHP(int max)
|
||
{
|
||
_maxShieldHP = Mathf.Max(0, max);
|
||
_brokenPenaltyTimer = 0f;
|
||
CurrentShieldHP = MaxShieldHP;
|
||
OnShieldChanged?.Invoke(CurrentShieldHP, MaxShieldHP);
|
||
}
|
||
}
|
||
}
|