MDD

Buff系统是一个万能系统,它最全能,也最无能。

概念

Buff这个词的起源似乎已经无从考证,貌似在电子游戏出现之前的桌游中就已存在。

现今约定俗成的buff debuff代表着很多游戏内的增益减益效果,已经是游戏制作中不可或缺的概念之一。

我愿将buff描述为一种外来的力量,他不是个体本身所拥有的,更像是一种从天而降附加在个体上的力量。

在MDDSkillEngine中也是以“buff是个体的外来力量”为基调,对buff系统进行实现。

buff系统的全能性

作为一个天降之物,一个buff内有什么皆有可能。

在程序实现中亦是如此,如果你愿意抽风,在一个buff中实现一个虚拟机跑一门自创语言也是可行的

buff系统的无能

越全能,越意味着什么都没有。

所以在程序实现中,buff本身空无一物如同一张白纸。

如果有着其他健壮的系统供buff系统调用,那么buff系统将很容易实现各种功能。

如果没有则一切都约等于从零开始。

MDDSkillEngine对buff系统全能性的刻意削弱

因为buff系统的不可或缺和其全能性,所以不可避免的会有”技能就是buff,buff就是技能“这种问题。

当然这个问题本身也是构建技能系统的方案之一。

但是我并不想这么做,所以通过概念上的约束来规避这个问题。

具体约束如下图:

MDDSkillEngine中技能和buff的关系

即buff可以是一个技能的组成部分,但buff绝对不能完全代表一个技能。

BUFF系统的程序结构设计

BuffSystem Runtime

基础架构简图

buffsystem基础框架
buff系统与个体的简易关系

单个buff基类


namespace MDDGameFramework
{
    public abstract class BuffBase : IReference
    {
        private object m_Target;
        private object m_From;

        public BuffDatabase buffData;

        /// <summary>
        /// buff目标
        /// </summary>
        public object Target
        {
            get { return m_Target; }
            protected set { m_Target = value; }
        }

        /// <summary>
        /// buff释放者
        /// </summary>
        public object From
        {
            get { return m_From; }
            protected set { m_From = value; }
        }


        /// <summary>
        /// buff初始化
        /// </summary>
        /// <param name="buffSystem">归属的buff系统</param>
        /// <param name="Target">buff的目标</param>
        /// <param name="From">释放的buff的实体</param>
        /// <param name="buffDatabase">basedata</param>
        /// <param name="userData">用户自定义数据</param>
        public virtual void OnInit(IBuffSystem buffSystem, object Target, object From, BuffDatabase buffDatabase = null, object userData = null)
        {
            m_Target = Target;
            m_From = From;
            buffData = buffDatabase;
        }

        /// <summary>
        /// buff执行
        /// </summary>
        /// <param name="buffSytem"></param>
        public abstract void OnExecute(IBuffSystem buffSytem);

        /// <summary>
        /// buff轮询
        /// </summary>
        /// <param name="buffSystem"></param>
        /// <param name="elapseSeconds"></param>
        /// <param name="realElapseSeconds"></param>
        public virtual void OnUpdate(IBuffSystem buffSystem, float elapseSeconds, float realElapseSeconds)
        {
            buffData.PassDuration += elapseSeconds;
            buffData.AccumulateDuration += elapseSeconds;

            if (buffData.Duration == -1)
            {
                //持续时间为-1则默认为永久buff
            }
            else if (buffData.Duration <= buffData.PassDuration)
            {
                BuffSystem system = (BuffSystem)buffSystem;
                system.Finish(this);
            }
        }

        /// <summary>
        /// buff物理轮询轮询
        /// </summary>
        /// <param name="buffSystem"></param>
        /// <param name="elapseSeconds"></param>
        /// <param name="realElapseSeconds"></param>
        public virtual void OnFixedUpdate(IBuffSystem buffSystem, float elapseSeconds, float realElapseSeconds)
        {
           
        }

        /// <summary>
        /// buff结束
        /// </summary>
        /// <param name="buffSystem"></param>
        public virtual void OnFininsh(IBuffSystem buffSystem)
        {
            buffData.PassDuration = 0f;
        }

        /// <summary>
        /// buff刷新
        /// </summary>
        /// <param name="buffSystem"></param>
        public virtual void OnRefresh(IBuffSystem buffSystem) { }



        public virtual void Clear()
        {
            ReferencePool.Release(buffData);
            buffData = null;
        }
    }
}


 

buff数据基类


namespace MDDGameFramework
{
    /// <summary>
    /// buff通用数据
    /// </summary>
    public abstract class BuffDatabase :IReference
    {
        private int m_Id;
        private int m_Level;
        private string m_Name;
        private bool m_CanOverlying;
        private float m_Duration;      
        private float m_PassDuration;
        private float m_accumulateDuration;

        /// <summary>
        /// buffID
        /// </summary>
        public int Id
        {
            get { return m_Id; }
        }

        public int UId
        {
            get { return GetHashCode(); }
        }

        /// <summary>
        /// buffname
        /// </summary>
        public string Name
        {
            get { return m_Name; }
            set { m_Name = value; }
        }

        /// <summary>
        /// buff等级
        /// </summary>
        public int Level
        {
            get { return m_Level; }
        }

        /// <summary>
        /// 持续时间
        /// 永久buff持续时间默认为-1
        /// </summary>
        public float Duration
        {
            get { return m_Duration; }
        }
        
        /// <summary>
        /// buff在单次持续时间内已经持续的时间
        /// </summary>
        public float PassDuration
        {
            get { return m_PassDuration; }
            set { m_PassDuration = value; }
        }


        /// <summary>
        /// 积累时间
        /// </summary>
        public float AccumulateDuration
        {
            get { return m_accumulateDuration; }
            set { m_accumulateDuration = value; }
        }

        /// <summary>
        /// 是否可叠加
        /// </summary>
        public bool CanOverlying
        {
            get { return m_CanOverlying; }
            set { m_CanOverlying = value;}
        }

        /// <summary>
        /// buff行进百分比
        /// 若是永久buff则直接返回百分之百
        /// 永久buff持续时间默认为-1
        /// </summary>
        public float DurationRadio
        {
            get
            {
                return m_Duration > 0 ? m_PassDuration / m_Duration : 1f;
            }
        }

        public virtual void Clear()
        {
            m_Id = 0;
            m_Level = 0;
            m_accumulateDuration = 0;
            m_Duration = 0;
            m_PassDuration = 0f;
        }

        public void Init(int id,string name,int level,float duration)
        {
            m_Id = id;
            m_Name = name;
            m_Level = level;
            m_Duration = duration;
        }
    }
}

buff生命周期展示流程图

buff生命周期

至此一个通用性极强的buff系统runtime构建完成

特化的业务逻辑实现继承buff数据基类以及buff基类具体实现即可

BuffSystem Editor

数据编辑规则不具有通用性

buff系统的数据构建规则很可能不具有通用性

因为不同游戏的buff业务需求或多或少都会有差异,甚至可能是大相径庭的两种规则

但是数据编辑方案还是有通用性的

Excel&NodeEditor

buff数据来源构建

在具体的游戏业务需求中buff和buff之间也许会有很强的关联性。

所以NodeEditor是一种非常好的方案去构建buff数据,具体可以参照肛少的用法

而Excel则是国内的游戏数据管理编辑大柱国,没什么好说的。

runtime中的临时数据当然也可能会是构建buff数据的关键组成部分,例如buff的效果强弱需要根据游戏中hero的实时属性进行调整。

MDDSkillEngine使用了Excel流程做了一个简易的数据流。

综上所述

对于BuffSystemEditor的部分 我们只需要根据游戏业务需求选好适合自己的数据编辑方案。

然后把编辑好的数据根据具体业务需求的规则注入buff中即可。

做法有无数种。

选择适合自己的就好。


记录历程,整理思路,共享知识,分享思维。