今天来考虑一下,想要实现一个buff系统需要什么功能。
能力不行,写的不好,请大家指正完善,谢谢~~
在我们接到一个需求的时候,往往需要先分析一下需求,确定我们要实现什么样的功能,大部分的情况下需求功能由策划提供,而你只需要考虑怎么实现就行了。不过今天特殊,没有策划,只能我们自己分析考虑了。
更据以往玩过的游戏,来看看buff系统都有什么功能:
1.计时,一个buff往往都会有存在时间,几秒,几分,甚至几天的存在
2.层级,有的buff只有一层,而有的buff可以拥有多层,效果叠加,有了层数,我们还需要考虑到,buff结束后是一次消除,还是逐层消除
3.次数,有的buff只会执行一次,而有的buff可以在一段时间内一直执行
4.间隔调用,有的buff比如加血,往往会1秒或两秒钟才会执行一次,其他时间是不会执行
5.行为操控,眩晕buff,击飞buff 等都是对玩家行为进行一种操控,夺取玩家控制权。
根据以上结果,我们需要制作的功能基本上就很明确了:
1.时间控制
2.层级控制
3.执行次数
5.位移控制等操作
下面就开始书写代码了
首先,我们先来实现一个配置类,这个类用来实现加载buff的配置表信息
[System.Serializable]
public class BuffBase
{
///
/// BuffID
///
public int BuffID;
///
/// Buff类型
///
public BuffType BuffType;
///
/// 执行此
///
public BuffCalculateType BuffCalculate = BuffCalculateType.Loop;
///
/// 叠加类型
///
public BuffOverlap BuffOverlap = BuffOverlap.StackedLayer;
///
/// 消除类型
///
public BuffShutDownType BuffShutDownType = BuffShutDownType.All;
///
/// 如果是堆叠层数,表示最大层数,如果是时间,表示最大时间
///
public int MaxLimit = 0;
///
/// 执行时间
///
public float Time = 0;
///
/// 间隔时间
///
public float CallFrequency = 1;
///
/// 执行数值 比如加血就是每次加多少
///
public float Num;
}
接下来是上面使用的几个枚举类型
///
/// buff类型
///
public enum BuffType
{
///
/// 恢复HP
///
AddHp,
///
/// 增加最大血量
///
AddMaxHp,
///
/// 减血
///
SubHp,
///
/// 减最大生命值
///
SubMaxHp,
///
/// 眩晕
///
AddVertigo,
///
/// 被击浮空
///
AddFloated,
///
/// 击退
///
AddRepel,
///
/// 冲刺
///
AddSprint,
///
/// 被击浮空
///
AddDamageFloated,
///
/// 添加忽略重力
///
AddIsIgnoreGravity,
}
///
/// 叠加类型
///
public enum BuffOverlap
{
None,
///
/// 增加时间
///
StackedTime,
///
/// 堆叠层数
///
StackedLayer,
///
/// 重置时间
///
ResterTime,
}
///
/// 关闭类型
///
public enum BuffShutDownType
{
///
/// 关闭所有
///
All,
///
/// 单层关闭
///
Layer,
}
///
/// 执行类型
///
public enum BuffCalculateType
{
///
/// 一次
///
Once,
///
/// 每次
///
Loop,
}
BuffType枚举,是我们用来控制buff类型的,比如眩晕,比如增加HP,比如减少HP等等…
BuffOverlap枚举,就是叠加类型,上面我们说,有的buff是叠加层数,有的buff是重置时间,或者增加时间。
BuffShutDownType枚举,这个枚举的作用是控制倒计时结束后,应该是减一层,还是直接清空buff
然后我们创建一个BuffManager类,用来配置我们buff属性:
public class BuffManager : MonoBehaviour {
private static BuffManager _instance;
public static BuffManager Instance
{
get { return _instance; }
}
public List buffBase = new List();
}
buff数据有了,我们还需要提供buff的执行方法吧,就是我使用这个buff后这个buff要做什么,是我们需要知道的。
这就需要我们在写一个类,实现buff 执行中的功能。
buff使用用到角色身上的,所以我们写一个脚本ActorBuff 挂载到角色身上。
这个类的最主要功能:
1.添加buff
2.执行buff
3.buff移除buff
我们先来看看实现:
public class ActorBuff : ActorCompent {
[SerializeField]
private List buffs = new List();
public override void OnInit()
{
InitBuff();
}
void InitBuff()
{
}
void Update()
{
}
///
/// 执行buff
///
void FixedUpdate()
{
for (int i = buffs.Count - 1; i >= 0; i--)
{
buffs[i].OnTick(Time.deltaTime);
if (buffs[i].IsFinsh)
{
buffs[i].CloseBuff();
buffs.Remove(buffs[i]);
}
}
}
///
/// 添加buff
///
///
public void AddBuff(BuffData buffData)
{
if (!buffs.Contains(buffData))
{
buffs.Add(buffData);
buffData.StartBuff();
}
}
///
/// 移除buff
///
///
public void RemoveBuff(int buffDataID)
{
BuffData bd = GetBuff(buffDataID);
if(bd!=null)
bd.CloseBuff();
}
///
/// 移除buff
///
///
public void RemoveBuff(BuffData buffData)
{
if (buffs.Contains(buffData))
{
buffData.CloseBuff();
}
}
///
/// 获取buff
///
///
///
public BuffData GetBuff(int buffDataID)
{
for (int i = 0; i < buffs.Count; i++)
{
if (buffs[i].buffDataID == buffDataID)
return buffs[i];
}
return null;
}
///
/// 获取buff
///
///
///
public BuffData GetBuffByBaseID(int buffBaseID)
{
for (int i = 0; i < buffs.Count; i++)
{
if (buffs[i].BuffID == buffBaseID)
return buffs[i];
}
return null;
}
///
/// 获取buff
///
///
///
public BuffData[] GetBuff(BuffType buffType)
{
List buffdatas = new List();
for (int i = 0; i < buffs.Count; i++)
{
if (buffs[i].BuffType == buffType)
buffdatas.Add(buffs[i]);
}
return buffdatas.ToArray();
}
///
/// 执行buff
///
///
public void DoBuff(int buffID)
{
BuffManager.Instance.DoBuff(Actor,buffID);
}
}
正如上面所说,这个类拥有这几个方法:
FixedUpdate()
这个方法中用来实现buff的执行,因为我写的buff有牵扯到位移,所以我放到FixedUpdate中了
DoBuff(int buffID)
这个方法是添加buff 的开端,他会调用BuffManager.Instance.DoBuff(Actor,buffID)
方法,将自己和要添加buff发送过去,在BuffManager
中解析完buff数据后会生成BuffData
数据,然后调用 AddBuff(BuffData buffData)
方法,将buff添加到buffs
列表中
AddBuff(BuffData buffData)
正如上面所说 BuffManager
解析完buff数据后会生成BuffData
调用进行添加
RemoveBuff
buff执行完毕后移除buff
RemoveBuff
同上
GetBuffByBaseID(int buffBaseID)
,GetBuff(int buffDataID)
,GetBuff(BuffType buffType)
三个方法都是获取buffdata,不过所需数据不同,其中GetBuffByBaseID
是根据BuffBase
中的BuffID
获取配置表的,而GetBuff
是根据BuffData
类自己生成的ID来获取,GetBuff(BuffType buffType)
就是根据buff类型来获取BuffData
了
在上面的类型调用了BuffManager的DoBuff(Actor,buffID)
方法,我们没有实现,我们先写个空方法来占个坑。在BuffManager
写入两个方法:
///
/// 执行buff
///
///
///
public void DoBuff(Actor actor,int buffID)
{
DoBuff(actor,GetBuffBase(buffID));
}
///
/// 执行buff
///
///
///
public void DoBuff(Actor actor, BuffBase buff)
{
}
功能先不实现,现在我们需要着重的来看一下BuffData
类的实现,这个类是执行中的buff,主要的buff功能均在此类中实现。
写这个类的时候比较蛋疼的是当初写的不知道怎么想的把BuffBase中的属性在此类中基本上都写了一遍,其实只要把BuffBase引用过来就行了,不过要过年了我先不该了,你们看的时候知道这个问题就行了。
因为这个BuffData的创建频率应该比较高,因为添加buff的时候就需要创建一个,使用完了就销毁,所以为了避免buffdata的重建,我们需要将失效buffdata存起来,后面在需要的时候重置里面的数据,再次使用。
[Serializable]
public class BuffData
{
///
/// 缓存栈
///
private static Stack poolCache = new Stack();
///
/// BuffData下一个ID
///
public static int buffIndex { get; private set; }
///
/// 构造方法
///
private BuffData() {
buffDataID = buffIndex++;
}
///
/// 创建BuffData
///
///
public static BuffData Create()
{
if (poolCache.Count < 1)
return new BuffData();
BuffData buffData = poolCache.Pop();
return buffData;
}
///
/// 弹出
///
///
private static BuffData Pop()
{
if (poolCache.Count < 1)
{
BuffData bd = new BuffData();
return bd;
}
BuffData buffData = poolCache.Pop();
return buffData;
}
///
/// 压入
///
///
private static void Push(BuffData buffData)
{
poolCache.Push(buffData);
}
}
这样,我们在创建BuffData
的时候通过BuffData.Create()
方法创建,它会先去poolCache
缓存池中去查看有没有空闲的BuffData
,如果有,就取出来使用,没有就进行创建。
按照逻辑上来说,万物有始有终,所以我们需要在BuffData
中添加两个方法,即开始方法StartBuff()
和结束方法CloseBuff()
:
///
/// 开始Buff
///
public void StartBuff()
{
}
///
/// 关闭buff
///
public void CloseBuff()
{
}
但是别忘了,我们还需要一个中间不断执行的方法OnTick(float delayTime)
:
///
/// 执行中
///
public void OnTick(float delayTime)
{
}
这样,我们开始,执行中,结束方法都有了,感觉主题框架搭完了,我们得开始写实际功能了。
首先,我们需要先实现类的属性,另外,我们还想在buff执行时候发送一个事件,和结束的时候发送一个事件给我们,所以我们还需要定义几个事件
///
/// ID
///
public int buffDataID;
///
/// 配置表ID
///
public int BuffID;
///
/// buff类型
///
public BuffType BuffType;
///
/// 叠加类型
///
public BuffOverlap BuffOverlap = BuffOverlap.StackedLayer;
///
/// 执行次数
///
public BuffCalculateType BuffCalculate = BuffCalculateType.Loop;
///
/// 关闭类型
///
public BuffShutDownType BuffShutDownType = BuffShutDownType.All;
///
/// 最大限制
///
public int MaxLimit;
///
/// 当前数据
///
[SerializeField]
private int _Limit;
public int GetLimit { get { return _Limit; } }
///
/// 执行时间
///
[SerializeField]
private float PersistentTime;
public float GetPersistentTime { get { return PersistentTime; } }
///
/// 当前时间
///
[SerializeField]
private float _CurTime;
///
/// 调用频率
///
public float CallFrequency { get; set; }
///
/// 当前时间
///
private float _curCallFrequency { get; set; }
///
/// 执行次数
///
[SerializeField]
private int index = 0;
///
/// 开始调用
///
public Action OnStart;
///
/// 结束调用
///
public Action OnFinsh;
上面很多属性其实不需要重复的写,注意了直接引用BuffBase的实例即可,但是以下划线开头的属性,是需要在这个类中定义的,他们控制时间,当前数量等数据。
我们构造方法中最好重置一个部分属性的值,看起来向这样:
///
/// 构造方法
///
private BuffData() {
buffDataID = buffIndex++;
CallFrequency = 1;
PersistentTime = 0;
}
然后我们开始写开始方法:
///
/// 开始Buff
///
public void StartBuff()
{
//ChangeLimit(MaxLimit);
_isFinsh = false;
if (OnStart != null)
OnStart();
}
首先我们要确保_isFinsh = false,确保我们buff尚未结束,其次,就是发送事件了,通知我们buff要开始运行了。
其次,我们写结束方法:
///
/// 关闭buff
///
public void CloseBuff()
{
if (OnFinsh != null)
OnFinsh();
Clear();
}
//重置数据
public void Clear()
{
_Limit = 0;
BuffID = -1;
index = 0;
PersistentTime = 0;
_CurTime = 0;
Data = null;
CallFrequency = 0;
_curCallFrequency = 0;
//OnCallBackParam = null;
//OnCallBack = null;
OnStart = null;
OnFinsh = null;
_isFinsh = false;
Push(this);
}
同样,发送结束事件,其次,调用Clear()
方法重置部分属性值。最后别忘了Push(this)
,将此类插入缓存中。
另外,我们buff叠加方式,有所不同,所以我们想在一些特殊的情况下通知一个事件,所以我在BuffData
又添加了以下几个事件:
///
/// 根据 CallFrequency 间隔 调用 结束时会调用一次 会传递 Data数据
///
public Action<object> OnCallBackParam;
///
/// ///
/// 根据 CallFrequency 间隔 调用 结束时会调用一次 会传递 Data数据 int 次数
///
///
public Action<object, int> OnCallBackParamIndex;
///
/// 根据 CallFrequency 间隔 调用 结束时会调用一次
///
public Action OnCallBack;
///
/// 根据 CallFrequency 间隔 调用 结束时会调用一次 int 次数
///
public Action<int> OnCallBackIndex;
///
/// 当改变时间
///
public Action OnChagneTime;
///
/// 当添加层
///
public Action OnAddLayer;
///
/// 当删除层
///
public Action OnSubLayer;
加载上面OnFinsh
事件下就行了。
剩下的就是需要实现OnTick
方法了:
///
/// 执行中
///
///
public void OnTick(float delayTime)
{
_CurTime += delayTime;
//判断时间是否结束
if (_CurTime >= PersistentTime)
{
///调用事件
CallBack();
//判断结束类型 为层方式
if (BuffShutDownType == BuffShutDownType.Layer)
{
SubLayer();
//判读层数小于1 则结束
if (_Limit <= 0)
{
_isFinsh = true;
return;
}
//重置时间
_curCallFrequency = 0;
_CurTime = 0;
return;
}
_isFinsh = true;
return;
}
//如果是按频率调用
if (CallFrequency > 0)
{
_curCallFrequency += delayTime;
if (_curCallFrequency >= CallFrequency)
{
_curCallFrequency = 0;
CallBack();
}
return;
}
///调用回调
CallBack();
}
///
/// 调用回调
///
private void CallBack()
{
//次数增加
index++;
//判断buff执行次数
if (BuffCalculate == BuffCalculateType.Once)
{
if (index > 1) { index = 2; return; }
}
if (OnCallBack != null)
OnCallBack();
if (OnCallBackIndex != null)
OnCallBackIndex(index);
if (OnCallBackParam != null)
OnCallBackParam(Data);
if (OnCallBackParamIndex != null)
OnCallBackParamIndex(Data, index);
}
///
/// 加一层
///
public void AddLayer()
{
_Limit++;
_CurTime = 0;
if (_Limit > MaxLimit)
{
_Limit = MaxLimit;
return;
}
if (OnAddLayer != null)
OnAddLayer(this);
}
///
/// 减一层
///
public void SubLayer()
{
_Limit--;
if (OnSubLayer != null)
OnSubLayer(this);
}
///
/// 重置时间
///
public void ResterTime()
{
_CurTime = 0;
}
///
/// 修改 时间
///
///
public void ChangePersistentTime(float time)
{
PersistentTime = time;
if (PersistentTime >= MaxLimit)
PersistentTime = MaxLimit;
if (OnChagneTime != null)
OnChagneTime(this);
}
主要流程控制是在Tick
方法中执行的。而主要的逻辑操作是在提供的几个事件中进行编辑。这个先不急,我们再在BuffData
中添加几个方法:
///
/// 获取当前执行时间
///
public float GetCurTime
{
get { return _CurTime; }
}
///
/// 是否结束
///
public bool IsFinsh
{
get { return _isFinsh; }
}
根据需求,我们在写几个创建方法,和构造函数:
private BuffData(float persistentTime,Action onCallBack)
{
PersistentTime = persistentTime;
OnCallBack = onCallBack;
buffDataID = buffIndex++;
}
public static BuffData Create(BuffBase buffBase,Action onCallBack)
{
return Create(buffBase,onCallBack,null,null);
}
public static BuffData Create(BuffBase buffBase, Action onCallBack,Action addLayerAcion,Action subLayerAction)
{
BuffData db = Pop();
db.BuffCalculate = buffBase.BuffCalculate;
db.BuffID = buffBase.BuffID;
db.CallFrequency = buffBase.CallFrequency;
db.PersistentTime = buffBase.Time;
db.BuffOverlap = buffBase.BuffOverlap;
db.BuffShutDownType = buffBase.BuffShutDownType;
db.BuffType = buffBase.BuffType;
db.MaxLimit = buffBase.MaxLimit;
db.OnCallBack = onCallBack;
db.OnAddLayer = addLayerAcion;
db.OnSubLayer = subLayerAction;
db._Limit = 1;
return db;
}
以上只是更据需求添加就行了,不是必要的。
至此,我们BuffData
列结束了。
接下来,Buff的流程控制有,我们还需要具体的执行方法,还记得上面提供的几个事件吗?在事件通知的时候我们进行逻辑操作就行了。
所以我们需要完善一下BuffManager类。
首先,我们在上面的BuffManager类中添加了一个方法DoBuff(Actor actor, BuffBase buff)
,如果忘记了可以翻上去看看。然后我们只实现了方法,还没实现功能,接下来我们就需要实现次数功能了。
它看起像这样:
///
/// 执行buff
///
///
///
public void DoBuff(Actor actor, BuffBase buff)
{
if (buff == null) return;
BuffData db = null;
switch (buff.BuffType)
{
case BuffType.AddHp: //增加血量
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
actor.ActorAttr.AddHP((int)buff.Num);
});
}
break;
case BuffType.AddMaxHp: //增加最大血量
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
actor.ActorAttr.AddMaxHP((int)buff.Num);
}, delegate {
actor.ActorAttr.AddMaxHP((int)buff.Num);
}, delegate {
actor.ActorAttr.SubMaxHp((int)buff.Num);
});
}
break;
case BuffType.SubHp: //减少血量
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
actor.ActorAttr.SubHp((int)buff.Num);
});
}
break;
case BuffType.SubMaxHp: //减少最大血量
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
actor.ActorAttr.SubMaxHp((int)buff.Num);
}, delegate
{
actor.ActorAttr.SubMaxHp((int)buff.Num);
}, delegate
{
actor.ActorAttr.AddMaxHP((int)buff.Num);
});
}
break;
case BuffType.AddDamageFloated: //浮空
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
if (actor.ActorState != ActorState.DamageRise)
actor.ActorAttr.DamageRiseAbility = buff.Num;
actor.SetDamageRiseState();
});
}
break;
case BuffType.AddFloated:
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
Vector3 moveDir = Vector3.up;
moveDir *= buff.Num;
actor.CharacterController.Move(moveDir*Time.deltaTime);
});
}
break;
case BuffType.AddSprint:
if (!IsAdd(actor,buff))
{
db = BuffData.Create(buff, delegate {
Vector3 moveDir = actor.transform.forward;
moveDir *= buff.Num;
moveDir.y += -20;
actor.CharacterController.Move(moveDir*Time.deltaTime);
//actor.Translate(Vector3.forward * buff.Num * Time.deltaTime);
});
}
break;
case BuffType.AddIsIgnoreGravity:
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, null);
db.OnStart = delegate { actor.ActorPhysical.IsIgnoreGravity = true; };
db.OnFinsh = delegate
{
actor.ActorPhysical.IsIgnoreGravity = false;
};
}
break;
}
if (db != null)
actor.ActorBuff.AddBuff(db);
}
///
/// 玩家是否已经有此buff
///
///
///
///
private bool IsAdd(Actor actor,BuffBase buff)
{
BuffData oldBuff = actor.ActorBuff.GetBuffByBaseID(buff.BuffID);
if (oldBuff != null)
{
switch (buff.BuffOverlap)
{
case BuffOverlap.ResterTime:
oldBuff.ResterTime();
break;
case BuffOverlap.StackedLayer:
oldBuff.AddLayer();
break;
case BuffOverlap.StackedTime:
oldBuff.ChangePersistentTime(oldBuff.GetPersistentTime + buff.Time);
break;
default:
break;
}
return true;
}
return false;
}
///
/// 获取配置数据
///
///
///
public BuffBase GetBuffBase(int buffID)
{
for (int i = 0; i < buffBase.Count; i++)
{
if (buffBase[i].BuffID == buffID)
return buffBase[i];
}
return null;
}
注意,后面还跟了几个方法,我们先来介绍一下:
IsAdd(Actor actor,BuffBase buff)
方法,主要是查看玩家身上是否已经曾在相同的buff,并且根据BuffOverlap(叠加类型)进行相对应的buff叠加。还记得在BuffData中添加的那几个方法吗?ResterTime()
、AddLayer()
、ChangePersistentTime()
分别对应重置时间,叠加层、修改时间,注意哟我这里叠加层会重置buff时间,当然你也可以不用重置时间。
GetBuffBase(int buffID)
这个方法作用,就是获取配置表,当然现在我们没用配置表,所以直接检索列表就行。
最后我们介绍DoBuff(Actor actor, BuffBase buff)
方法中内容:
根据上面的代码来看一大串的case,这个显然不好看,但是你们知道的,过年了。不过这个地方还是可以用工厂或者注册类的方式去做,我这里直接用switch和case了。
我们先看BuffType.AddHp
的分支:
我们先看一下addhp的配置
id为0,buff类型为Addhp(BuffType) ,添加血量,执行次数(BuffCalculate)为Loop就是不断的执行,叠加方式(BuffOverlap) 时间叠加,清除类型(Buff Shut Down)为层清除,在这里这个清除类型什么样的都可以。根据上下文知道,添加血量按时间计算最大限制就是1000秒(MaxLimit),持续时间为5秒(Time),调用频率为1秒一次(CallFrequency)。每次增加血量为10(num).
那我们在添加 Buff的时候就会调用BuffManager
中的DoBuff
方法,通过BuffType
执行BuffType.AddHp
case分支中的逻辑:
首先,我们需要判断一下玩家身上是否存在该类型buff,没错就是通过IsAdd
方法,如果有,则根据叠加方式进行叠加,并返回true,否则返回flase。
其次,如果为flase,那代表我们要创建BuffData
类了,这个是什么呢?就是运行时的Buff类。我们调用Create(BuffBase buffBase,Action onCallBack)
,两个参数,一个是buff数据,一个回调方法,因为AddHP的Call Frequency
为1,所以会走频率调用的方,就是BuffData
中的Tick
方法:
//如果是按频率调用
if (CallFrequency > 0)
{
_curCallFrequency += delayTime;
if (_curCallFrequency >= CallFrequency)
{
_curCallFrequency = 0;
CallBack();
}
return;
}
这样这里CallBack
就会按频率进行调用。
db = BuffData.Create(buff, delegate
{
actor.ActorAttr.AddHP((int)buff.Num);
});
CallBack
里的逻辑,就是你增加血量的逻辑了。如上,这是我的加血方法,你应该提供你自己的方法
最后别忘了把BuffData
添加到玩家身上进行执行:
if (db != null)
actor.ActorBuff.AddBuff(db);
这样我们一个加血的buff制作完成了。
根据上面,我在研究一个AddMaxHp的Buff,一看就很明白,这是一个临时增加最大HP的buff,同样我们先看配置:
与上面AddHP不同的是,这里我们调用次数为一次,叠加方式为层叠加,调用频率为0。
按照上面的流程,判断是否存在,存在,叠加,不存在创建。
db = BuffData.Create(buff, delegate
{
actor.ActorAttr.AddMaxHP((int)buff.Num);
}, delegate {
actor.ActorAttr.AddMaxHP((int)buff.Num);
}, delegate {
actor.ActorAttr.SubMaxHp((int)buff.Num);
});
调用的是Create(BuffBase buffBase, Action onCallBack,Action
方法。
相比上面而言,多了addLayerAcion
和subLayerAction
两个事件,根据BuffData
中的代码知道addLayerAcion
是在添加层是调用,而subLayerAction
是在删除层时调用。
而onCallBack是在第一次运行该buff的时候调用一次。
我们来看一下,我默认的血量
然后我开始执行buff,
我执行3次,根据配置,知道一次叠加增加100点血量。那三次后应该是1300的血量
正确,为了方便查看buff,我把BuffData在检视面板中显示出来了,我们来看看ActorBuff
中的内容
我们可以看到,当前的MaxLimit 为10,Limit为3 ,Persistent Time 为10。在这里MaxLimit表示最大层数,也就说最大血量叠加最多只能叠加10层,如果超过10层则强制为0层,Limit为当前层数,PersistentTime 为每层存在的时间。
通过实例,就可以看到,没过10秒钟,就会减少一层知道所有结束为止,血量也会变回原来的数值。
上面的两个例子都是关于属性的增减,下面看一个关于控制的例子:
每个游戏基本都会有眩晕,但不幸的是,我没写。不过我这里有个关于忽略重力的例子和眩晕差不多:
case BuffType.AddIsIgnoreGravity:
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, null);
db.OnStart = delegate { actor.ActorPhysical.IsIgnoreGravity = true; };
db.OnFinsh = delegate
{
actor.ActorPhysical.IsIgnoreGravity = false;
};
}
break;
大概流程都差不多,不同的回调的使用方法。这里同样创建一个BuffData
,但是使用的回调是OnStart
和OnFinsh
,
在回调用实现了忽略重力的方法。
在这里,配置表中的数据基本上没什么作用,除了一个时间,不过我这里没有用配置表里的时间,我用的动画时间。
最后一个,位移的例子:
比如我有个一个技能,我想让英雄使用该技能时进行冲刺,就是一个直线位移,通过buff怎么实现呢?
case BuffType.AddSprint:
if (!IsAdd(actor,buff))
{
db = BuffData.Create(buff, delegate {
Vector3 moveDir = actor.transform.forward;
moveDir *= buff.Num;
moveDir.y += -20;
actor.CharacterController.Move(moveDir*Time.deltaTime);
//actor.Translate(Vector3.forward * buff.Num * Time.deltaTime);
});
}
break;
上面这个buff我就进行简单位移操作。
我们看一下配置
这个buff中我们需要用到3个属性:执行次数为Loop,持续时间,距离。
注意要将CallFrequency设置为零这样才能保证CallBack每帧都会调用。
然后在回调中实现位移方法就OK了
为了防止我书写的漏掉什么代码,下面我把完整的代码贴出来:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class BuffManager : MonoBehaviour {
private static BuffManager _instance;
public static BuffManager Instance
{
get { return _instance; }
}
public List buffBase = new List();
#region GUI
[SerializeField]
private Actor TestActor;
#endregion
void Awake()
{
_instance = this;
GUITools.ResterWindowAction(new Rect(300, 0, 220, 120), delegate(GUIAction action)
{
action.Rect = GUI.Window(action.Id, action.Rect, delegate
{
action.Param[0] = GUI.TextField(new Rect(10, 20, 200, 20), action.Param[0]);
action.Param[1] = GUI.TextField(new Rect(10, 45, 200, 20), action.Param[1]);
if (GUI.Button(new Rect(10, 70, 200, 30), "AddBuff"))
{
if (TestActor == null || !TestActor.gameObject.Equals(action.Param[1]))
{
GameObject obj = GameObject.Find(action.Param[1]);
if (obj != null)
TestActor = obj.GetComponent();
}
if (TestActor != null)
DoBuff(TestActor, int.Parse(action.Param[0]));
else
GUI.Label(new Rect(10, 105, 200, 10), "测试Actor 为 null ,请检查Actor Name是否正确!");
}
GUI.DragWindow();
}, action.Id+ " - Buff Test|BuffManager");
},"0","Player");
}
///
/// 执行buff
///
///
///
public void DoBuff(Actor actor,int buffID)
{
DoBuff(actor,GetBuffBase(buffID));
}
///
/// 执行buff
///
///
///
public void DoBuff(Actor actor, BuffBase buff)
{
if (buff == null) return;
BuffData db = null;
switch (buff.BuffType)
{
case BuffType.AddHp: //增加血量
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
actor.ActorAttr.AddHP((int)buff.Num);
});
}
break;
case BuffType.AddMaxHp: //增加最大血量
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
actor.ActorAttr.AddMaxHP((int)buff.Num);
}, delegate {
actor.ActorAttr.AddMaxHP((int)buff.Num);
}, delegate {
actor.ActorAttr.SubMaxHp((int)buff.Num);
});
}
break;
case BuffType.SubHp: //减少血量
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
actor.ActorAttr.SubHp((int)buff.Num);
});
}
break;
case BuffType.SubMaxHp: //减少最大血量
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
actor.ActorAttr.SubMaxHp((int)buff.Num);
}, delegate
{
actor.ActorAttr.SubMaxHp((int)buff.Num);
}, delegate
{
actor.ActorAttr.AddMaxHP((int)buff.Num);
});
}
break;
case BuffType.AddDamageFloated: //浮空
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
if (actor.ActorState != ActorState.DamageRise)
actor.ActorAttr.DamageRiseAbility = buff.Num;
actor.SetDamageRiseState();
});
}
break;
case BuffType.AddFloated:
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, delegate
{
Vector3 moveDir = Vector3.up;
moveDir *= buff.Num;
actor.CharacterController.Move(moveDir*Time.deltaTime);
});
}
break;
case BuffType.AddSprint:
if (!IsAdd(actor,buff))
{
db = BuffData.Create(buff, delegate {
Vector3 moveDir = actor.transform.forward;
moveDir *= buff.Num;
moveDir.y += -20;
actor.CharacterController.Move(moveDir*Time.deltaTime);
//actor.Translate(Vector3.forward * buff.Num * Time.deltaTime);
});
}
break;
case BuffType.AddIsIgnoreGravity:
if (!IsAdd(actor, buff))
{
db = BuffData.Create(buff, null);
db.OnStart = delegate { actor.ActorPhysical.IsIgnoreGravity = true; };
db.OnFinsh = delegate
{
actor.ActorPhysical.IsIgnoreGravity = false;
};
}
break;
}
if (db != null)
actor.ActorBuff.AddBuff(db);
}
///
/// 玩家是否已经有此buff
///
///
///
///
private bool IsAdd(Actor actor,BuffBase buff)
{
BuffData oldBuff = actor.ActorBuff.GetBuffByBaseID(buff.BuffID);
if (oldBuff != null)
{
switch (buff.BuffOverlap)
{
case BuffOverlap.ResterTime:
oldBuff.ResterTime();
break;
case BuffOverlap.StackedLayer:
oldBuff.AddLayer();
break;
case BuffOverlap.StackedTime:
oldBuff.ChangePersistentTime(oldBuff.GetPersistentTime + buff.Time);
break;
default:
break;
}
return true;
}
return false;
}
///
/// 获取配置数据
///
///
///
public BuffBase GetBuffBase(int buffID)
{
for (int i = 0; i < buffBase.Count; i++)
{
if (buffBase[i].BuffID == buffID)
return buffBase[i];
}
return null;
}
}
///
/// buff类型
///
public enum BuffType
{
///
/// 恢复HP
///
AddHp,
///
/// 增加最大血量
///
AddMaxHp,
///
/// 减血
///
SubHp,
///
/// 减最大生命值
///
SubMaxHp,
///
/// 眩晕
///
AddVertigo,
///
/// 被击浮空
///
AddFloated,
///
/// 击退
///
AddRepel,
///
/// 冲刺
///
AddSprint,
///
/// 被击浮空
///
AddDamageFloated,
///
/// 添加忽略重力
///
AddIsIgnoreGravity,
}
///
/// 叠加类型
///
public enum BuffOverlap
{
None,
///
/// 增加时间
///
StackedTime,
///
/// 堆叠层数
///
StackedLayer,
///
/// 重置时间
///
ResterTime,
}
///
/// 关闭类型
///
public enum BuffShutDownType
{
///
/// 关闭所有
///
All,
///
/// 单层关闭
///
Layer,
}
///
/// 执行类型
///
public enum BuffCalculateType
{
///
/// 一次
///
Once,
///
/// 每次
///
Loop,
}
[System.Serializable]
public class BuffBase
{
///
/// BuffID
///
public int BuffID;
///
/// Buff类型
///
public BuffType BuffType;
///
/// 执行此
///
public BuffCalculateType BuffCalculate = BuffCalculateType.Loop;
///
/// 叠加类型
///
public BuffOverlap BuffOverlap = BuffOverlap.StackedLayer;
///
/// 消除类型
///
public BuffShutDownType BuffShutDownType = BuffShutDownType.All;
///
/// 如果是堆叠层数,表示最大层数,如果是时间,表示最大时间
///
public int MaxLimit = 0;
///
/// 执行时间
///
public float Time = 0;
///
/// 间隔时间
///
public float CallFrequency = 1;
///
/// 执行数值 比如加血就是每次加多少
///
public float Num;
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class ActorBuff : ActorCompent {
[SerializeField]
private List buffs = new List();
public override void OnInit()
{
InitBuff();
}
void InitBuff()
{
}
void Update()
{
}
///
/// 执行buff
///
void FixedUpdate()
{
for (int i = buffs.Count - 1; i >= 0; i--)
{
buffs[i].OnTick(Time.deltaTime);
if (buffs[i].IsFinsh)
{
buffs[i].CloseBuff();
buffs.Remove(buffs[i]);
}
}
}
///
/// 添加buff
///
///
public void AddBuff(BuffData buffData)
{
if (!buffs.Contains(buffData))
{
buffs.Add(buffData);
buffData.StartBuff();
}
}
///
/// 移除buff
///
///
public void RemoveBuff(int buffDataID)
{
BuffData bd = GetBuff(buffDataID);
if(bd!=null)
bd.CloseBuff();
}
///
/// 移除buff
///
///
public void RemoveBuff(BuffData buffData)
{
if (buffs.Contains(buffData))
{
buffData.CloseBuff();
}
}
///
/// 获取buff
///
///
///
public BuffData GetBuff(int buffDataID)
{
for (int i = 0; i < buffs.Count; i++)
{
if (buffs[i].buffDataID == buffDataID)
return buffs[i];
}
return null;
}
///
/// 获取buff
///
///
///
public BuffData GetBuffByBaseID(int buffBaseID)
{
for (int i = 0; i < buffs.Count; i++)
{
if (buffs[i].BuffID == buffBaseID)
return buffs[i];
}
return null;
}
///
/// 获取buff
///
///
///
public BuffData[] GetBuff(BuffType buffType)
{
List buffdatas = new List();
for (int i = 0; i < buffs.Count; i++)
{
if (buffs[i].BuffType == buffType)
buffdatas.Add(buffs[i]);
}
return buffdatas.ToArray();
}
///
/// 执行buff
///
///
public void DoBuff(int buffID)
{
BuffManager.Instance.DoBuff(Actor,buffID);
}
}
[Serializable]
public class BuffData
{
///
/// 缓存栈
///
private static Stack poolCache = new Stack();
///
/// BuffData下一个ID
///
public static int buffIndex { get; private set; }
///
/// ID
///
public int buffDataID;
///
/// 配置表ID
///
public int BuffID;
///
/// buff类型
///
public BuffType BuffType;
///
/// 叠加类型
///
public BuffOverlap BuffOverlap = BuffOverlap.StackedLayer;
///
/// 执行次数
///
public BuffCalculateType BuffCalculate = BuffCalculateType.Loop;
///
/// 关闭类型
///
public BuffShutDownType BuffShutDownType = BuffShutDownType.All;
///
/// 最大限制
///
public int MaxLimit;
///
/// 当前数据
///
[SerializeField]
private int _Limit;
public int GetLimit { get { return _Limit; } }
///
/// 执行时间
///
[SerializeField]
private float PersistentTime;
public float GetPersistentTime { get { return PersistentTime; } }
///
/// 当前时间
///
[SerializeField]
private float _CurTime;
///
/// 事件参数
///
public object Data;
///
/// 调用频率
///
public float CallFrequency { get; set; }
///
/// 当前频率
///
private float _curCallFrequency { get; set; }
///
/// 执行次数
///
[SerializeField]
private int index = 0;
///
/// 根据 CallFrequency 间隔 调用 结束时会调用一次 会传递 Data数据
///
public Action<object> OnCallBackParam;
///
/// ///
/// 根据 CallFrequency 间隔 调用 结束时会调用一次 会传递 Data数据 int 次数
///
///
public Action<object, int> OnCallBackParamIndex;
///
/// 根据 CallFrequency 间隔 调用 结束时会调用一次
///
public Action OnCallBack;
///
/// 根据 CallFrequency 间隔 调用 结束时会调用一次 int 次数
///
public Action<int> OnCallBackIndex;
///
/// 当改变时间
///
public Action OnChagneTime;
///
/// 当添加层
///
public Action OnAddLayer;
///
/// 当删除层
///
public Action OnSubLayer;
///
/// 开始调用
///
public Action OnStart;
///
/// 结束调用
///
public Action OnFinsh;
[SerializeField]
private bool _isFinsh;
///
/// 构造方法
///
private BuffData() {
buffDataID = buffIndex++;
CallFrequency = 1;
PersistentTime = 0;
}
private BuffData(float persistentTime,Action onCallBack)
{
PersistentTime = persistentTime;
OnCallBack = onCallBack;
buffDataID = buffIndex++;
}
///
/// 重置时间
///
public void ResterTime()
{
_CurTime = 0;
}
///
/// 修改 时间
///
///
public void ChangePersistentTime(float time)
{
PersistentTime = time;
if (PersistentTime >= MaxLimit)
PersistentTime = MaxLimit;
if (OnChagneTime != null)
OnChagneTime(this);
}
///
/// 加一层
///
public void AddLayer()
{
_Limit++;
_CurTime = 0;
if (_Limit > MaxLimit)
{
_Limit = MaxLimit;
return;
}
if (OnAddLayer != null)
OnAddLayer(this);
}
///
/// 减一层
///
public void SubLayer()
{
_Limit--;
if (OnSubLayer != null)
OnSubLayer(this);
}
///
/// 开始Buff
///
public void StartBuff()
{
//ChangeLimit(MaxLimit);
_isFinsh = false;
if (OnStart != null)
OnStart();
}
///
/// 执行中
///
///
public void OnTick(float delayTime)
{
_CurTime += delayTime;
//判断时间是否结束
if (_CurTime >= PersistentTime)
{
///调用事件
CallBack();
//判断结束类型 为层方式
if (BuffShutDownType == BuffShutDownType.Layer)
{
SubLayer();
//判读层数小于1 则结束
if (_Limit <= 0)
{
_isFinsh = true;
return;
}
//重置时间
_curCallFrequency = 0;
_CurTime = 0;
return;
}
_isFinsh = true;
return;
}
//如果是按频率调用
if (CallFrequency > 0)
{
_curCallFrequency += delayTime;
if (_curCallFrequency >= CallFrequency)
{
_curCallFrequency = 0;
CallBack();
}
return;
}
///调用回调
CallBack();
}
///
/// 获取当前执行时间
///
public float GetCurTime
{
get { return _CurTime; }
}
///
/// 是否结束
///
public bool IsFinsh
{
get { return _isFinsh; }
}
///
/// 调用回调
///
private void CallBack()
{
//次数增加
index++;
//判断buff执行次数
if (BuffCalculate == BuffCalculateType.Once)
{
if (index > 1) { index = 2; return; }
}
if (OnCallBack != null)
OnCallBack();
if (OnCallBackIndex != null)
OnCallBackIndex(index);
if (OnCallBackParam != null)
OnCallBackParam(Data);
if (OnCallBackParamIndex != null)
OnCallBackParamIndex(Data, index);
}
///
/// 关闭buff
///
public void CloseBuff()
{
if (OnFinsh != null)
OnFinsh();
Clear();
}
public void Clear()
{
_Limit = 0;
BuffID = -1;
index = 0;
PersistentTime = 0;
_CurTime = 0;
Data = null;
CallFrequency = 0;
_curCallFrequency = 0;
OnCallBackParam = null;
OnCallBack = null;
OnStart = null;
OnFinsh = null;
_isFinsh = false;
Push(this);
}
///
/// 创建BuffData
///
///
public static BuffData Create()
{
if (poolCache.Count < 1)
return new BuffData();
BuffData buffData = poolCache.Pop();
return buffData;
}
public static BuffData Create(BuffBase buffBase,Action onCallBack)
{
return Create(buffBase,onCallBack,null,null);
}
public static BuffData Create(BuffBase buffBase, Action onCallBack,Action addLayerAcion,Action subLayerAction)
{
BuffData db = Pop();
db.BuffCalculate = buffBase.BuffCalculate;
db.BuffID = buffBase.BuffID;
db.CallFrequency = buffBase.CallFrequency;
db.PersistentTime = buffBase.Time;
db.BuffOverlap = buffBase.BuffOverlap;
db.BuffShutDownType = buffBase.BuffShutDownType;
db.BuffType = buffBase.BuffType;
db.MaxLimit = buffBase.MaxLimit;
db.OnCallBack = onCallBack;
db.OnAddLayer = addLayerAcion;
db.OnSubLayer = subLayerAction;
db._Limit = 1;
return db;
}
///
/// 弹出
///
///
private static BuffData Pop()
{
if (poolCache.Count < 1)
{
BuffData bd = new BuffData();
return bd;
}
BuffData buffData = poolCache.Pop();
return buffData;
}
///
/// 压入
///
///
private static void Push(BuffData buffData)
{
poolCache.Push(buffData);
}
}
OK,到此,大部分功能都做完了,当然还有很多需要完善的部分,不过这毕竟是个人作品,没动力啊。buff系统大概流程就是如此了,在上述提供的列子中还有浮空的Buff等等,大家就需要凭才华进行制作了。
这样我们已经完成,UI系统,关卡系统,Buff系统,剧情系统,寻路系统,还有配置表。等明年回来我在完善一下任务系统,指引系统,技能系统,Ai系统和状态机,这样一个游戏大部分功能就都已经实现了。