Files
zeling_v2/Assets/Scripts/Combat/StatusEffects/StatusEffectManager.cs
2026-05-13 09:19:54 +08:00

186 lines
7.7 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 System;
using System.Collections.Generic;
using UnityEngine;
namespace BaseGames.Combat.StatusEffects
{
/// <summary>
/// 状态效果管理器。
///
/// 架构 06_CombatModule §11
/// - 双结构ListUpdate 遍历)+ DictionaryO(1) 类型查找)
/// - 实现 IStatusEffectable接受来自 HurtBox 的 DamageType 并映射到具体效果
/// - DealDotDamageStatusEffect 子类通过 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,
});
}
}
}