今天来考虑一下,想要实现一个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
{/// <summary>/// BuffID/// </summary>public int BuffID;/// <summary>/// Buff类型/// </summary>public BuffType BuffType;/// <summary>/// 执行此/// </summary>public BuffCalculateType BuffCalculate = BuffCalculateType.Loop;/// <summary>/// 叠加类型/// </summary>public BuffOverlap BuffOverlap = BuffOverlap.StackedLayer;/// <summary>/// 消除类型/// </summary>public BuffShutDownType BuffShutDownType = BuffShutDownType.All;/// <summary>/// 如果是堆叠层数,表示最大层数,如果是时间,表示最大时间/// </summary>public int MaxLimit = 0;/// <summary>/// 执行时间/// </summary>public float Time = 0;/// <summary>/// 间隔时间/// </summary>public float CallFrequency = 1;/// <summary>/// 执行数值 比如加血就是每次加多少/// </summary>public float Num;
}

接下来是上面使用的几个枚举类型

/// <summary>
/// buff类型
/// </summary>
public enum BuffType
{/// <summary>/// 恢复HP/// </summary>AddHp,/// <summary>/// 增加最大血量/// </summary>AddMaxHp,/// <summary>/// 减血/// </summary>SubHp,/// <summary>/// 减最大生命值/// </summary>SubMaxHp,/// <summary>/// 眩晕/// </summary>AddVertigo,/// <summary>/// 被击浮空/// </summary>AddFloated,/// <summary>/// 击退/// </summary>AddRepel,/// <summary>/// 冲刺/// </summary>AddSprint,/// <summary>/// 被击浮空/// </summary>AddDamageFloated,/// <summary>/// 添加忽略重力/// </summary>AddIsIgnoreGravity,}/// <summary>
/// 叠加类型
/// </summary>
public enum BuffOverlap
{None,/// <summary>/// 增加时间/// </summary>StackedTime,/// <summary>/// 堆叠层数/// </summary>StackedLayer,/// <summary>/// 重置时间/// </summary>ResterTime,
}/// <summary>
/// 关闭类型
/// </summary>
public enum BuffShutDownType
{/// <summary>/// 关闭所有/// </summary>All,/// <summary>/// 单层关闭/// </summary>Layer,
}/// <summary>
/// 执行类型
/// </summary>
public enum BuffCalculateType
{/// <summary>/// 一次/// </summary>Once,/// <summary>/// 每次/// </summary>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> buffBase = new List<BuffBase>();
}

因为我们还没有配置表,所以在检视面板中直接填写数据就行了

buff数据有了,我们还需要提供buff的执行方法吧,就是我使用这个buff后这个buff要做什么,是我们需要知道的。
这就需要我们在写一个类,实现buff 执行中的功能。
buff使用用到角色身上的,所以我们写一个脚本ActorBuff 挂载到角色身上。


这个类的最主要功能:
1.添加buff
2.执行buff
3.buff移除buff
我们先来看看实现:

public class ActorBuff : ActorCompent {[SerializeField]private List<BuffData> buffs = new List<BuffData>();public override void OnInit(){InitBuff();}void InitBuff(){}void Update(){}/// <summary>/// 执行buff/// </summary>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]);}}}/// <summary>/// 添加buff/// </summary>/// <param name="buffData"></param>public void AddBuff(BuffData buffData){if (!buffs.Contains(buffData)){buffs.Add(buffData);buffData.StartBuff();}}/// <summary>/// 移除buff/// </summary>/// <param name="buffDataID"></param>public void RemoveBuff(int buffDataID){BuffData bd = GetBuff(buffDataID);if(bd!=null)bd.CloseBuff();}/// <summary>/// 移除buff/// </summary>/// <param name="buffData"></param>public void RemoveBuff(BuffData buffData){if (buffs.Contains(buffData)){buffData.CloseBuff();}}/// <summary>/// 获取buff/// </summary>/// <param name="buffDataID"></param>/// <returns></returns>public BuffData GetBuff(int buffDataID){for (int i = 0; i < buffs.Count; i++){if (buffs[i].buffDataID == buffDataID)return buffs[i];}return null;}/// <summary>/// 获取buff/// </summary>/// <param name="buffBaseID"></param>/// <returns></returns>public BuffData GetBuffByBaseID(int buffBaseID){for (int i = 0; i < buffs.Count; i++){if (buffs[i].BuffID == buffBaseID)return buffs[i];}return null;}/// <summary>/// 获取buff/// </summary>/// <param name="buffType"></param>/// <returns></returns>public BuffData[] GetBuff(BuffType buffType){List<BuffData> buffdatas = new List<BuffData>();for (int i = 0; i < buffs.Count; i++){if (buffs[i].BuffType == buffType)buffdatas.Add(buffs[i]);}return buffdatas.ToArray();}/// <summary>/// 执行buff/// </summary>/// <param name="buffID"></param>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写入两个方法:

    /// <summary>/// 执行buff/// </summary>/// <param name="actor"></param>/// <param name="buffID"></param>public void DoBuff(Actor actor,int buffID){DoBuff(actor,GetBuffBase(buffID));}/// <summary>/// 执行buff/// </summary>/// <param name="actor"></param>/// <param name="buff"></param>public void DoBuff(Actor actor, BuffBase buff){}

功能先不实现,现在我们需要着重的来看一下BuffData类的实现,这个类是执行中的buff,主要的buff功能均在此类中实现。

写这个类的时候比较蛋疼的是当初写的不知道怎么想的把BuffBase中的属性在此类中基本上都写了一遍,其实只要把BuffBase引用过来就行了,不过要过年了我先不该了,你们看的时候知道这个问题就行了。

因为这个BuffData的创建频率应该比较高,因为添加buff的时候就需要创建一个,使用完了就销毁,所以为了避免buffdata的重建,我们需要将失效buffdata存起来,后面在需要的时候重置里面的数据,再次使用。

[Serializable]
public class BuffData
{/// <summary>/// 缓存栈/// </summary>private static Stack<BuffData> poolCache = new Stack<BuffData>();/// <summary>/// BuffData下一个ID/// </summary>public static int buffIndex { get; private set; }/// <summary>/// 构造方法/// </summary>private BuffData() {buffDataID = buffIndex++;}/// <summary>/// 创建BuffData/// </summary>/// <returns></returns>public static BuffData Create(){if (poolCache.Count < 1)return new BuffData();BuffData buffData = poolCache.Pop();return buffData;}/// <summary>/// 弹出/// </summary>/// <returns></returns>private static BuffData Pop(){if (poolCache.Count < 1){BuffData bd = new BuffData();return bd;}BuffData buffData = poolCache.Pop();return buffData;}/// <summary>/// 压入/// </summary>/// <param name="buffData"></param>private static void Push(BuffData buffData){poolCache.Push(buffData);}
}

这样,我们在创建BuffData的时候通过BuffData.Create()方法创建,它会先去poolCache缓存池中去查看有没有空闲的BuffData,如果有,就取出来使用,没有就进行创建。

按照逻辑上来说,万物有始有终,所以我们需要在BuffData中添加两个方法,即开始方法StartBuff()和结束方法CloseBuff()

    /// <summary>/// 开始Buff/// </summary>public void StartBuff(){}/// <summary>/// 关闭buff/// </summary>public void CloseBuff(){}

但是别忘了,我们还需要一个中间不断执行的方法OnTick(float delayTime)

 /// <summary>/// 执行中/// </summary>public void OnTick(float delayTime){}

这样,我们开始,执行中,结束方法都有了,感觉主题框架搭完了,我们得开始写实际功能了。
首先,我们需要先实现类的属性,另外,我们还想在buff执行时候发送一个事件,和结束的时候发送一个事件给我们,所以我们还需要定义几个事件

    /// <summary>/// ID/// </summary>public int buffDataID;/// <summary>/// 配置表ID/// </summary>public int BuffID;/// <summary>/// buff类型/// </summary>public BuffType BuffType;/// <summary>/// 叠加类型/// </summary>public BuffOverlap BuffOverlap = BuffOverlap.StackedLayer;/// <summary>/// 执行次数/// </summary>public BuffCalculateType BuffCalculate = BuffCalculateType.Loop;/// <summary>/// 关闭类型/// </summary>public BuffShutDownType BuffShutDownType = BuffShutDownType.All;/// <summary>/// 最大限制/// </summary>public int MaxLimit;/// <summary>/// 当前数据/// </summary>[SerializeField]private int _Limit;public int GetLimit { get { return _Limit; } }/// <summary>/// 执行时间/// </summary>[SerializeField]private float PersistentTime;public float GetPersistentTime { get { return PersistentTime; } }/// <summary>/// 当前时间/// </summary>[SerializeField]private float _CurTime;/// <summary>/// 调用频率/// </summary>public float CallFrequency { get; set; }/// <summary>/// 当前时间/// </summary>private float _curCallFrequency { get; set; }/// <summary>/// 执行次数/// </summary>[SerializeField]private int index = 0;/// <summary>/// 开始调用/// </summary>public Action OnStart;/// <summary>/// 结束调用/// </summary>public Action OnFinsh;

上面很多属性其实不需要重复的写,注意了直接引用BuffBase的实例即可,但是以下划线开头的属性,是需要在这个类中定义的,他们控制时间,当前数量等数据。
我们构造方法中最好重置一个部分属性的值,看起来向这样:

   /// <summary>/// 构造方法/// </summary>private BuffData() {buffDataID = buffIndex++;CallFrequency = 1;PersistentTime = 0;}

然后我们开始写开始方法:

    /// <summary>/// 开始Buff/// </summary>public void StartBuff(){//ChangeLimit(MaxLimit);_isFinsh = false;if (OnStart != null)OnStart();}
首先我们要确保_isFinsh = false,确保我们buff尚未结束,其次,就是发送事件了,通知我们buff要开始运行了。
其次,我们写结束方法:
/// <summary>
/// 关闭buff
/// </summary>
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又添加了以下几个事件:

    /// <summary>/// 根据 CallFrequency 间隔 调用 结束时会调用一次 会传递 Data数据/// </summary>public Action<object> OnCallBackParam;/// <summary>///   /// <summary>/// 根据 CallFrequency 间隔 调用 结束时会调用一次 会传递 Data数据 int 次数/// </summary>/// </summary>public Action<object, int> OnCallBackParamIndex;/// <summary>/// 根据 CallFrequency 间隔 调用 结束时会调用一次/// </summary>public Action OnCallBack;/// <summary>/// 根据 CallFrequency 间隔 调用 结束时会调用一次 int 次数/// </summary>public Action<int> OnCallBackIndex;/// <summary>/// 当改变时间/// </summary>public Action<BuffData> OnChagneTime;/// <summary>/// 当添加层/// </summary>public Action<BuffData> OnAddLayer;/// <summary>/// 当删除层/// </summary>public Action<BuffData> OnSubLayer;

加载上面OnFinsh 事件下就行了。

剩下的就是需要实现OnTick方法了:

    /// <summary>/// 执行中/// </summary>/// <param name="delayTime"></param>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();}/// <summary>/// 调用回调/// </summary>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);}/// <summary>/// 加一层/// </summary>public void AddLayer(){_Limit++;_CurTime = 0;if (_Limit > MaxLimit){_Limit = MaxLimit;return;}if (OnAddLayer != null)OnAddLayer(this);}/// <summary>/// 减一层/// </summary>public void SubLayer(){_Limit--;if (OnSubLayer != null)OnSubLayer(this);}/// <summary>/// 重置时间/// </summary>public void ResterTime(){_CurTime = 0;}/// <summary>/// 修改 时间/// </summary>/// <param name="time"></param>public void ChangePersistentTime(float time){PersistentTime = time;if (PersistentTime >= MaxLimit)PersistentTime = MaxLimit;if (OnChagneTime != null)OnChagneTime(this);}

主要流程控制是在Tick方法中执行的。而主要的逻辑操作是在提供的几个事件中进行编辑。这个先不急,我们再在BuffData中添加几个方法:

    /// <summary>/// 获取当前执行时间/// </summary>public float GetCurTime{get { return _CurTime; }}/// <summary>/// 是否结束/// </summary>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<BuffData> addLayerAcion,Action<BuffData> 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),如果忘记了可以翻上去看看。然后我们只实现了方法,还没实现功能,接下来我们就需要实现次数功能了。
它看起像这样:

 /// <summary>/// 执行buff/// </summary>/// <param name="actor"></param>/// <param name="buff"></param>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);}/// <summary>/// 玩家是否已经有此buff/// </summary>/// <param name="actor"></param>/// <param name="buff"></param>/// <returns></returns>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;}/// <summary>/// 获取配置数据/// </summary>/// <param name="buffID"></param>/// <returns></returns>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.AddHpcase分支中的逻辑:
首先,我们需要判断一下玩家身上是否存在该类型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<BuffData> addLayerAcion,Action<BuffData> subLayerAction) 方法。
相比上面而言,多了addLayerAcionsubLayerAction两个事件,根据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,但是使用的回调是OnStartOnFinsh
在回调用实现了忽略重力的方法。
在这里,配置表中的数据基本上没什么作用,除了一个时间,不过我这里没有用配置表里的时间,我用的动画时间。

最后一个,位移的例子:
比如我有个一个技能,我想让英雄使用该技能时进行冲刺,就是一个直线位移,通过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> buffBase = new List<BuffBase>();#region GUI[SerializeField]private Actor TestActor;#endregionvoid 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<Actor>();}if (TestActor != null)DoBuff(TestActor, int.Parse(action.Param[0]));elseGUI.Label(new Rect(10, 105, 200, 10), "测试Actor 为 null ,请检查Actor Name是否正确!");}GUI.DragWindow();}, action.Id+ " - Buff Test|BuffManager");},"0","Player");}/// <summary>/// 执行buff/// </summary>/// <param name="actor"></param>/// <param name="buffID"></param>public void DoBuff(Actor actor,int buffID){DoBuff(actor,GetBuffBase(buffID));}/// <summary>/// 执行buff/// </summary>/// <param name="actor"></param>/// <param name="buff"></param>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);}/// <summary>/// 玩家是否已经有此buff/// </summary>/// <param name="actor"></param>/// <param name="buff"></param>/// <returns></returns>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;}/// <summary>/// 获取配置数据/// </summary>/// <param name="buffID"></param>/// <returns></returns>public BuffBase GetBuffBase(int buffID){for (int i = 0; i < buffBase.Count; i++){if (buffBase[i].BuffID == buffID)return buffBase[i];}return null;}
}/// <summary>
/// buff类型
/// </summary>
public enum BuffType
{/// <summary>/// 恢复HP/// </summary>AddHp,/// <summary>/// 增加最大血量/// </summary>AddMaxHp,/// <summary>/// 减血/// </summary>SubHp,/// <summary>/// 减最大生命值/// </summary>SubMaxHp,/// <summary>/// 眩晕/// </summary>AddVertigo,/// <summary>/// 被击浮空/// </summary>AddFloated,/// <summary>/// 击退/// </summary>AddRepel,/// <summary>/// 冲刺/// </summary>AddSprint,/// <summary>/// 被击浮空/// </summary>AddDamageFloated,/// <summary>/// 添加忽略重力/// </summary>AddIsIgnoreGravity,}/// <summary>
/// 叠加类型
/// </summary>
public enum BuffOverlap
{None,/// <summary>/// 增加时间/// </summary>StackedTime,/// <summary>/// 堆叠层数/// </summary>StackedLayer,/// <summary>/// 重置时间/// </summary>ResterTime,
}/// <summary>
/// 关闭类型
/// </summary>
public enum BuffShutDownType
{/// <summary>/// 关闭所有/// </summary>All,/// <summary>/// 单层关闭/// </summary>Layer,
}/// <summary>
/// 执行类型
/// </summary>
public enum BuffCalculateType
{/// <summary>/// 一次/// </summary>Once,/// <summary>/// 每次/// </summary>Loop,
}[System.Serializable]
public class BuffBase
{/// <summary>/// BuffID/// </summary>public int BuffID;/// <summary>/// Buff类型/// </summary>public BuffType BuffType;/// <summary>/// 执行此/// </summary>public BuffCalculateType BuffCalculate = BuffCalculateType.Loop;/// <summary>/// 叠加类型/// </summary>public BuffOverlap BuffOverlap = BuffOverlap.StackedLayer;/// <summary>/// 消除类型/// </summary>public BuffShutDownType BuffShutDownType = BuffShutDownType.All;/// <summary>/// 如果是堆叠层数,表示最大层数,如果是时间,表示最大时间/// </summary>public int MaxLimit = 0;/// <summary>/// 执行时间/// </summary>public float Time = 0;/// <summary>/// 间隔时间/// </summary>public float CallFrequency = 1;/// <summary>/// 执行数值 比如加血就是每次加多少/// </summary>public float Num;
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;public class ActorBuff : ActorCompent {[SerializeField]private List<BuffData> buffs = new List<BuffData>();public override void OnInit(){InitBuff();}void InitBuff(){}void Update(){}/// <summary>/// 执行buff/// </summary>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]);}}}/// <summary>/// 添加buff/// </summary>/// <param name="buffData"></param>public void AddBuff(BuffData buffData){if (!buffs.Contains(buffData)){buffs.Add(buffData);buffData.StartBuff();}}/// <summary>/// 移除buff/// </summary>/// <param name="buffDataID"></param>public void RemoveBuff(int buffDataID){BuffData bd = GetBuff(buffDataID);if(bd!=null)bd.CloseBuff();}/// <summary>/// 移除buff/// </summary>/// <param name="buffData"></param>public void RemoveBuff(BuffData buffData){if (buffs.Contains(buffData)){buffData.CloseBuff();}}/// <summary>/// 获取buff/// </summary>/// <param name="buffDataID"></param>/// <returns></returns>public BuffData GetBuff(int buffDataID){for (int i = 0; i < buffs.Count; i++){if (buffs[i].buffDataID == buffDataID)return buffs[i];}return null;}/// <summary>/// 获取buff/// </summary>/// <param name="buffBaseID"></param>/// <returns></returns>public BuffData GetBuffByBaseID(int buffBaseID){for (int i = 0; i < buffs.Count; i++){if (buffs[i].BuffID == buffBaseID)return buffs[i];}return null;}/// <summary>/// 获取buff/// </summary>/// <param name="buffType"></param>/// <returns></returns>public BuffData[] GetBuff(BuffType buffType){List<BuffData> buffdatas = new List<BuffData>();for (int i = 0; i < buffs.Count; i++){if (buffs[i].BuffType == buffType)buffdatas.Add(buffs[i]);}return buffdatas.ToArray();}/// <summary>/// 执行buff/// </summary>/// <param name="buffID"></param>public void DoBuff(int buffID){BuffManager.Instance.DoBuff(Actor,buffID);}}[Serializable]
public class BuffData
{/// <summary>/// 缓存栈/// </summary>private static Stack<BuffData> poolCache = new Stack<BuffData>();/// <summary>/// BuffData下一个ID/// </summary>public static int buffIndex { get; private set; }/// <summary>/// ID/// </summary>public int buffDataID;/// <summary>/// 配置表ID/// </summary>public int BuffID;/// <summary>/// buff类型/// </summary>public BuffType BuffType;/// <summary>/// 叠加类型/// </summary>public BuffOverlap BuffOverlap = BuffOverlap.StackedLayer;/// <summary>/// 执行次数/// </summary>public BuffCalculateType BuffCalculate = BuffCalculateType.Loop;/// <summary>/// 关闭类型/// </summary>public BuffShutDownType BuffShutDownType = BuffShutDownType.All;/// <summary>/// 最大限制/// </summary>public int MaxLimit;/// <summary>/// 当前数据/// </summary>[SerializeField]private int _Limit;public int GetLimit { get { return _Limit; } }/// <summary>/// 执行时间/// </summary>[SerializeField]private float PersistentTime;public float GetPersistentTime { get { return PersistentTime; } }/// <summary>/// 当前时间/// </summary>[SerializeField]private float _CurTime;/// <summary>/// 事件参数/// </summary>public object Data;/// <summary>/// 调用频率/// </summary>public float CallFrequency { get; set; }/// <summary>/// 当前频率/// </summary>private float _curCallFrequency { get; set; }/// <summary>/// 执行次数/// </summary>[SerializeField]private int index = 0;/// <summary>/// 根据 CallFrequency 间隔 调用 结束时会调用一次 会传递 Data数据/// </summary>public Action<object> OnCallBackParam;/// <summary>///   /// <summary>/// 根据 CallFrequency 间隔 调用 结束时会调用一次 会传递 Data数据 int 次数/// </summary>/// </summary>public Action<object, int> OnCallBackParamIndex;/// <summary>/// 根据 CallFrequency 间隔 调用 结束时会调用一次/// </summary>public Action OnCallBack;/// <summary>/// 根据 CallFrequency 间隔 调用 结束时会调用一次 int 次数/// </summary>public Action<int> OnCallBackIndex;/// <summary>/// 当改变时间/// </summary>public Action<BuffData> OnChagneTime;/// <summary>/// 当添加层/// </summary>public Action<BuffData> OnAddLayer;/// <summary>/// 当删除层/// </summary>public Action<BuffData> OnSubLayer;/// <summary>/// 开始调用/// </summary>public Action OnStart;/// <summary>/// 结束调用/// </summary>public Action OnFinsh;[SerializeField]private bool _isFinsh;/// <summary>/// 构造方法/// </summary>private BuffData() {buffDataID = buffIndex++;CallFrequency = 1;PersistentTime = 0;}private BuffData(float persistentTime,Action onCallBack){PersistentTime = persistentTime;OnCallBack = onCallBack;buffDataID = buffIndex++;}/// <summary>/// 重置时间/// </summary>public void ResterTime(){_CurTime = 0;}/// <summary>/// 修改 时间/// </summary>/// <param name="time"></param>public void ChangePersistentTime(float time){PersistentTime = time;if (PersistentTime >= MaxLimit)PersistentTime = MaxLimit;if (OnChagneTime != null)OnChagneTime(this);}/// <summary>/// 加一层/// </summary>public void AddLayer(){_Limit++;_CurTime = 0;if (_Limit > MaxLimit){_Limit = MaxLimit;return;}if (OnAddLayer != null)OnAddLayer(this);}/// <summary>/// 减一层/// </summary>public void SubLayer(){_Limit--;if (OnSubLayer != null)OnSubLayer(this);}/// <summary>/// 开始Buff/// </summary>public void StartBuff(){//ChangeLimit(MaxLimit);_isFinsh = false;if (OnStart != null)OnStart();}/// <summary>/// 执行中/// </summary>/// <param name="delayTime"></param>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();}/// <summary>/// 获取当前执行时间/// </summary>public float GetCurTime{get { return _CurTime; }}/// <summary>/// 是否结束/// </summary>public bool IsFinsh{get { return _isFinsh; }}/// <summary>/// 调用回调/// </summary>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);}/// <summary>/// 关闭buff/// </summary>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);}/// <summary>/// 创建BuffData/// </summary>/// <returns></returns>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<BuffData> addLayerAcion,Action<BuffData> 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;}/// <summary>/// 弹出/// </summary>/// <returns></returns>private static BuffData Pop(){if (poolCache.Count < 1){BuffData bd = new BuffData();return bd;}BuffData buffData = poolCache.Pop();return buffData;}/// <summary>/// 压入/// </summary>/// <param name="buffData"></param>private static void Push(BuffData buffData){poolCache.Push(buffData);}}

OK,到此,大部分功能都做完了,当然还有很多需要完善的部分,不过这毕竟是个人作品,没动力啊。buff系统大概流程就是如此了,在上述提供的列子中还有浮空的Buff等等,大家就需要凭才华进行制作了。

这样我们已经完成,UI系统,关卡系统,Buff系统,剧情系统,寻路系统,还有配置表。等明年回来我在完善一下任务系统,指引系统,技能系统,Ai系统和状态机,这样一个游戏大部分功能就都已经实现了。

预热技能系统:

Unity3D 实现简单的Buff系统相关推荐

  1. 简单的技能Buff系统

    在本教程中,我将向您展示如何使用可脚本化对象为Unity创建灵活的buff系统.我们将使用可编写脚本的对象作为快速创建和管理buff的方法,而不必使用外部数据类型(如xml或txt文件).这可以让我们 ...

  2. Unity3D的简单商店、背包与装备系统

    物品元素可以说是所有RPG的必备系统,毕竟,如果没有物品系统就没有消耗,没有消耗哪能骗玩家充钱呢?最简单的物品系统应该是所谓的商店.背包加装备的结构,而这个Demo则主要基于UGUI实现一个简单的上述 ...

  3. Unity3D简单的UI系统

    Unity3d的ugui如果用得不好,非常的影响性能,可能很多人都不知道,其实ugui是基于网格模型渲染的,一个ugui组件就是一个Mesh,ui组件的Material为空,其实他是用了默认的Mate ...

  4. Unity3D的简单商店、背包与装备系统(UGUI)

    Store And Equipment System In Unity3D Introduction 物品元素可以说是所有RPG的必备系统,毕竟,如果没有物品系统就没有消耗,没有消耗哪能骗玩家充钱呢? ...

  5. 【Unity3d】 教会你如何做一个简单的电梯系统(升降平台)

    博主第一次写博客,语言略俗,有不足之处还请指正! 由于自己还处在unity小白阶段,受2d升降平台的影响(后续我也会上传关于2d升降平台的文章),突发奇想如何用3d做一个电梯系统,查阅网上资料后,发现 ...

  6. 浅谈魔兽世界的BUFF系统和阵营系统

    游戏中的行为所产生的效果在设计上是一种看似简单,但是难以做的很抽象并且有很强的扩展性的系统.但魔兽世界创立了BUFF系统来统一这个设计概念. 这一概念可以很简单的把一些系统连接起来,例如:通道魔法,只 ...

  7. 基于Unity3D的智能家居仿真系统——户型绘制基本功能介绍

    本篇我们来介绍一下左侧工具栏中基本绘制的应用. 一.墙体绘制 直墙 & 矩形墙 绘制墙体时,可以看到上方的工具栏中对墙体进行参数的设定. 弧形墙 在建筑版的户型工具中,增加了弧形墙的绘制功能, ...

  8. buff系统 游戏中_如何实现一个强大的MMO技能系统——BUFF

    前言 Buff模块可以说是技能中最核心又最复杂的系统了.一个优秀的Buff系统能够让策划的创意得到最大限度的发挥,大幅增强游戏的战斗深度和可玩性,并且同时也能让开发者轻易的扩展维护,支持更多的效果和功 ...

  9. Unity--简单Buff系统

    2017年2月25日,广州前2天开始降温,久违的感受到寒冷的天气了.也在这个时候,确定了新的工作,希望以后能有更好的发展,对得起家人和朋友和恋人.在这里也感谢一下上家公司带我的组长,学到了很多东西,希 ...

最新文章

  1. opencv java 灰度_如何使用Java将图像转换为灰度?
  2. bootstrap跟vue冲突吗_知道微服务,但你知道微前端吗?
  3. PyTorch攻势凶猛,程序员正在抛弃TensorFlow?
  4. 数据可视化:Matplotlib vs ggplot2
  5. Ferris教程学习笔记:js示例2.13 简易JS年历
  6. 想学测试如何入门和学习软件测试?今天我就好好给你唠唠
  7. PostgreSQL 与 MySQL 相比,优势何在?[转]
  8. Bugku—web题解
  9. socket通信(C#)
  10. Python 标准库 —— uuid(生成唯一 ID)
  11. redis 安装使用
  12. 购物商城框架java_基于jsp的购物商城-JavaEE实现购物商城 - java项目源码
  13. opencv 鱼眼矫正
  14. 遇到一个把.o文件strip后出现的奇怪问题
  15. 天正坐标标注显示不全_广联达导入CAD图纸不显示怎么办?
  16. 如何查html病毒svchost.exe,小编教你在Win7系统中检查svchost.exe进程是否为病毒的方法步骤...
  17. ArcGIS水文分析实战教程(3)DEM数据准备
  18. html5调查问卷的计分实验,问卷调查实验
  19. python图片压缩算法_Python实现奇异值分解(SVD)压缩图片
  20. 数据结构分类及八种常见数据结构

热门文章

  1. 文本内容过多,显示省略号...,鼠标悬浮显示全部
  2. Speedoffice(word)文档如何插入表格
  3. 如何快速剪辑每段视频,删除片尾,旋转画面
  4. 使用PS制作噪质感图画
  5. 金蝶EAS登录获取用户相关信息失败!
  6. JavaScript基础一些重要知识-JavaWeb篇
  7. Elasticsearch 学习
  8. 关闭出装显示无服务器,王者荣耀:出装问题很多人都不懂,完全忽略几个细节问题...
  9. Houdini:简单杯子建模+贴图
  10. java 多线程遍历list_如何线程安全地遍历List:Vector、CopyOnWriteArrayList