【unity实战】实现类似英雄联盟的buff系统(附项目源码)
文章目录
- 先来看看最终效果
- 前言
- 开始
- BUFF系统
- 加几个BUFF测试
- 1. 逐层消失,升级不重置剩余时间的BUFF
- 2. 一次性全部消失,升级重置剩余时间的BUFF
- 3. 永久BUFF,类似被动BUFF
- 4. 负面BUFF,根据当前BUFF等级计算每秒收到伤害值,当两个不同单位向同一个单位施加同一个buff时BUFF独立存在
- 5. 一级叠加两层,后面都叠加一层
- 最终效果
- 参考
- 源码
- 完结
先来看看最终效果
前言
当今大多数游戏都拥有一些形式的Buff系统,利用这种系统可以增强或削弱游戏角色的特定属性。在Unity中,我们可以使用脚本轻松地创建这样的Buff系统。
在本教程中,我们将探索如何实现一种基本的Buff系统,其中包括对游戏中的玩家或敌对角色施加各种不同类型的Buff。我们还将学习如何设置时间限制和叠加限制,以及如何实现Buff效果的应用和移除。
通过本教程,您将学习如何在Unity中创建一个完整的Buff系统,为您的游戏增加全新的深度和策略性。
开始
新增脚本PlayerController,添加玩家血量和攻击力变量,并实时显示
public class PlayerController : MonoBehaviour
{[Header("生命值")]public float HP;[Header("攻击力")]public float AD;public TextMeshProUGUI HPText;public TextMeshProUGUI ADText;private void Update(){HPText.text = $"生命值:{HP}";ADText.text = $"攻击力:{AD}";}
}
效果
绘制BUFF显示界面
状态栏
遮罩
最终效果
BUFF系统
定义BUFF类型枚举
public enum BuffType
{/// <summary>/// 正面buff/// </summary>Buff,/// <summary>/// 负面buff/// </summary>Debuff,/// <summary>/// 没有buff/// </summary>None,
}
BUFF冲突方式枚举类型
/// <summary>
/// 当两个不同单位向同一个单位施加同一个buff时的冲突处理
/// </summary>
public enum ConflictResolution
{/// <summary>/// 合并为一个buff,叠层(提高等级)/// </summary>combine,/// <summary>/// 独立存在/// </summary>separate,/// <summary>/// 覆盖,后者覆盖前者/// </summary>cover,
}
新建BuffBase,Buff系统中的基类
public class BuffBase
{private GameObject m_Owner;private string m_Provider = "";private float m_MaxDuration = 3;private float m_TimeScale = 1;private int m_MaxLevel = 1;private BuffType m_BuffType = BuffType.None;private ConflictResolution m_ConflictResolution = ConflictResolution.cover;private bool m_Dispellable = true;private string m_Name = "默认名称";private string m_Description = "这个Buff没有介绍";private int m_Demotion = 1;private string m_IconPath = "";private int m_CurrentLevel = 0;private float m_ResidualDuration = 3;private bool m_Initialized = false;/// <summary>/// 此buff的持有者/// </summary>public GameObject Owner{get { return m_Owner; }protected set { m_Owner = value; }}/// <summary>/// 此Buff的提供者/// </summary>public string Provider{get { return m_Provider; }protected set { m_Provider = value; }}/// <summary>/// Buff的初始持续时间/// </summary>public float MaxDuration{get { return m_MaxDuration; }protected set { m_MaxDuration = Math.Clamp(value, 0, float.MaxValue); }}/// <summary>/// buff的时间流失速度,最小为0,最大为10。/// </summary>public float TimeScale{get { return m_TimeScale; }set { m_TimeScale = Math.Clamp(value, 0, 10); }}/// <summary>/// buff的最大堆叠层数,最小为1,最大为2147483647/// </summary>public int MaxLevel{get { return m_MaxLevel; }protected set { m_MaxLevel = Math.Clamp(value, 1, int.MaxValue); }}/// <summary>/// Buff的类型,分为正面、负面、中立三种/// </summary>public BuffType BuffType{get { return m_BuffType; }protected set { m_BuffType = value; }}/// <summary>/// 当两个不同单位向同一个单位施加同一个buff时的冲突处理/// 分为三种:/// combine,合并为一个buff,叠层(提高等级)/// separate,独立存在/// cover, 覆盖,后者覆盖前者/// </summary>public ConflictResolution ConflictResolution{get { return m_ConflictResolution; }protected set { m_ConflictResolution = value; }}/// <summary>/// 可否被驱散/// </summary>public bool Dispellable{get { return m_Dispellable; }protected set { m_Dispellable = value; }}/// <summary>/// Buff对外显示的名称/// </summary>public string Name{get { return m_Name; }protected set { m_Name = value; }}/// <summary>/// Buff的介绍文本/// </summary>public string Description{get { return m_Description; }protected set { m_Description = value; }}/// <summary>/// 图标资源的路径/// </summary>public string IconPath{get { return m_IconPath; }protected set { m_IconPath = value; }}/// <summary>/// 每次Buff持续时间结束时降低的等级,一般降低1级或者降低为0级。/// </summary>public int Demotion{get { return m_Demotion; }protected set { m_Demotion = Math.Clamp(value, 0, MaxLevel); }}/// <summary>/// Buff的当前等级/// </summary>public int CurrentLevel{get { return m_CurrentLevel; }set{//计算出改变值int change = Math.Clamp(value, 0, MaxLevel) - m_CurrentLevel;OnLevelChange(change);m_CurrentLevel += change;}}/// <summary>/// Buff的当前剩余时间/// </summary>public float ResidualDuration{get { return m_ResidualDuration; }set { m_ResidualDuration = Math.Clamp(value, 0, float.MaxValue); }}/// <summary>/// 当Owner获得此buff时触发/// 由BuffManager在合适的时候调用/// </summary>public virtual void OnGet() { }/// <summary>/// 当Owner失去此buff时触发/// 由BuffManager在合适的时候调用/// </summary>public virtual void OnLost() { }/// <summary>/// Update,由BuffManager每物理帧调用/// </summary>public virtual void FixedUpdate() { }/// <summary>/// 当等级改变时调用/// </summary>/// <param name="change">改变了多少级</param>protected virtual void OnLevelChange(int change) { }/// <summary>/// 初始化/// </summary>/// <param name="owner"></param>/// <param name="provider"></param>/// <exception cref="Exception"></exception>public virtual void Initialize(GameObject owner, string provider){if (m_Initialized){throw new Exception("不能对已经初始化的buff再次初始化");}if (owner == null || provider == null){throw new Exception("初始化值不能为空");}Owner = owner;Provider = provider;m_Initialized = true;}
}
新建ShowBuff,控制BUFF的显示
public class ShowBuff : MonoBehaviour
{[SerializeField, Header("Buff项预制体")]private GameObject m_BuffItemTemplate;[SerializeField, Header("对象池")]private GameObject m_Pool;[SerializeField, Header("Buff项父物体")]private GameObject m_Buffs;[SerializeField, Header("与Buff相关联的游戏对象")]private PlayerController m_Hero;private ObjectPool<UI_BuffItem> m_BuffItemPool;// Buff项对象池// Buff项对象池的创建函数,用于实例化Buff项private UI_BuffItem Pool_CreateFunc(){return Instantiate(m_BuffItemTemplate, this.transform).GetComponent<UI_BuffItem>();}// Buff项对象池的获取时回调,用于激活对象并设置父物体private void Pool_ActionOnGet(UI_BuffItem UI_BuffItem){UI_BuffItem.gameObject.SetActive(true);UI_BuffItem.transform.SetParent(m_Buffs.transform);}// Buff项对象池的回收时回调,用于隐藏对象并设置父物体private void Pool_ActionOnRelease(UI_BuffItem UI_BuffItem){UI_BuffItem.gameObject.SetActive(false);UI_BuffItem.transform.SetParent(m_Pool.transform);}// Buff项对象池的销毁时回调,用于销毁对象private void Pool_ActionOnDestroy(UI_BuffItem UI_BuffItem){Destroy(UI_BuffItem.gameObject);}// Buff监听器,当有新的Buff时调用ShowBuffCore方法private void BuffListener(BuffBase newBuff){ShowBuffCore(newBuff);}private void ShowBuffCore(BuffBase buff){m_BuffItemPool.Get().Initialize(buff, m_BuffItemPool);}private void Awake(){m_BuffItemPool = new ObjectPool<UI_BuffItem>(Pool_CreateFunc,Pool_ActionOnGet,Pool_ActionOnRelease,Pool_ActionOnDestroy,true,100,10000);// 遍历BuffManager中与m_Hero关联的所有Buff,并调用ShowBuffCore方法显示它们foreach (BuffBase item in BuffManager.Instance.StartObserving(m_Hero.gameObject, BuffListener)){ShowBuffCore(item);}}}
挂载脚本并配置参数
新增UI_BuffItem,控制Buff信息UI显示
public class UI_BuffItem : MonoBehaviour
{[SerializeField, Header("遮罩层")]private Image m_Mask_M;[SerializeField, Header("等级文本")]private TextMeshProUGUI m_Level;[SerializeField, Header("边框")]private Image m_Frame;[SerializeField, Header("图标")]private Image m_Icon;[Space][Header("Buff详情")][SerializeField, Header("详情弹窗")]private GameObject m_BuffInfo;[SerializeField, Header("Buff名称文本")]private TextMeshProUGUI m_BuffName;[SerializeField, Header("Buff描述文本")]private TextMeshProUGUI m_Description;[SerializeField, Header("Buff来源文本")]private TextMeshProUGUI m_Provider;private ObjectPool<UI_BuffItem> m_RecyclePool;private bool m_Initialized = false;// 是否已经初始化private bool m_NeedNumber = false;// 是否需要显示等级private bool m_NeedLine = false;// 是否需要显示计时工具private BuffBase m_TargetBuff;public void OnPointerEnter(){m_BuffInfo.gameObject.SetActive(true);ShowInfo(m_TargetBuff);}// 显示Buff详细信息public void ShowInfo(BuffBase buff){m_BuffName.text = buff.Name;m_Description.text = buff.Description;m_Provider.text = "来自:" + buff.Provider;}public void OnPointerExit(){m_BuffInfo.gameObject.SetActive(false);}public void Initialize(BuffBase buff, ObjectPool<UI_BuffItem> recyclePool){m_Icon.sprite = Resources.Load<Sprite>(buff.IconPath);m_TargetBuff = buff;m_RecyclePool = recyclePool;if (m_TargetBuff.MaxLevel > 1){m_NeedNumber = true;m_Level.gameObject.SetActive(true);}else{m_NeedNumber = false;m_Level.gameObject.SetActive(false);}if (m_TargetBuff.TimeScale > 0){m_NeedLine = true;m_Mask_M.gameObject.SetActive(true);}else{m_NeedLine = false;m_Mask_M.gameObject.SetActive(false);}switch (buff.BuffType){case BuffType.Buff:m_Frame.color = Color.green;break;case BuffType.Debuff:m_Frame.color = Color.red;break;case BuffType.None:m_Frame.color = Color.white;break;default:break;}m_Initialized = true;}private void Update(){if (m_Initialized){//需要显示计时工具才显示if (m_NeedLine){m_Mask_M.fillAmount = 1 - (m_TargetBuff.ResidualDuration / m_TargetBuff.MaxDuration);}//需要显示等级才显示if (m_NeedNumber){m_Level.text = m_TargetBuff.CurrentLevel.ToString();}//如果当前等级等于零说明他已经被废弃了,所以就可以回收了if (m_TargetBuff.CurrentLevel == 0 ){m_RecyclePool.Release(this);}}}
}
绑定脚本,配置参数并添加鼠标移入移出事件
新增ShowBuff,控制BUFFBuff的显示
public class ShowBuff : MonoBehaviour
{[SerializeField, Header("Buff项预制体")]private GameObject m_BuffItemTemplate;[SerializeField, Header("对象池")]private GameObject m_Pool;[SerializeField, Header("Buff项父物体")]private GameObject m_Buffs;[SerializeField, Header("与Buff相关联的游戏对象")]private PlayerController m_Hero;private ObjectPool<UI_BuffItem> m_BuffItemPool;// Buff项对象池// Buff项对象池的创建函数,用于实例化Buff项private UI_BuffItem Pool_CreateFunc(){return Instantiate(m_BuffItemTemplate, this.transform).GetComponent<UI_BuffItem>();}// Buff项对象池的获取时回调,用于激活对象并设置父物体private void Pool_ActionOnGet(UI_BuffItem UI_BuffItem){UI_BuffItem.gameObject.SetActive(true);UI_BuffItem.transform.SetParent(m_Buffs.transform);}// Buff项对象池的回收时回调,用于隐藏对象并设置父物体private void Pool_ActionOnRelease(UI_BuffItem UI_BuffItem){UI_BuffItem.gameObject.SetActive(false);UI_BuffItem.transform.SetParent(m_Pool.transform);}// Buff项对象池的销毁时回调,用于销毁对象private void Pool_ActionOnDestroy(UI_BuffItem UI_BuffItem){Destroy(UI_BuffItem.gameObject);}// Buff监听器,当有新的Buff时调用ShowBuffCore方法private void BuffListener(BuffBase newBuff){ShowBuffCore(newBuff);}private void ShowBuffCore(BuffBase buff){m_BuffItemPool.Get().Initialize(buff, m_BuffItemPool);}private void Awake(){m_BuffItemPool = new ObjectPool<UI_BuffItem>(Pool_CreateFunc,Pool_ActionOnGet,Pool_ActionOnRelease,Pool_ActionOnDestroy,true,100,10000);// 遍历BuffManager中与m_Hero关联的所有Buff,并调用ShowBuffCore方法显示它们foreach (BuffBase item in BuffManager.Instance.StartObserving(m_Hero.gameObject, BuffListener)){ShowBuffCore(item);}}
}
挂载脚本,配置参数
新增BuffManager,BUFF管理类
public class BuffManager : MonoBehaviour
{/// <summary>/// 固定时间更新的更新频率,此值不宜过高,可以过低(会增加性能消耗)。/// </summary>public const float FixedDeltaTime = 0.1f;#region 单例private static BuffManager m_Instance;public static BuffManager Instance{get{if (m_Instance == null){GameObject l_GameObject = new GameObject("Buff Manager");m_Instance = l_GameObject.AddComponent<BuffManager>();DontDestroyOnLoad(l_GameObject);}return m_Instance;}}#endregion/// <summary>/// 存储了所有的buff,key为buff持有者,value为他所持有的所有buff。/// </summary>private Dictionary<GameObject, List<BuffBase>> m_BuffDictionary = new Dictionary<GameObject, List<BuffBase>>(25);private Dictionary<GameObject, Action<BuffBase>> m_ObserverDicitinary = new Dictionary<GameObject, Action<BuffBase>>(25);#region Public方法/// <summary>/// 返回要观察的对象现有的buff,并且在对象被添加新buff时通知你/// (如果现在对象身上没有buff会返回空列表,不会返回null)/// </summary>/// <returns></returns>public List<BuffBase> StartObserving(GameObject target, Action<BuffBase> listener){List<BuffBase> list;//添加监听if (!m_ObserverDicitinary.ContainsKey(target)){m_ObserverDicitinary.Add(target, null);}m_ObserverDicitinary[target] += listener;//查找已有buffif (m_BuffDictionary.ContainsKey(target)){list = m_BuffDictionary[target];}else{list = new List<BuffBase>();}//返回return list;}/// <summary>/// 停止观察某一对象,请传入与调用开始观察方法时使用的相同参数。/// </summary>/// <param name="target"></param>/// <param name="listener"></param>/// <exception cref="Exception"></exception>public void StopObsveving(GameObject target, Action<BuffBase> listener){if (!m_ObserverDicitinary.ContainsKey(target)){throw new Exception("要停止观察的对象不存在");}m_ObserverDicitinary[target] -= listener;if (m_ObserverDicitinary[target] == null){m_ObserverDicitinary.Remove(target);}}/// <summary>/// 在目标身上挂buff/// </summary>/// <typeparam name="T"></typeparam>/// <param name="target"></param>/// <param name="provider"></param>/// <param name="level"></param>public void AddBuff<T>(GameObject target, string provider, int level = 1) where T : BuffBase, new(){//如果我们的字典里没有存储这个key,就进行初始化if (!m_BuffDictionary.ContainsKey(target)){m_BuffDictionary.Add(target, new List<BuffBase>(5));//目标身上自然没有任何buff,直接挂一个新buff即可AddNewBuff<T>(target, provider, level);return;}//如果目标身上没有任何buff,直接挂一个新buff即可if (m_BuffDictionary[target].Count == 0){AddNewBuff<T>(target, provider, level);return;}//遍历看看目标身上有没有已存在的要挂的buff。List<T> temp01 = new List<T>();foreach (BuffBase item in m_BuffDictionary[target]){if (item is T){temp01.Add(item as T);}}//如果没有直接挂一个新buff就行了//如果有已存在的要挂的buff,就要进行冲突处理了if (temp01.Count == 0){AddNewBuff<T>(target, provider, level);}else{switch (temp01[0].ConflictResolution){//如果是独立存在,那也直接挂buffcase ConflictResolution.separate:bool temp = true;foreach (T item in temp01){if (item.Provider == provider){item.CurrentLevel += level;temp = false;continue;}}if (temp){AddNewBuff<T>(target, provider, level);}break;//如果是合并,则跟已有的buff叠层。case ConflictResolution.combine:temp01[0].CurrentLevel += level;break;//如果是覆盖,则移除旧buff,然后添加这个buff。case ConflictResolution.cover:RemoveBuff(target, temp01[0]);AddNewBuff<T>(target, provider, level);break;}}}/// <summary>/// 获得单位身上指定类型的buff的列表/// </summary>/// <typeparam name="T"></typeparam>/// <param name="Owner"></param>/// <returns></returns>public List<T> FindBuff<T>(GameObject Owner) where T : BuffBase, new(){List<T> result = new List<T>();if (m_BuffDictionary.ContainsKey(Owner)){List<BuffBase> buff = m_BuffDictionary[Owner];foreach (BuffBase item in buff){if (item is T){result.Add(item as T);}}}return result;}/// <summary>/// 获得单位身上所有的buff/// 如果单位身上没有任何buff则返回空列表/// </summary>/// <param name="Owner"></param>/// <returns></returns>public List<BuffBase> FindAllBuff(GameObject Owner){List<BuffBase> result = new List<BuffBase>();if (m_BuffDictionary.ContainsKey(Owner)){result = m_BuffDictionary[Owner];}return result;}/// <summary>/// 移除单位身上指定的一个buff/// </summary>/// <param name="owner"></param>/// <param name="buff"></param>/// <returns>是否成功,如果失败说明目标不存在</returns>public bool RemoveBuff(GameObject owner, BuffBase buff){if (!m_BuffDictionary.ContainsKey(owner)){return false;}bool haveTarget = false;foreach (BuffBase item in m_BuffDictionary[owner]){if (item == buff){haveTarget = true;item.CurrentLevel -= item.CurrentLevel;item.OnLost();m_BuffDictionary[owner].Remove(item);break;}}if (!haveTarget){return false;}return true;}#endregion#region Private方法private void AddNewBuff<T>(GameObject target, string provider, int level) where T : BuffBase, new(){T buff = new T();buff.Initialize(target, provider);m_BuffDictionary[target].Add(buff);buff.ResidualDuration = buff.MaxDuration;buff.CurrentLevel = level;buff.OnGet();if (m_ObserverDicitinary.ContainsKey(target)){m_ObserverDicitinary[target]?.Invoke(buff);}}#endregionprivate WaitForSeconds m_WaitForFixedDeltaTimeSeconds = new WaitForSeconds(FixedDeltaTime);private IEnumerator ExecuteFixedUpdate(){while (true){yield return m_WaitForFixedDeltaTimeSeconds;//执行所有buff的update;foreach (KeyValuePair<GameObject, List<BuffBase>> item1 in m_BuffDictionary){foreach (BuffBase item2 in item1.Value){if (item2.CurrentLevel > 0 && item2.Owner != null){item2.FixedUpdate();}}}}}private WaitForSeconds m_WaitFor10Seconds = new WaitForSeconds(10f);private Dictionary<GameObject, List<BuffBase>> m_BuffDictionaryCopy = new Dictionary<GameObject, List<BuffBase>>(25);private IEnumerator ExecuteGrabageCollection(){while (true){yield return m_WaitFor10Seconds;//复制一份m_BuffDictionaryCopy.Clear();foreach (KeyValuePair<GameObject, List<BuffBase>> item in m_BuffDictionary){m_BuffDictionaryCopy.Add(item.Key, item.Value);}//清理无用对象foreach (KeyValuePair<GameObject, List<BuffBase>> item in m_BuffDictionaryCopy){//如果owner被删除,我们这边也跟着删除if (item.Key == null){m_BuffDictionary.Remove(item.Key);continue;}//如果一个owner身上没有任何buff,就没必要留着他了if (item.Value.Count == 0){m_BuffDictionary.Remove(item.Key);continue;}}}}private void Awake(){StartCoroutine(ExecuteFixedUpdate());StartCoroutine(ExecuteGrabageCollection());}private BuffBase m_Transfer_Buff;private void FixedUpdate(){//清理无用对象foreach (KeyValuePair<GameObject, List<BuffBase>> item in m_BuffDictionary){//清理无用buff//降低持续时间for (int i = item.Value.Count - 1; i >= 0; i--){m_Transfer_Buff = item.Value[i];//如果等级为0,则移除if (m_Transfer_Buff.CurrentLevel == 0){RemoveBuff(item.Key, m_Transfer_Buff);continue;}//如果持续时间为0,则降级,//降级后如果等级为0则移除,否则刷新持续时间if (m_Transfer_Buff.ResidualDuration == 0){m_Transfer_Buff.CurrentLevel -= m_Transfer_Buff.Demotion;if (m_Transfer_Buff.CurrentLevel == 0){RemoveBuff(item.Key, m_Transfer_Buff);continue;}else{m_Transfer_Buff.ResidualDuration = m_Transfer_Buff.MaxDuration;}}//降低持续时间m_Transfer_Buff.ResidualDuration -= Time.fixedDeltaTime;}}}
}
加几个BUFF测试
1. 逐层消失,升级不重置剩余时间的BUFF
public class Buff001 : BuffBase
{// Buff每秒钟恢复的生命值private float m_HealingPerSecond = 20f;// 作用目标,即被添加Buff的角色private PlayerController playerController;// 初始化Buff的属性和状态public override void Initialize(GameObject owner, string provider){base.Initialize(owner, provider);// 获取作用目标的PlayerController组件playerController = owner.GetComponent<PlayerController>();// 设置Buff的基本属性MaxDuration = 15; // 最大持续时间为15秒TimeScale = 1f; // 时间流失速度为正常值MaxLevel = 5; // 最大等级为5级BuffType = BuffType.Buff; // Buff类型为增益效果ConflictResolution = ConflictResolution.combine; // Buff冲突时采用合并方式Dispellable = false; // 不可被驱散Name = "生命值"; // Buff的名称Description = $"每秒恢复{m_HealingPerSecond}点生命值"; // Buff的描述Demotion = 1; // 每次Buff持续时间结束时降低的等级IconPath = "Icon/2003"; // Buff的图标路径}// 在固定时间间隔内更新Buff的效果public override void FixedUpdate(){// 每秒钟恢复指定的生命值playerController.HP += m_HealingPerSecond * BuffManager.FixedDeltaTime;}
}
调用测试
public class Test : MonoBehaviour
{public PlayerController playerController;void Update(){if (Input.GetKeyDown(KeyCode.Alpha1)){//作用目标 来源:自己,每次加1层BuffManager.Instance.AddBuff<Buff001>(playerController.gameObject, "自己", 1);}}
}
效果
2. 一次性全部消失,升级重置剩余时间的BUFF
public class Buff002 : BuffBase
{// 攻击力增加的数值private float m_ADUp = 10f;private PlayerController playerController;public override void Initialize(GameObject owner, string provider){base.Initialize(owner, provider);MaxDuration = 5f;// 最大持续时间为5秒MaxLevel = 10;// 最大等级为10级BuffType = BuffType.Buff;// Buff类型为增益效果ConflictResolution = ConflictResolution.combine;// Buff冲突时采用合并方式Dispellable = false;// 不可被驱散Name = "借来的短剑";// Buff的名称Description = "每层增加10点攻击力";// Buff的描述IconPath = "Icon/1036";// Buff的图标路径Demotion = MaxLevel;// 每次Buff持续时间结束时降低的等级playerController = Owner.GetComponent<PlayerController>();}//当等级改变时调用protected override void OnLevelChange(int change){// 根据变化的等级调整角色的攻击力playerController.AD += m_ADUp * change;//每次升级,重置Buff的当前剩余时间ResidualDuration = MaxDuration;}
}
调用
BuffManager.Instance.AddBuff<Buff002>(playerController.gameObject, "自己", 1);
效果
3. 永久BUFF,类似被动BUFF
public class Buff003 : BuffBase
{PlayerController playerController;public override void Initialize(GameObject owner, string provider){base.Initialize(owner, provider);TimeScale = 0f;// 时间缩放为0,暂停游戏中的时间流逝MaxLevel = int.MaxValue;// 最大等级设置为int的最大值,表示无限等级BuffType = BuffType.Buff;// Buff类型为增益效果ConflictResolution = ConflictResolution.separate;// Buff冲突时采用分离方式Dispellable = false;// 不可被驱散Name = "盛宴";Description = "增加生命值";IconPath = "Icon/Feast";Demotion = 0;// 每次Buff持续时间结束时降低的等级playerController = owner.GetComponent<PlayerController>();}// 当Buff等级发生变化时触发protected override void OnLevelChange(int change){// 根据变化的等级调整角色的生命值playerController.HP += change;}
}
调用
BuffManager.Instance.AddBuff<Buff003>(playerController.gameObject, "自己", 80);
效果
4. 负面BUFF,根据当前BUFF等级计算每秒收到伤害值,当两个不同单位向同一个单位施加同一个buff时BUFF独立存在
public class Buff004 : BuffBase
{PlayerController playerController;// 每秒受到的伤害值float m_DamagePerSeconds = 30;public override void Initialize(GameObject owner, string provider){base.Initialize(owner, provider);playerController = owner.GetComponent<PlayerController>();MaxDuration = 5f;// Buff的最大持续时间为5秒TimeScale = 1f;// 时间缩放为1,正常流逝时间MaxLevel = 5;// 最大等级设置为5BuffType = BuffType.Debuff;// Buff类型为减益效果ConflictResolution = ConflictResolution.separate;// Buff冲突时采用分离方式Dispellable = true;// 可以被驱散Name = "流血";Description = "每层每秒受到30点伤害";IconPath = "Icon/Darius_PassiveBuff";Demotion = MaxLevel;// 每次Buff持续时间结束时降低的等级}// 当Buff等级发生变化时触发protected override void OnLevelChange(int change){//每次升级,重置Buff的当前剩余时间ResidualDuration = MaxDuration;}public override void FixedUpdate(){// 根据当前等级、每秒伤害值和固定时间步长来计算角色受到的伤害playerController.HP -= m_DamagePerSeconds * CurrentLevel * BuffManager.FixedDeltaTime;}
}
调用
if (Input.GetKeyDown(KeyCode.Alpha4))
{BuffManager.Instance.AddBuff<Buff004>(playerController.gameObject, "敌人1", 1);
}
if (Input.GetKeyDown(KeyCode.Alpha5))
{BuffManager.Instance.AddBuff<Buff004>(playerController.gameObject, "敌人2", 1);
}
效果
5. 一级叠加两层,后面都叠加一层
public class Buff005 : BuffBase
{PlayerController playerController;// 每秒受到的伤害值float m_DamagePerSeconds = 10;public override void Initialize(GameObject owner, string provider){base.Initialize(owner, provider);playerController = owner.GetComponent<PlayerController>();MaxDuration = 1f;// Buff的最大持续时间为1秒TimeScale = 1f;// 时间缩放为1,正常流逝时间MaxLevel = int.MaxValue;// 最大等级设置为int.MaxValue,即无限大BuffType = BuffType.Debuff;// Buff类型为减益效果ConflictResolution = ConflictResolution.combine;// Buff冲突时采用合并方式Dispellable = true;// 可以被驱散Name = "被点燃";Description = "每秒受到10点伤害,首次受到该BUFF伤害,一次叠加2层,后续叠加1层";IconPath = "Icon/Darius_PassiveBuff";Demotion = 1;// 每次Buff持续时间结束时降低的等级}public override void FixedUpdate(){// 根据每秒伤害值和固定时间步长来计算角色受到的伤害playerController.HP -= m_DamagePerSeconds * BuffManager.FixedDeltaTime;}
}
调用
if (Input.GetKeyDown(KeyCode.Alpha6))
{int number = 1;//获取叠加的BUff层数if(BuffManager.Instance.FindBuff<Buff005>(playerController.gameObject).Count == 0 ){number = 2;}BuffManager.Instance.AddBuff<Buff005>(playerController.gameObject, "敌人1", number);
}
效果
最终效果
参考
【视频】https://www.bilibili.com/video/BV1Xy4y1N7Cb
源码
https://gitcode.net/unity1/buffsystem
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
相关文章:

【unity实战】实现类似英雄联盟的buff系统(附项目源码)
文章目录 先来看看最终效果前言开始BUFF系统加几个BUFF测试1. 逐层消失,升级不重置剩余时间的BUFF2. 一次性全部消失,升级重置剩余时间的BUFF3. 永久BUFF,类似被动BUFF4. 负面BUFF,根据当前BUFF等级计算每秒收到伤害值,…...

Draft-P802.11be-D3.2协议学习__$9-Frame-Format__$9.3.1.22-Trigger-frame-format
Draft-P802.11be-D3.2协议学习__$9-Frame-Format__$9.3.1.22-Trigger-frame-format 9.3.1.22.1 Genreal9.3.1.22.2 Common Info field9.3.1.22.3 Special User Info field9.3.1.22.4 HE variant User Info field9.3.1.22.5 EHT variant User Info field9.3.1.22.6 Basic Trigge…...

vSLAM中IMU预积分的作用--以惯性导航的角度分析
作为一个学过一点惯导的工程师,在初次接触视觉slam方向时,最感兴趣的就是IMU预积分了。但为什么要用这个预积分,在看了很多材料和书后,还是感觉模模糊糊,云里雾里。 在接触了vSLAM的更多内容后,站在历史研究…...
c++ libevent demo
Server::Server(const char *ip, int port) {//创建事件集合base event_base_new();struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family AF_INET;server_addr.sin_port htons(port);server_addr.sin_addr.s_addr in…...

51单片机锅炉监控系统仿真设计( proteus仿真+程序+原理图+报告+讲解视频)
51单片机锅炉监控系统仿真设计( proteus仿真程序原理图报告讲解视频) 1.主要功能:讲解视频2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接资料下载链接(可点击): 51单片机锅炉监控系统仿…...

zip文件解压缩命令全
zip文件解压缩命令全 入门Zip 用法选项示例语法形式和选项基本语法压缩目录将文件添加到现有压缩文件解压缩文件将 zip 文件解压缩到指定目录列出 zip 文件中的内容将 zip 文件加密将 zip 文件解密将 zip 文件中的文件转成 UTF-8 编码Zip 压缩示例创建新的 zip 压缩文件将文件添…...

章鱼网络进展月报 | 2023.10.1-10.31
章鱼网络大事摘要 1、Louis 成功竞选 NDC 的 HoM 议席,将会尽最大努力推动 NEAR 变革。2、章鱼网络受邀参加在土耳其主办的 Cosmoverse2023,分享 Adaptive IBC 的技术架构。3、2023年10月8日章鱼日,是章鱼网络主网上线2周年的纪念日。 …...

数据结构 | 单链表专题【详解】
数据结构 | 单链表专题【详解】 文章目录 数据结构 | 单链表专题【详解】链表的概念及结构单链表的实现头文件打印尾插头插尾删头删查找在指定位置之前插入数据在指定位置之后插入数据删除pos节点删除pos之后的节点销毁链表 顺序表遗留下来的问题 中间/头部的插⼊删除ÿ…...

前端基础之BOM和DOM
目录 一、前戏 window对象 window的子对象 navigator对象(了解即可) screen对象(了解即可) history对象(了解即可) location对象 弹出框 计时相关 二、DOM HTML DOM 树 查找标签 直接查找 间…...

第23期 | GPTSecurity周报
GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区,集成了生成预训练 Transformer(GPT)、人工智能生成内容(AIGC)以及大型语言模型(LLM)等安全领域应用的知识。在这里,您可以…...

VSCode实用远程主机功能
作为嵌入式开发者,经常在各种系统平台或者开发工具之间切换,比如你的代码在Linux虚拟机上,如果不习惯在Linux下用IDE,那么我尝试将Linux的目录通过samba共享出来,在windows下用网络映射盘的方式映射出来,VS…...

并发编程: 2. 线程管控
给定一个线程,只要令std::thread对象与之关联,就能管控该线程的几乎每个细节。 2.1 线程的基本管控 2.1.1 发起线程 线程通过构建std::thread对象而启动,该对象指明线程要运行的任务(函数)。简单的任务,…...
使用 Python、XML 和 YAML 编写 ROS 2 Launch 文件
系列文章目录 ROS2 重要概念 ament_cmake_python 用户文档 ROS2 ament_cmake 用户文档 使用 rosdep 管理 ROS 2 依赖项 文章目录 系列文章目录前言一、Launch 文件示例1.1 Python 版本1.2 XML 版本1.3 YAML 版本 二、从命令行使用 Launch 文件1. Launching2. 设置参数3. 控制海…...

SpringCloud 微服务全栈体系(十)
第十章 RabbitMQ 一、初识 MQ 1. 同步和异步通讯 微服务间通讯有同步和异步两种方式: 同步通讯:就像打电话,需要实时响应。 异步通讯:就像发邮件,不需要马上回复。 两种方式各有优劣,打电话可以立即得…...

[原创]Cadence17.4,win64系统,构建CIS库
目录 1、背景介绍 2、具体操作流程 3、遇到问题、分析鉴别问题、解决问题 4、借鉴链接并评论 1、背景介绍 CIS库,绘制原理图很方便,但是需要在Cadence软件与数据库之间建立联系,但是一直不成功,花费半天时间才搞明白如何建立关系并…...

Python 海龟绘图基础教学教案(一)
Python 海龟绘图——第 1 题 题目:绘制下面的图形 解析: 考察 turtle 基本命令,绘制直线,使用 forward,可缩写为 fd。 答案: import turtle as t t.fd(100) # 或者使用 t.forward(100) t.done() Python 海…...

JUC并发编程系列(一):Java线程
前言 JUC并发编程是Java程序猿必备的知识技能,只有深入理解并发过程中的一些原则、概念以及相应源码原理才能更好的理解软件开发的流程。在这篇文章中荔枝会梳理并发编程的基础,整理有关Java线程以及线程死锁的知识,希望能够帮助到有需要的小…...
双向链表相关代码
DLinkList.h // // DLinkList.hpp // FirstP // // Created by 赫赫 on 2023/10/31. // 双向链表相关代码:双向链表、循环双向链表#ifndef DLinkList_hpp #define DLinkList_hpp #include <stdio.h> #include <stdlib.h> #include <iostream>…...

[每周一更]-(第70期):常用的GIT操作命令
1、增删文件 # 添加当前目录的所有文件到暂存区 $ git add .# 添加指定文件到暂存区 $ git add <file1> <file2> ...# 添加指定目录到暂存区,包括其子目录 $ git add <dir># 删除工作区文件,并且将这次删除放入暂存区 $ git rm [file…...

Leetcode-283 移动零
count记录0的个数,不为0的数取代0位置,最后把剩余位置置零 class Solution {public void moveZeroes(int[] nums) {int count 0;for(int i0;i<nums.length;i){if(nums[i]0){count;}else{nums[i-count]nums[i];}}for(int inums.length-count;i<nu…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...

Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...