现在基本做游戏都会需要些buff,之前我也在网上找也看到很多别人写的buff系统,并不是很符合我的心理预期,大部分在网上看到的都是面向过程的而不是面向对象的独立开来的buff,这样每改动一个小地方或者写一个buff基本上就要改动整个框架的部分,这对合作开发来说是比较致命的,就一个人来做的话还问题不大,但是人一单多了一人写几个buff最后就全乱了,今天把我之前写的这套框架发出来,经过验证了,原来的一个项目大概有3000多个buff,能够比较完美的支持下来。
这个buff系统架构我放到CSDN资源中,可以进入BGBuffSystem下的SampleScene查看demo。
架构这里我就简单说一下整体的逻辑,大家可以下载使用看核心。
这里我选择的面向对象也是面向组件的方式,这意味着我每一个buff都会创建两个类分别是xxxBuffCtrl操作类、xxxBuffEntity数据实体类,当buff挂载时会将对应的xxxBuffCtrl挂载到对应的对象上,而xxxBuffCtrl是负责控制buff的具体效果并且持有xxxBuffEntity数据实体,他们分别也是继承自BuffCtrlBase、BuffEntityBase。这样上层管理的时候只需要管理BuffEntityBase的初始化数据和BuffCtrlBase对应的上层管理即可。数据用的是json。
Buff层数、buff的移除方式(全部移除还是单独移除),buff多挂载,buff执行次数,buff的类型(叠加,永久,重复添加刷新时间,刷新层数)等我在上层提供了集中常用的类型,如果要想添加可以在BuffCtrlBase中写具体逻辑,在BuffEntityBase的BuffOverlap枚举中添加新类型。
这里我也提供了很多的接口具体的可以在BuffCtrlBase中看。
using System.Collections;
using UnityEngine;
using System;
public class BuffCtrlBase : MonoBehaviour, IDisposable
{
#region 基础属性
///
/// Buff信息
///
public BuffEntityBase m_buffInfo;
///
/// 人物控制器
///
public RoleAttribute m_Self;
///
/// 敌人控制器
///
public RoleAttribute m_Other;
///
/// 是否触发
///
public bool m_IsEnable = false;
#endregion
#region 辅助计算使用
///
/// buff Ctrl层的限制时长 根据Entity的m_LimitTime获取
///
public float m_CtrlLimitTime = 0;
///
/// 辅助计算使用
///
private float m_CtrlFrequencyTimer = 0;
#endregion
#region 进入buff(初始化)
///
/// 进入Buff
///
public virtual void BuffOnEnter(RoleAttribute self, RoleAttribute other, string json)
{
m_Self = self;
m_Other = other;
m_IsEnable = true;
}
///
/// 初始化
///
public virtual void InitAttr()
{
}
#endregion
#region 暂停继续buff
///
/// 暂停buff
///
public virtual void PauseBuff()
{
m_IsEnable = false;
}
///
/// 继续buff
///
public virtual void ContinueBuff()
{
m_IsEnable = false;
}
#endregion
#region 持续buff中
///
/// 持续Buff
///
public virtual void BuffOnStay()
{
if (m_IsEnable)
{
switch (m_buffInfo.buffOverlap)
{
//计时结束移除 重新复制是将时间叠加
case BuffOverlap.OnlyStackedTimeWithLimitTime:
m_buffInfo.m_TimingTime += Time.deltaTime;
m_CtrlFrequencyTimer += Time.deltaTime;
if (m_buffInfo.m_Frequency != 0 && m_buffInfo.buffCalculateType == BuffCalculateType.Loop)
{
if (m_CtrlFrequencyTimer >= m_buffInfo.m_Frequency)
{
m_CtrlFrequencyTimer = m_CtrlFrequencyTimer - m_buffInfo.m_Frequency;
InsistCallBack();
}
}
if (m_buffInfo.m_TimingTime >= m_buffInfo.m_LimitTime)
{
m_IsEnable = false;
BuffOnExit();
}
break;
//计时结束移除 重新复制是将时间重置
case BuffOverlap.OnlyStackedResterTimeWithLimitTime:
m_buffInfo.m_TimingTime += Time.deltaTime;
m_CtrlFrequencyTimer += Time.deltaTime;
if (m_buffInfo.m_Frequency != 0 && m_buffInfo.buffCalculateType == BuffCalculateType.Loop)
{
if (m_CtrlFrequencyTimer >= m_buffInfo.m_Frequency)
{
m_CtrlFrequencyTimer = m_CtrlFrequencyTimer - m_buffInfo.m_Frequency;
InsistCallBack();
}
}
if (m_buffInfo.m_TimingTime >= m_buffInfo.m_LimitTime)
{
m_IsEnable = false;
BuffOnExit();
}
break;
case BuffOverlap.OnlyStackedLimitLayer:
m_CtrlFrequencyTimer += Time.deltaTime;
if (m_buffInfo.m_Frequency != 0 && m_buffInfo.buffCalculateType == BuffCalculateType.Loop)
{
if (m_CtrlFrequencyTimer >= m_buffInfo.m_Frequency)
{
m_CtrlFrequencyTimer = m_CtrlFrequencyTimer - m_buffInfo.m_Frequency;
InsistCallBack();
}
}
break;
case BuffOverlap.OnlyStackedLimitLayerWithResetLimitTime:
m_buffInfo.m_TimingTime += Time.deltaTime;
m_CtrlFrequencyTimer += Time.deltaTime;
if (m_buffInfo.m_Frequency != 0 && m_buffInfo.buffCalculateType == BuffCalculateType.Loop)
{
if (m_CtrlFrequencyTimer >= m_buffInfo.m_Frequency)
{
m_CtrlFrequencyTimer = m_CtrlFrequencyTimer - m_buffInfo.m_Frequency;
InsistCallBack();
}
}
if (m_buffInfo.m_TimingTime >= m_buffInfo.m_LimitTime)
{
m_IsEnable = false;
BuffOnExit();
}
break;
case BuffOverlap.MoreStackedLimitTime:
m_buffInfo.m_TimingTime += Time.deltaTime;
m_CtrlFrequencyTimer += Time.deltaTime;
if (m_buffInfo.m_Frequency != 0 && m_buffInfo.buffCalculateType == BuffCalculateType.Loop)
{
if (m_CtrlFrequencyTimer >= m_buffInfo.m_Frequency)
{
m_CtrlFrequencyTimer = m_CtrlFrequencyTimer - m_buffInfo.m_Frequency;
InsistCallBack();
}
}
if (m_buffInfo.m_TimingTime >= m_buffInfo.m_LimitTime)
{
m_IsEnable = false;
BuffOnExit();
}
break;
//永久存在 无需层数叠加 只执行一次
case BuffOverlap.OnlyStackedOnePermanent:
m_CtrlFrequencyTimer += Time.deltaTime;
if (m_buffInfo.m_Frequency != 0 && m_buffInfo.buffCalculateType == BuffCalculateType.Loop)
{
if (m_CtrlFrequencyTimer >= m_buffInfo.m_Frequency)
{
m_CtrlFrequencyTimer = m_CtrlFrequencyTimer - m_buffInfo.m_Frequency;
InsistCallBack();
}
}
break;
//永久存在 无需层数叠加 每间隔时间执行一次
case BuffOverlap.OnlyStackedLoopTimeCallPermanent:
m_buffInfo.m_TimingTime += Time.deltaTime;
if (m_buffInfo.m_TimingTime >= m_buffInfo.m_LimitTime)
{
m_buffInfo.m_TimingTime = m_buffInfo.m_TimingTime - m_buffInfo.m_LimitTime;
InsistCallBack();
}
break;
//倒计时结束消失 无重叠
case BuffOverlap.OnlyStackedLimitTime:
m_buffInfo.m_TimingTime += Time.deltaTime;
m_CtrlFrequencyTimer += Time.deltaTime;
if (m_buffInfo.m_Frequency != 0 && m_buffInfo.buffCalculateType == BuffCalculateType.Loop)
{
if (m_CtrlFrequencyTimer >= m_buffInfo.m_Frequency)
{
m_CtrlFrequencyTimer = m_CtrlFrequencyTimer - m_buffInfo.m_Frequency;
InsistCallBack();
}
}
if (m_buffInfo.m_TimingTime >= m_buffInfo.m_LimitTime)
{
m_IsEnable = false;
BuffOnExit();
}
break;
//永久存在现实层数
case BuffOverlap.OnlyStackOnePermanentWithLimitLayer:
m_CtrlFrequencyTimer += Time.deltaTime;
if (m_buffInfo.m_Frequency != 0 && m_buffInfo.buffCalculateType == BuffCalculateType.Loop)
{
if (m_CtrlFrequencyTimer >= m_buffInfo.m_Frequency)
{
m_CtrlFrequencyTimer = m_CtrlFrequencyTimer - m_buffInfo.m_Frequency;
InsistCallBack();
}
}
break;
default:
break;
}
}
}
#endregion
#region 持续回调 根据Frequency数据间隔进行调用
///
/// 持续回调 根据Frequency数据间隔进行调用
///
public virtual void InsistCallBack()
{
}
#endregion
#region 层数回调 根据当前层数进行回调
///
/// 根据层数进行回调
///
///
public virtual void LayerCallBack(int layer)
{
}
///
/// 减少层数
///
public virtual void RemoveLayerCallBack()
{
m_buffInfo.m_TimingLayer--;
}
#endregion
#region 离开buff
///
/// 离开buff
///
public virtual void BuffOnExit()
{
if (m_Self != null)
{
m_Self.RemoveBuff(this);
}
Dispose();
Destroy(this);
}
#endregion
#region 重复添加buff
///
/// 重复添加
///
public virtual void RepeatAdd()
{
Debug.LogError("重复添加buff");
switch (m_buffInfo.buffOverlap)
{
//计时结束移除 重新复制是将时间叠加
case BuffOverlap.OnlyStackedTimeWithLimitTime:
m_buffInfo.m_LimitTime += m_CtrlLimitTime;
break;
case BuffOverlap.OnlyStackedResterTimeWithLimitTime:
m_buffInfo.m_TimingTime = 0;
break;
case BuffOverlap.OnlyStackedLimitLayer:
if (m_buffInfo.m_TimingLayer < m_buffInfo.m_LimitLayer)
{
m_buffInfo.m_TimingLayer += 1;
LayerCallBack(m_buffInfo.m_TimingLayer);
}
break;
case BuffOverlap.OnlyStackedLimitLayerWithResetLimitTime:
m_buffInfo.m_TimingTime = 0;
if (m_buffInfo.m_TimingLayer < m_buffInfo.m_LimitLayer)
{
m_buffInfo.m_TimingLayer += 1;
LayerCallBack(m_buffInfo.m_TimingLayer);
}
break;
case BuffOverlap.MoreStackedLimitTime:
break;
case BuffOverlap.OnlyStackedOnePermanent:
break;
case BuffOverlap.OnlyStackedLoopTimeCallPermanent:
break;
case BuffOverlap.OnlyStackedLimitTime:
break;
case BuffOverlap.OnlyStackOnePermanentWithLimitLayer:
break;
default:
break;
}
}
#endregion
#region FixedUpdate
private void FixedUpdate()
{
BuffOnStay();
}
#endregion
#region 释放
///
/// 释放接口
///
public virtual void Dispose()
{
m_buffInfo.Dispose();
m_Self = null;
m_Other = null;
m_IsEnable = false;
m_CtrlLimitTime = 0;
m_CtrlFrequencyTimer = 0;
Debug.LogError("移除释放buff");
}
#endregion
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
///
/// 展示类型
///
public enum ShowType
{
None = 0,
///
/// 需要展示UI
///
Show = 1,
///
/// 无需展示UI
///
Hide = 2,
}
///
/// buff类型 获取buf名 找buf实体类和控制类
///
public enum BuffType
{
None = 0,
///
/// 破甲buff
///
PoJiaBuff = 1,
///
/// 流血buff
///
LiuXueBuff = 2,
///
/// 中毒buff
///
ZhongDuBuff = 3,
}
///
/// 挂载类型
///
public enum MountType
{
None = 0,
///
/// 单挂载
///
Only = 1,
///
/// 多次挂载
///
More = 2,
}
///
/// buff叠加类型
///
public enum BuffOverlap
{
None = 0,
///
/// 同时只能挂载单脚本,计时结束移除 重复挂载时将时间进行叠加, (所需字段 LimitTime(限制时间) TimingTime(计时时间))
///
OnlyStackedTimeWithLimitTime = 1,
///
/// 同时只能挂载单脚本,计时结束移除,重复挂载时将时间进行重置 (所需字段 LimitTime(限制时间) TimingTime(计时时间))
///
OnlyStackedResterTimeWithLimitTime = 2,
///
/// 同时只能挂载单脚本,永久存在,重复挂载时将层数进行叠加(有层数上限) (所需字段 LimitLayer(限制层数) TimingLayer(计算层数))
///
OnlyStackedLimitLayer = 3,
///
/// 同时只能挂载单脚本,计时结束移除,重复挂载时将层数进行叠加并将时间重置(有层数上限)(所需字段 LimitLayer(限制时间) TimingTime(计时时间) LimitLayer(限制层数) TimingLayer(计算层数))
///
OnlyStackedLimitLayerWithResetLimitTime = 4,
///
/// 同时多次挂载同脚本,计时结束移除,可以多次重复挂载(所需字段 LimitTime() TimingTime(计时时间))
///
MoreStackedLimitTime = 5,
///
/// 仅堆叠一次 永久存在
///
OnlyStackedOnePermanent = 6,
///
/// 仅堆叠一次 永久存在 每间隔时间执行一次 (所需字段 LimitTime(限制时间) TimingTime(计时时间))
///
OnlyStackedLoopTimeCallPermanent = 7,
///
/// 仅堆叠一次 倒计时结束 (所需字段 LimitTime(限制时间) TimingTime(计时时间))
///
OnlyStackedLimitTime = 8,
///
/// 仅堆叠一次永久存在并且需要展示层数 (所需字段 LimitLayer(限制层数) TimingLayer(计算层数))
///
OnlyStackOnePermanentWithLimitLayer = 9,
}
///
/// 执行次数类型
///
public enum BuffCalculateType
{
None = 0,
///
/// 一次
///
Once = 1,
///
/// 每次都执行跟随时间间隔走
///
Loop = 2,
}
///
/// 关闭类型
///
public enum BuffShutDownType
{
None = 0,
///
/// 关闭所有
///
All = 1,
///
/// 单层关闭
///
Layer = 2,
}
///
/// Buff基类
///
public class BuffEntityBase : IDisposable
{
///
/// buff Id
///
public int buffID;
///
/// buff路径
///
public string buffUrl;
///
/// buff Name
///
public string buffName;
///
/// buff 描述
///
public string buffDesc;
///
/// 此buff是否有UI展示
///
public ShowType showType = ShowType.None;
///
/// buff 类型名 反射string为class使用
///
public BuffType buffType = BuffType.None;
///
/// buff 挂载类型是否可以挂载多个
///
public MountType mountType = MountType.None;
///
/// buff重叠类型
///
public BuffOverlap buffOverlap = BuffOverlap.None;
///
/// 执行次数
///
public BuffCalculateType buffCalculateType = BuffCalculateType.None;
///
/// buff关闭类型
///
public BuffShutDownType buffShutDownType = BuffShutDownType.None;
///
/// 限制时间
///
public float m_LimitTime;
///
/// 计时时间
///
public float m_TimingTime;
///
/// 限制层数
///
public int m_LimitLayer;
///
/// 计算层数
///
public int m_TimingLayer;
///
/// 间隔时间 0代表不跟间隔走
///
public float m_Frequency = 1;
///
/// 执行数值 比如召唤怪物1次 增加攻速5% 攻击力翻倍
///
public float m_NumValue;
///
/// 释放接口
///
public virtual void Dispose()
{
buffID = 0;
buffUrl = null;
buffName = null;
buffDesc = null;
showType = ShowType.None;
buffType = BuffType.None;
mountType = MountType.None;
buffOverlap = BuffOverlap.None;
buffCalculateType = BuffCalculateType.None;
buffShutDownType = BuffShutDownType.None;
m_LimitTime = 0;
m_TimingTime = 0;
m_LimitLayer = 0;
m_TimingLayer = 0;
m_Frequency = 0;
m_NumValue = 0;
}
}
RoleAttribute这个类主要是存放记录当前生物所包含的buff,这样方便来管理。
using System.Collections.Generic;
using UnityEngine;
///
/// 人物基类
///
public class RoleAttribute: MonoBehaviour
{
///
/// buff控制列表
///
public List<BuffCtrlBase> m_buffLst = new List<BuffCtrlBase> ();
///
/// 添加Buff
///
///
public void AddBuff(BuffCtrlBase buffCtrlBase)
{
m_buffLst.Add(buffCtrlBase);
}
///
/// 移除Buff
///
public void RemoveBuff(BuffCtrlBase buffCtrlBase)
{
if(m_buffLst.Contains(buffCtrlBase)) m_buffLst.Remove(buffCtrlBase);
}
///
/// 是否包含该buff
///
///
///
public bool IsContainsBuff(BuffCtrlBase buffCtrlBase)
{
return m_buffLst.Contains (buffCtrlBase);
}
///
/// 是否包含该Id的buff
///
///
///
public bool IsContainsBuff(int Id)
{
for (int i = 0; i < m_buffLst.Count; i++)
{
if (m_buffLst[i].m_buffInfo.buffID == Id) return true;
}
return false;
}
///
/// 是否包含该类型的buff
///
///
///
public bool IsContainsBuff(BuffType buffType)
{
for (int i = 0; i < m_buffLst.Count; i++)
{
Debug.LogError(m_buffLst[i].m_buffInfo.buffType.ToString());
if (m_buffLst[i].m_buffInfo.buffType == buffType) return true;
}
return false;
}
///
/// 获取buff控制类
///
///
///
public BuffCtrlBase GetBuffCtrl(BuffType buffType)
{
for (int i = 0; i < m_buffLst.Count; i++)
{
if (m_buffLst[i].m_buffInfo.buffType == buffType) return m_buffLst[i];
}
return null;
}
///
/// 获取buff控制类
///
///
///
public BuffCtrlBase GetBuffCtrl(int Id)
{
for (int i = 0; i < m_buffLst.Count; i++)
{
if (m_buffLst[i].m_buffInfo.buffID == Id) return m_buffLst[i];
}
return null;
}
///
/// 获取buff控制类集合
///
///
///
public List<BuffCtrlBase> GetBuffCtrls(BuffType buffType)
{
List<BuffCtrlBase> lst = new List<BuffCtrlBase> ();
for (int i = 0; i < m_buffLst.Count; i++)
{
if (m_buffLst[i].m_buffInfo.buffType == buffType) lst.Add(m_buffLst[i]);
}
return lst;
}
}
BuffManager这个则是管理具体给谁添加buff和移除buff的管理类。
using LitJson;
using System;
using System.Collections.Generic;
using UnityEngine;
///
/// Buff管理器
///
public class BuffManager : MonoBehaviour
{
///
/// buff组件单例
///
public static BuffManager Instance;
// Start is called before the first frame update
void Awake()
{
Instance = this;
}
#region 添加Buff
///
/// 创建buff
///
public void AddBuff(RoleAttribute self, RoleAttribute other, BuffType buffType)
{
Type t = Type.GetType(buffType.ToString() + "Ctrl");
//buff数据
string buffData = GetBuffJsonStr(buffType);
//如果buff数据为null
if (string.IsNullOrEmpty(buffData))
{
Debug.LogError("当前buff还未配置无法添加buff");
return;
}
JsonData data = JsonMapper.ToObject(buffData);
//挂载类型
MountType mount = (MountType)int.Parse(data["mountType"].ToString());
//buff控制层脚本
BuffCtrlBase bcb = null;
//多个挂载
if (mount == MountType.More)
{
bcb = self.gameObject.AddComponent(t) as BuffCtrlBase;
self.AddBuff(bcb);
bcb.BuffOnEnter(self, other, GetBuffJsonStr(buffType));
}
//单挂载
else
{
bool IsAdd = self.IsContainsBuff(buffType);
Debug.LogError("IsAdd = " + IsAdd);
//未挂载过
if (!IsAdd)
{
bcb = self.gameObject.AddComponent(t) as BuffCtrlBase;
self.AddBuff(bcb);
bcb.BuffOnEnter(self, other, GetBuffJsonStr(buffType));
}
//已经挂载过
else
{
bcb = self.GetBuffCtrl(buffType);
bcb.RepeatAdd();
}
}
}
#endregion
#region 移除buf(主角、敌人)
///
/// 移除人物身上的buf
///
///
///
public void RemoveBuff(RoleAttribute role, BuffType buffType)
{
role.GetBuffCtrl(buffType)?.BuffOnExit();
}
///
/// 移除人物身上的全部该buf
///
///
///
public void RemoveAllBuff(RoleAttribute role, BuffType buffType)
{
List<BuffCtrlBase> lst = role.GetBuffCtrls(buffType);
lst.ForEach(cb => cb.BuffOnExit());
}
#endregion
#region 获取buff信息
///
/// 根据buff类型 获取buff的json数据
///
public string GetBuffJsonStr(BuffType buffType)
{
TextAsset buffdata = Resources.Load<TextAsset>(string.Format("BuffData/{0}/{0}", buffType.ToString()));
if (buffdata == null)
{
Debug.LogErrorFormat("未找到对应的{0}{1} ", buffType.ToString(), " buff数据,检查是否配置buff文件");
return null;
}
return buffdata.text;
}
///
/// 根据buff类型 获取buff的JsonData
///
public JsonData GetBuffJsonData(BuffType buffType)
{
TextAsset buffdata = Resources.Load<TextAsset>(string.Format("BuffData/{0}/{0}", buffType.ToString()));
if (buffdata == null)
{
Debug.LogErrorFormat("未找到对应的{0}{1} ", buffType.ToString(), " buff数据,检查是否配置buff文件");
return null;
}
return JsonMapper.ToObject(buffdata.text);
}
///
/// 根据buff的Id 获取buff的json数据
///
///
public string GetBuffJsonStr(int Id)
{
if (Id > Enum.GetNames(typeof(BuffType)).Length - 1)
{
Debug.LogError("Id超过buff上限,未配置对应的buff,请先配置");
return null;
}
BuffType bt = (BuffType)Id;
return GetBuffJsonStr(bt);
}
///
/// 根据buff的Id 获取buff的JsonData
///
///
public JsonData GetBuffJsonData(int Id)
{
//buff数据
string buffData = GetBuffJsonStr(Id);
if (string.IsNullOrEmpty(buffData))
{
return null;
}
return JsonMapper.ToObject(buffData);
}
#endregion
}
json如果太长看不太明白可以用在线解析工具
解析的如下图
这里我只讲几个比较关键的点,至于一眼能看懂的我就不详细说了。
这里也不需要多说,如果你要添加新的buff的话需要在这里创建一个新枚举,按照我下面创建的命名方式,创建的json文件放在下面的文件夹下(需要自行创建)
这个意思是你创建的脚本是否可以在同一个对象上挂载多个,如果你想能挂载无数个则用多挂载,如果想要单挂载跌层数则选择单挂载
这里主要是buff的叠加类型这里我创建了几个比较常用的方式,具体的逻辑是在BuffCtrlBase中的RepeatAdd方法和BuffOnStay方法
这个参数也是为后面接入做准备留的接口,因为现在关闭其实都是有的,可以关闭多个也可以关闭一个,所以这里暂时是不需要,具体的方法都在BuffManager中。
这里是关于buff回调执行的问题,如果是持续的buff则可以选择Loop在设置固定时间进行回调,如果只有一次(比如属性提升的buff)则可以选择Once。这里需要配合json的"m_Frequency"来使用,这里如果不为0并且 BuffCalculateType类型为Loop则相当于多少秒执行一次回调如下。
"m_LimitTime": 0,
"m_TimingTime": 0,
"m_LimitLayer": 0,
"m_TimingLayer": 0,
这几个参数大概看下就明白了,具体逻辑也在BuffCtrlBase中
通过BuffManager来对生物添加移除buff,这里注意:需要挂载的生物是需要集成RoleAttribute的
以上是我对网上一些面向过程的buff系统总结写出来的,如果有问题或者不明白的地方可以联系我,同事也感谢大家一直以来的支持。