Skip to content

Buff系统

简介

Buff系统提供了一个简单的状态管理功能,用于处理角色身上的基础状态效果。比如:

  • 增益效果:攻击力提升、防御力提升等
  • 减益效果:中毒、减速等

系统支持:

  • 基础的持续时间管理
  • 简单的Buff添加和移除

注意

如果达不到需求还需自行修改

核心代码

实体(Entity)定义

BuffInfo.cs
csharp
using System.Collections.Generic;
using cfg;
using Newtonsoft.Json;
namespace MH
{
    public class BuffInfo : Entity, IAwake, IAwake<int, Unit, Unit>, IDestroy
    {
        /// <summary>
        /// 计时器ID
        /// </summary>
        public long TimerId;
        /// <summary>
        /// 创建时间
        /// </summary>
        public long CreateTime;
        /// <summary>
        /// 结束时间
        /// </summary>
        public long EndTime;
        /// <summary>
        /// 配置ID
        /// </summary>
        public int ConfigId;
        /// <summary>
        /// 层数
        /// </summary>
        public int Layer;
        /// <summary>
        /// 施法者
        /// </summary>
        public Unit Caster;
        /// <summary>
        /// 目标
        /// </summary>
        public Unit Target;
        /// <summary>
        /// 配置
        /// </summary>
        [JsonIgnore]
        public BuffConfig Config => ConfigsSingleton.Instance.Tables.TbBuffConfig.Get(ConfigId);
    }
    /// <summary>
    /// buff添加类型
    /// </summary>
    public enum BuffAddType
    {
        ResetTime = 1,                     //重置Buff时间
        MultipleLayer,                 //增加Buff层数
        MultipleLayerAndResetTime,     //增加Buff层数且重置Buff时间
    }
    /// <summary>
    /// buff动作类型
    /// </summary>
    public enum BuffActionType
    {
        Add = 1,
        Sub,
        Override,
    }
    /// <summary>
    /// 周期结束类型
    /// </summary>
    public enum CycleEndType
    {
        Sub = 1,
        Clear,
    }
}
BuffInfosComponent.cs
csharp
using System.Collections.Generic;
namespace MH
{
    public class BuffInfosComponent: Entity, IAwake, IDestroy
    {
        /// <summary>
        /// buff列表
        /// </summary>
        public List<BuffInfo> BuffInfos = new List<BuffInfo>();
    }
}

系统(System)定义

BuffInfoSystem.cs
csharp
using System.Collections.Generic;
using UnityEngine;

namespace MH
{
    [Invoke(TimerInvokeType.BuffActionIntervalTimerInvoke)]
    public class Buff_TimerAction : ATimer<BuffInfo>
    {
        protected override void Run(BuffInfo self)
        {
            self.TryStartCountDown();
        }
    }
    [EntitySystem]
    public class BuffInfoAwakeSystem: AwakeSystem<BuffInfo, int, Unit, Unit>
    {
        protected override void Awake(BuffInfo self, int configId, Unit caster, Unit target)
        {
            self.ConfigId = configId;
            self.CreateTime = TimeInfo.Instance.ClientNow();
            self.EndTime = self.Config.BuffInfoConfig.BuffInfoTimer * 1000 + self.CreateTime;
            self.Caster = caster;
            self.Target = target;
            self.Layer = 1;
            BuffActionDispatcher.Instance.OnStart(self);
            self.TryStartCountDown();
        }
    }
    
    [EntitySystem]
    public class BuffInfoDestroySystem: DestroySystem<BuffInfo>
    {
        protected override void Destroy(BuffInfo self)
        {
            BuffActionDispatcher.Instance.OnEnd(self);
            self.EndTime = 0;
            self.ConfigId = 0;
            self.Target = null;
            self.Caster = null;
            self.Layer = 0;
            self.Root.GetComponent<TimerComponent>().Remove(ref self.TimerId);
        }
    }

    public static class BuffInfoSystem
    {
        /// <summary>
        /// 重复添加buff
        /// </summary>
        /// <param name="self"></param>
        public static void RepeatAddBuffInfo(this BuffInfo self)
        {
            int oldLayer = self.Layer;
            switch (self.Config.BuffInfoConfig.BuffAddType)
            {
                case (int)BuffAddType.ResetTime:
                    self.EndTime = self.Config.BuffInfoConfig.BuffInfoTimer * 1000 + TimeInfo.Instance.ClientNow();
                    break;
                case (int)BuffAddType.MultipleLayer:
                    if (self.Layer < self.Config.BuffInfoConfig.MaxLayer)
                        self.Layer++;
                    break;
                case (int)BuffAddType.MultipleLayerAndResetTime:
                    if (self.Layer < self.Config.BuffInfoConfig.MaxLayer)
                        self.Layer++;
                    self.EndTime = self.Config.BuffInfoConfig.BuffInfoTimer * 1000 + TimeInfo.Instance.ClientNow();
                    break;
            }
            //如果层数发生变化,则调用BuffActionDispatcher的OnLevelChange方法
            if (oldLayer != self.Layer)
                BuffActionDispatcher.Instance.OnLevelChange(self, oldLayer, self.Layer);
        }
        /// <summary>
        /// 尝试开始计时器
        /// </summary>
        /// <param name="self"></param>
        public static void TryStartCountDown(this BuffInfo self)
        {
            if (!self.TryCompleted())
            {
                var timerComponent = self.Root.GetComponent<TimerComponent>();
                self.TimerId = timerComponent.NewOnceTimer(TimeInfo.Instance.ClientNow() + (int)(self.Config.BuffInfoConfig.Interval * 1000f),
                        TimerInvokeType.BuffActionIntervalTimerInvoke, self);
                BuffActionDispatcher.Instance.Run(self);
                return;
            }
            //如果buff完成,则移除buff
            BuffFactory.RemoveBuff(self);
        }
        /// <summary>
        /// 尝试完成buff
        /// </summary>
        /// <param name="self"></param>
        /// <returns></returns>
        public static bool TryCompleted(this BuffInfo self)
        {
            int oldLayer = self.Layer;
            if (self.EndTime > TimeInfo.Instance.ClientNow())
                return false;
            //还有层数
            if (self.Config.BuffInfoConfig.CycleEndType == (int)CycleEndType.Clear)
                self.Layer = 0;
            else if (self.Config.BuffInfoConfig.CycleEndType == (int)CycleEndType.Sub)
            {
                self.Layer -= 1;
                self.EndTime = self.Config.BuffInfoConfig.BuffInfoTimer * 1000 + TimeInfo.Instance.ClientNow();
            }
            //销毁
            if (self.Layer > 0)
                return false;
            //如果层数发生变化,则调用BuffActionDispatcher的OnLevelChange方法
            if (oldLayer != self.Layer)
                BuffActionDispatcher.Instance.OnLevelChange(self, oldLayer, self.Layer);
            return true;
        }
    }
}
BuffInfosComponentSystem.cs
csharp
namespace MH
{
    public static class BuffInfosComponentSystem
    {
        /// <summary>
        /// 添加或更新buff
        /// </summary>
        /// <param name="self"></param>
        /// <param name="buff">buff</param>
        public static void AddOrUpdate(this BuffInfosComponent self, BuffInfo buff)
        {
            BuffInfo old = self.GetByConfigId(buff.ConfigId);
            if (old != null)
            {
                old.RepeatAddBuffInfo();
                buff?.Dispose();
                return;
            }
            self.BuffInfos.Add(buff);
        }
        /// <summary>
        /// 根据ID获取buff
        /// </summary>
        /// <param name="self"></param>
        /// <param name="buffInfoId">buffId</param>
        /// <returns></returns>
        public static BuffInfo GetById(this BuffInfosComponent self, long buffInfoId)
        {
            foreach (BuffInfo buffInfo in self.BuffInfos)
            {
                if (buffInfo.Id == buffInfoId)
                    return buffInfo;
            }
            return null;
        }
        /// <summary>
        /// 根据配置ID获取buff
        /// </summary>
        /// <param name="self"></param>
        /// <param name="configId">配置表Id</param>
        /// <returns></returns>
        public static BuffInfo GetByConfigId(this BuffInfosComponent self, int configId)
        {
            foreach (BuffInfo buffInfo in self.BuffInfos)
            {
                if (buffInfo.ConfigId == configId)
                    return buffInfo;
            }
            return null;
        }
        /// <summary>
        /// 移除buff
        /// </summary>
        /// <param name="self"></param>
        /// <param name="buffInfoId">buffId</param>
        public static void Remove(this BuffInfosComponent self, long buffInfoId)
        {
            BuffInfo buffInfo = self.GetById(buffInfoId);
            if (buffInfo != null)
            {
                self.BuffInfos.Remove(buffInfo);
                buffInfo?.Dispose();
            }
        }
    }
}
BuffFactory.cs
csharp
namespace MH
{
    public static class BuffFactory
    {
        /// <summary>
        /// 创建buff
        /// </summary>
        /// <param name="caster"></param>
        /// <param name="target"></param>
        /// <param name="configId"></param>
        public static void CreateBuff(Unit caster, Unit target, int configId)
        {
            var buffInfosComponent = target.GetComponent<BuffInfosComponent>();
            var info = buffInfosComponent.AddChild<BuffInfo, int, Unit, Unit>(configId, caster, target);
            buffInfosComponent.AddOrUpdate(info);
        }

        /// <summary>
        /// 移除buff
        /// </summary>
        /// <param name="buffInfo"></param>
        public static void RemoveBuff(BuffInfo buffInfo)
        {
            buffInfo.GetParent<BuffInfosComponent>().Remove(buffInfo.Id);
        }
        /// <summary>
        /// buff层数变化
        /// </summary>
        /// <param name="buffInfo"></param>
        /// <param name="oldLayer"></param>
        /// <param name="newLayer"></param>
        public static void OnBuffLayerValueChange(BuffInfo buffInfo, int oldLayer, int newLayer)
        {
            var attributeModifierComponent = buffInfo.Target.GetComponent<AttributeModifierComponent>();
            if (attributeModifierComponent == null)
                return;
            if (newLayer > oldLayer)
            {
                for (int i = 0; i < buffInfo.Config.BuffInfoConfig.NumericTypes.Count; i++)
                {
                    var key = buffInfo.Config.BuffInfoConfig.NumericTypes[i];
                    var value = buffInfo.Config.BuffInfoConfig.NumericValues[i];
                    attributeModifierComponent.AddModifier(key, buffInfo.Id, value, ModifierType.Add);
                }
            }
            else
            {
                for (int i = 0; i < buffInfo.Config.BuffInfoConfig.NumericTypes.Count; i++)
                {
                    var key = buffInfo.Config.BuffInfoConfig.NumericTypes[i];
                    var value = buffInfo.Config.BuffInfoConfig.NumericValues[i];
                    attributeModifierComponent.AddModifier(key, buffInfo.Id, -value, ModifierType.Add);
                }
            }
        }
        /// <summary>
        /// buff开始
        /// </summary>
        /// <param name="buffInfo"></param>
        /// <param name="modifierType"></param>
        public static void OnBuffStart(BuffInfo buffInfo, ModifierType modifierType)
        {
            var attributeModifierComponent = buffInfo.Target.GetComponent<AttributeModifierComponent>();
            if (attributeModifierComponent == null)
                return;
            for (int i = 0; i < buffInfo.Config.BuffInfoConfig.NumericTypes.Count; i++)
            {
                var key = buffInfo.Config.BuffInfoConfig.NumericTypes[i];
                var value = buffInfo.Config.BuffInfoConfig.NumericValues[i];
                attributeModifierComponent.AddModifier(key, buffInfo.Id, value, modifierType);
            }
        }
    }
}

Buff生命周期的事件分发组件

BuffActionDispatcher.cs

csharp
using System;
using System.Collections.Generic;

namespace MH
{
    public class BuffActionDispatcher : LogicSingleton<BuffActionDispatcher>, ISingletonAwake
    {
        private Dictionary<BuffInfoType, IBuffAction> buffActions = new Dictionary<BuffInfoType, IBuffAction>();

        public void Awake()
        {
            HashSet<Type> types = CodeTypes.Instance.GetTypes(typeof(BuffActionAttribute));
            foreach (Type type in types)
            {
                object[] attrs = type.GetCustomAttributes(typeof(BuffActionAttribute), false);
                foreach (object attr in attrs)
                {
                    BuffActionAttribute buffActionAttribute = (BuffActionAttribute)attr;
                    IBuffAction obj = (IBuffAction)Activator.CreateInstance(type);
                    if (this.buffActions.ContainsKey(buffActionAttribute.buffInfoType))
                        throw new Exception($"BuffActionDispatcher中已经存在BuffInfoType为{buffActionAttribute.buffInfoType}的BuffAction");
                    this.buffActions[buffActionAttribute.buffInfoType] = obj;
                }
            }
        }
        public void Run(BuffInfo buffInfo)
        {
            if (this.buffActions.TryGetValue((BuffInfoType)buffInfo.Config.Id, out IBuffAction buffAction))
            {
                buffAction.Run(buffInfo);
            }
        }
        public void OnStart(BuffInfo buffInfo)
        {
            if (this.buffActions.TryGetValue((BuffInfoType)buffInfo.Config.Id, out IBuffAction buffAction))
            {
                buffAction.OnStart(buffInfo);
            }
        }
        public void OnEnd(BuffInfo buffInfo)
        {
            if (this.buffActions.TryGetValue((BuffInfoType)buffInfo.Config.Id, out IBuffAction buffAction))
            {
                buffAction.OnEnd(buffInfo);
            }
        }
        public void OnLevelChange(BuffInfo buffInfo, int oldLayer, int newLayer)
        {
            if (this.buffActions.TryGetValue((BuffInfoType)buffInfo.Config.Id, out IBuffAction buffAction))
            {
                buffAction.OnLevelChange(buffInfo, oldLayer, newLayer);
            }
        }
    }
}

这个组件会在Awake的时候收集带有BuffAction标签的buff处理类缓存起来,方便调用

配置说明

Buff配置表总览

Buff配置信息

基础字段说明

以下是 Buff 配置的基础字段说明:

字段说明示例
IdBuff的唯一标识符1
NameBuff的名称生命流逝
DescBuff的详细描述每秒降低3%的最大生命值,持续3秒,最高叠加1层
BuffInfoConfig详细配置项见下方详细说明
IconBuff图标资源名UI_Skill_Icon_9

BuffInfoConfig 详细配置

BuffInfoConfig 包含了 Buff 的核心配置参数:

配置项说明可选值
数值id影响的属性ID1001=攻击力
1002=最大生命值
1003=防御力
buff添加类型Buff重复添加时的处理方式1=重置时间
2=叠加层数
3=叠加层数并重置时间
buff数值效果数值(除以10000后得到实际值)30000=3%
50000=5%
持续时间Buff持续时间(秒)3=3秒
5=5秒
buff执行类型Buff的执行方式
周期结束执行类型Buff结束时的处理方式
间隔周期性执行的时间间隔(秒)1=每秒执行一次
最大层级Buff最大可叠加层数

任何数值的改动都搭配着数值组件来做

配置示例

Id: 1
Name: 生命流逝
Desc: 每秒降低3%的最大生命值,持续3秒,最高叠加1层
BuffInfoConfig: {
    数值id: 1002,          // 影响生命值
    buff添加类型: 1,       // 重复添加时重置时间
    buff数值: 30000,       // 3%
    持续时间: 3,           // 3秒
    buff执行类型: 3,       // 周期执行
    周期结束执行类型: 1,   // 时间到直接移除
    间隔: 1,              // 每秒执行
    最大层级: 1           // 不可叠加
}
Icon: UI_Skill_Icon_9

效果说明:

  1. 该Buff每秒(间隔=1)会降低目标3%的最大生命值
  2. 效果持续3秒
  3. 不可叠加(最大层级=1)
  4. 时间结束后直接移除效果(周期结束执行类型=1)
  5. 如果重复添加,会刷新持续时间(buff添加类型=1)

使用示例

1. Buff具体的处理类实现

csharp
namespace MH
{
    /// <summary>
    /// 每秒降低3%的最大生命值,持续3秒,最高叠加1层
    /// </summary>
    [BuffAction(BuffInfoType.SlowHealth3)]
    public class BuffActionDispatcher_SlowHealth3 : IBuffAction
    {
        public void OnEnd(BuffInfo buffInfo)
        {
            buffInfo.Target.GetComponent<AttributeModifierComponent>()?.ClearSourceModifiers(buffInfo.Id);
        }

        public void OnLevelChange(BuffInfo buffInfo, int oldLayer, int newLayer)
        {
        }

        public void OnStart(BuffInfo buffInfo)
        {
        }

        public void Run(BuffInfo buffInfo)
        {
            var numericComponent = buffInfo.Target.GetComponent<NumericComponent>();
            //首先获取目标的数值组件拿到最大生命值
            var maxHp = numericComponent[NumericType.MaxHp];
            var value = buffInfo.Config.BuffInfoConfig.NumericValues[0];
            //这里再处理一下扣血就可以了
            EventSystem.Instance.PublishAsync(buffInfo.Root, new AttackUnitStartSpecifyDamage()
            {
                TargetUnit = buffInfo.Target,
                Damage = (long)(maxHp * value / 1000000f),
            }).Coroutine();
        }
    }
}
csharp
namespace MH
{
    [Event(SceneType.Main)]
    public class AttackUnitStartSpecifyDamage_EventView : AEvent<Scene, AttackUnitStartSpecifyDamage>
    {
        protected override async ETTask Run(Scene scene, AttackUnitStartSpecifyDamage args)
        {
            if (!args.TargetUnit.IsAlive())
                return;
            long hp = args.TargetUnit.GetComponent<NumericComponent>().GetAsInt(NumericType.Hp);
            hp -= args.Damage;
            if (hp <= 0)
            {
                args.TargetUnit.GetComponent<NumericComponent>()[NumericType.Hp] = 0;
                args.TargetUnit.SetAlive(false);
                return;
            }
            args.TargetUnit.GetComponent<NumericComponent>().Set(NumericType.Hp, hp);
            await ETTask.CompletedTask;
        }
    }
}

技术支持

获取帮助

  • 💬 加入QQ群讨论交流(ET框架群) : 474643097
  • ⭐ 在GitHub上关注项目获取最新更新

Released under the MIT License.