1. 程式人生 > >Unity3D 實現簡單的Buff系統

Unity3D 實現簡單的Buff系統

今天來考慮一下,想要實現一個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);