正常的buff系统都不会写,现在要用DOTS写,更难受了
言归正传,想实现buff系统首先分析一下buff的定义,这样才能知道它需要实现什么功能。
我对buff的定义就是,一个有生命周期、能触发事件的标记
这里可以将一次攻击简单理解成 “给敌人上了一个buff,这个buff的效果是:在OnStart的时候对自身造成x点伤害,持续0秒”
中毒的话,则是“给敌人上了一个buff,这个buff的效果是:持续10秒,每秒造成10点伤害” -,- 这是一个比较正常的buff了
再比如减速buff:在OnStart时减少x点移速,OnEnd时恢复x点移速,持续10秒
这样我们可以发现,一个buff,他会有生命周期函数,能够触发一些事件,并且它是挂在在某个单位身上的,作为一个标记。
那么如何用DOTS实现这个最简单的效果呢?
在一开始,我还是用面向对象的思路去制作,想着继承、多态巴拉巴拉的,但最后跑出来的结果却不尽人意,3000左右单位的时候就已经跌破10帧了,这显然是和使用DOTS的目的相违背了。
于是乎我花了一整天的时间研究,终于发现了问题所在:思维上的固化。
在解释之前,先说明一下正常buff系统需要的东西:
BuffManager : 每个角色身上挂一个,用来管理自身buff的添加、卸载、时间
BuffBase: 用来被BuffManager管理,身上有各种生命周期方法,例如 添加时、触发时、销毁时…等等
假设想要实现一个中毒buff,那么只需要继承BuffBase并实现它的生命周期方法就行了,例如在触发时扣除10点生命值什么的
就这么简单的东西,问题来了,如何使用DOTS实现?
之后又整理了一下,写了一个完全版:【DOTS】基于DOTS的BUFF系统实现 (完全版)
首先要明确观念:【所有的操作都是针对数据,所有的System都是对数据进行操作,所有的操作都是为了得到一个结果】。只有清楚了这一点,才能摆脱oop的思想。
明确观念后,我们再来拆分
首先先将BuffManager拆分,他一共负责几个模块的功能:
- 增删Buff
- 管理buff的生命周期
- 管理buff时间
解决:
1、增删的方案,创建专门用于删除和增加的System
[BurstCompile]
public partial struct BuffAddSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
new AddBuffJob()
{
}.ScheduleParallel();
}
#region 古希腊创世神
[BurstCompile]
public partial struct AddBuffJob : IJobEntity
{
[BurstCompile]
void Execute(DynamicBuffer<BuffData> buffDataBuffer,DynamicBuffer<ChangeBuffData> addBuffDataBuffer)
{
//遍历addBuffDataBuffer,判断条件并构建添加到buffDataBuffer中。
for (int i = 0; i < addBuffDataBuffer.Length; i++)
{
if (addBuffDataBuffer[i].ChangeBuffState != ChangeBuffState.Add)
{
continue;
}
//TODO: 临时先这么处理下,实际上还得判断层数啥的啊啊啊啊啊
var buffData = new BuffData(
addBuffDataBuffer[i].BuffType,
addBuffDataBuffer[i].OwnerID,
addBuffDataBuffer[i].BuffValue,
addBuffDataBuffer[i].StartTime,
addBuffDataBuffer[i].StartTime + addBuffDataBuffer[i].Duration
);
var canAdd = true;
for (int j = 0; j < buffDataBuffer.Length; j++)
{
if (buffDataBuffer[j].BuffType == buffData.BuffType)
{
//说明是一样的
//需要判断是否叠加层数
//需要刷新时间啥的
//临时先都叠加了
var tmpBuffData = buffDataBuffer[j];
tmpBuffData.BuffCount += buffData.BuffCount;
buffDataBuffer[j] = tmpBuffData;
canAdd = false;
}
}
if (canAdd)
{
buffDataBuffer.Add(buffData);
}
}
//加完之后,把他清空
addBuffDataBuffer.Clear();
}
}
#endregion
}
2、创建buffFlag,用于标记生命周期触发情况,用数据改变来通知该执行生命周期逻辑了
public struct BuffFlag
{
/// <summary>
/// 是否存在
/// </summary>
public bool IsValid;
/// <summary>
/// 添加 从无到有才触发
/// </summary>
public bool IsNewAdd;
/// <summary>
/// 新增一层 已经有了,又增加了才触发(不会同时与IsNewAdd一起触发)
/// 可能会增加很多层,这里就改成int了,如果增加了,就不为0
/// </summary>
public int IsAdded;
/// <summary>
/// 可能会减少很多层,这里就改成int了,如果减少了,就不为0
/// </summary>
public int IsReduce; //已处理
/// <summary>
/// 是否触发 已处理
/// </summary>
public bool IsTrigger;
/// <summary>
/// 是否需要移除了 已处理
/// </summary>
public bool IsRemove;
}
3、单独写一个System去管理时间:
/// <summary>
/// 只负责对时间做处理 古希腊掌管时间的神
/// </summary>
[BurstCompile]
public partial struct BuffTimeSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// 先处理时间
new UpdateTimeJob()
{
elapsedTime = SystemAPI.Time.ElapsedTime,
deltaTime = SystemAPI.Time.DeltaTime,
}.ScheduleParallel();
}
#region 古希腊掌管时间的神
[BurstCompile]
public partial struct UpdateTimeJob : IJobEntity
{
public double elapsedTime;
public double deltaTime;
[BurstCompile]
void Execute(DynamicBuffer<BuffData> buffDataBuffer,Entity owner)
{
//计时器逻辑
for (int i = 0; i < buffDataBuffer.Length; i++)
{
var buffData = buffDataBuffer[i];
var bt = buffData.BuffTime;
var bf = buffData.BuffFlag;
if (buffData.BuffTime.TriggerTime > 0 && buffData.BuffTime.TriggerTimer > 0)
{
bt.TriggerTimer -= deltaTime;
buffData.BuffTime = bt;
buffDataBuffer[i] = buffData;
continue;
}
bf.IsTrigger = true; //如果没有间隔时间,则会一直触发哦。
bt.TriggerTimer = bt.TriggerTime;
// 结束时间为负,表示无限时间,不做减少
if (bt.EndTime > 0)
{
//处理减少一层的逻辑:
//如果不是一次性全减掉,则减少一层
if (elapsedTime > bt.EndTime) //如果当前时间大于结束时间,则表示buff结束,需要移除层数
{
bf.IsRemove = buffData.RemoveAllCountOnRemove;
if (!buffData.RemoveAllCountOnRemove) //减少一层
{
buffData.BuffCount--;
bf.IsReduce = 1;
if (buffData.BuffCount == 0)
{
bf.IsRemove = true;
}
}
}
}
buffData.BuffTime = bt;
buffData.BuffFlag = bf;
buffDataBuffer[i] = buffData;
}
}
}
#endregion
}
最后的结果对比:
之前:
之后: