186 lines
7.7 KiB
C#
186 lines
7.7 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace BaseGames.Combat.StatusEffects
|
||
{
|
||
/// <summary>
|
||
/// 状态效果管理器。
|
||
///
|
||
/// 架构 06_CombatModule §11:
|
||
/// - 双结构:List(Update 遍历)+ Dictionary(O(1) 类型查找)
|
||
/// - 实现 IStatusEffectable,接受来自 HurtBox 的 DamageType 并映射到具体效果
|
||
/// - DealDotDamage:StatusEffect 子类通过 Owner 调用,绕过无敌帧造成 DoT
|
||
/// - CleanseEffect:净化指定类型效果(道具/技能使用)
|
||
/// </summary>
|
||
[RequireComponent(typeof(SpriteRenderer))]
|
||
public class StatusEffectManager : MonoBehaviour, IStatusEffectable
|
||
{
|
||
[Header("事件频道(可选)")]
|
||
[SerializeField] private StatusEffectEventChannelSO _onStatusEffectApplied;
|
||
[SerializeField] private StatusEffectEventChannelSO _onStatusEffectExpired;
|
||
|
||
// ── 双结构 ─────────────────────────────────────────────────────────
|
||
private readonly List<StatusEffect> _activeList = new();
|
||
private readonly Dictionary<StatusEffectType, StatusEffect> _activeIndex = new();
|
||
|
||
// ── Shader 渲染(MaterialPropertyBlock,不修改共享材质)─────────
|
||
private SpriteRenderer _renderer;
|
||
private MaterialPropertyBlock _propBlock;
|
||
|
||
// ── DoT 伤害代理(由 StatusEffect.OnTick 通过 Owner 调用)──────────
|
||
private IDamageable _damageable;
|
||
|
||
// ── 效果工厂字典(可在 Awake 后动态注册)─────────────────────
|
||
private readonly Dictionary<DamageType, Func<StatusEffect>> _effectFactories = new();
|
||
|
||
private void Awake()
|
||
{
|
||
_renderer = GetComponent<SpriteRenderer>();
|
||
_propBlock = new MaterialPropertyBlock();
|
||
_damageable = GetComponentInParent<IDamageable>();
|
||
|
||
// 默认标准效果注册(子类或外部模块可调用 RegisterEffectFactory 覆盖或扩展)
|
||
RegisterEffectFactory(DamageType.Fire, () => new FireEffect());
|
||
RegisterEffectFactory(DamageType.Poison, () => new PoisonEffect());
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
float delta = Time.deltaTime;
|
||
|
||
// 逆序遍历,避免移除时索引错位
|
||
for (int i = _activeList.Count - 1; i >= 0; i--)
|
||
{
|
||
StatusEffect effect = _activeList[i];
|
||
effect.Update(delta);
|
||
|
||
if (effect.IsExpired)
|
||
RemoveAt(i, effect);
|
||
}
|
||
}
|
||
|
||
// ── IStatusEffectable 实现 ─────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// HurtBox 调用入口:将 DamageType 映射为具体 StatusEffect 实例并施加。
|
||
/// </summary>
|
||
public void ApplyStatusEffect(DamageType type)
|
||
{
|
||
StatusEffect effect = CreateEffect(type);
|
||
if (effect != null)
|
||
ApplyEffect(effect);
|
||
}
|
||
/// <summary>
|
||
/// 注册或覆盖一个 DamageType 对应的效果工厂。
|
||
/// Boss 或特殊游玩法式可在运行时注册自定义效果。
|
||
/// </summary>
|
||
public void RegisterEffectFactory(DamageType type, Func<StatusEffect> factory)
|
||
=> _effectFactories[type] = factory;
|
||
// ── 公开 API ───────────────────────────────────────────────────────
|
||
|
||
/// <summary>直接施加一个具体效果(供技能/Boss 使用)。</summary>
|
||
public void ApplyEffect(StatusEffect effect)
|
||
{
|
||
// 检查是否被现有效果阻断
|
||
foreach (var blockerType in effect.BlockedBy)
|
||
if (_activeIndex.ContainsKey(blockerType)) return;
|
||
|
||
// 净化与新效果互斥的现有效果
|
||
foreach (var excludedType in effect.MutualExclusions)
|
||
if (_activeIndex.ContainsKey(excludedType))
|
||
CleanseEffect(excludedType);
|
||
|
||
if (_activeIndex.TryGetValue(effect.EffectType, out StatusEffect existing))
|
||
{
|
||
existing.OnStack();
|
||
BroadcastApplied(existing);
|
||
}
|
||
else
|
||
{
|
||
effect.OnApply(this);
|
||
_activeList.Add(effect);
|
||
_activeIndex[effect.EffectType] = effect;
|
||
BroadcastApplied(effect);
|
||
}
|
||
}
|
||
|
||
/// <summary>净化指定类型效果(净化道具/技能调用)。</summary>
|
||
public void CleanseEffect(StatusEffectType type)
|
||
{
|
||
if (!_activeIndex.TryGetValue(type, out StatusEffect effect)) return;
|
||
|
||
effect.OnExpire();
|
||
_activeIndex.Remove(type);
|
||
_activeList.Remove(effect);
|
||
BroadcastExpired(effect);
|
||
}
|
||
|
||
/// <summary>查询是否存在指定类型效果(供状态机/UI 轮询)。</summary>
|
||
public bool HasEffect(StatusEffectType type) => _activeIndex.ContainsKey(type);
|
||
|
||
/// <summary>获取指定类型效果(可为 null)。</summary>
|
||
public StatusEffect GetEffect(StatusEffectType type)
|
||
=> _activeIndex.TryGetValue(type, out var e) ? e : null;
|
||
|
||
/// <summary>
|
||
/// DoT 伤害代理(架构 06 §10)。StatusEffect.OnTick() 调用此方法,传入已构建好的 DamageInfo。
|
||
/// </summary>
|
||
public void ApplyDirectDamage(DamageInfo info)
|
||
{
|
||
_damageable?.TakeDamage(info);
|
||
}
|
||
|
||
/// <summary>设置 Sprite Shader 参数(MaterialPropertyBlock,不修改共享材质)。</summary>
|
||
public void SetShaderParam(string param, float value)
|
||
{
|
||
if (_renderer == null) return;
|
||
_renderer.GetPropertyBlock(_propBlock);
|
||
_propBlock.SetFloat(param, value);
|
||
_renderer.SetPropertyBlock(_propBlock);
|
||
}
|
||
|
||
/// <summary>净化所有状态效果(存档点激活 / 返回城镇等调用)。</summary>
|
||
public void CleanseAll()
|
||
{
|
||
foreach (var e in _activeList) e.OnExpire();
|
||
_activeList.Clear();
|
||
_activeIndex.Clear();
|
||
}
|
||
|
||
// ── 私有辅助 ───────────────────────────────────────────────────────
|
||
|
||
private StatusEffect CreateEffect(DamageType type)
|
||
=> _effectFactories.TryGetValue(type, out var factory) ? factory() : null;
|
||
|
||
private void RemoveAt(int index, StatusEffect effect)
|
||
{
|
||
effect.OnExpire();
|
||
_activeList.RemoveAt(index);
|
||
_activeIndex.Remove(effect.EffectType);
|
||
BroadcastExpired(effect);
|
||
}
|
||
|
||
private void BroadcastApplied(StatusEffect effect)
|
||
{
|
||
_onStatusEffectApplied?.Raise(new StatusEffectEvent
|
||
{
|
||
EffectType = effect.EffectType,
|
||
StackCount = effect.StackCount,
|
||
RemainingDuration = effect.Duration,
|
||
});
|
||
}
|
||
|
||
private void BroadcastExpired(StatusEffect effect)
|
||
{
|
||
_onStatusEffectExpired?.Raise(new StatusEffectEvent
|
||
{
|
||
EffectType = effect.EffectType,
|
||
StackCount = 0,
|
||
RemainingDuration = 0f,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|