在上一篇文章中写的buff很初期,很多细节都没有考虑到,在之后的几天我把他们完善了一下
现在的buff系统支持:
1、能够通用的给Entity添加、移除buff
2、异步的方式实现生命周期
3、支持外部配置Buff初始属性,例如配置不同等级的效果影响等
4、支持通过owner与target的其他属性影响到增加的buff属性
例如:owner有个宝物,效果是给target附加毒时多加x层。
5、提供封装的拓展方法,可简单快速的使用添加、移除等方法
基础结构:
废话不多说,直接贴代码:
BuffData + BuffTableData: 这个很简单,就是定义了一下Buff的基础结构,其中包含了时间、生命周期标记。配置数据中提供了一些需要外部配置的属性。
using System;
using Unity.Collections;
using Unity.Entities;
namespace Core.Battle.Buff
{
/// <summary>
/// 说明:
/// 由于DOTS的特性,数据就保证绝对的数据
/// 生命周期的实现,改变为异步实现:当发生改变时,对BuffData的Flag做修改,System会一直迭代遍历BuffData的Flag,当发现
/// 改动后,会执行对应逻辑,然后再修改回去。
/// </summary>
public struct BuffData : IBufferElementData
{
/// <summary>
/// buff的类型
/// </summary>
public BuffType BuffType;
/// <summary>
/// 名称
/// </summary>
public FixedString64Bytes Name;
/// <summary>
/// 描述
/// </summary>
public FixedString64Bytes Info;
/// <summary>
/// 来源Entity ID
/// </summary>
public Entity Owner;
/// <summary>
/// 目标
/// </summary>
public Entity Target;
/// <summary>
/// buff等级
/// </summary>
public int BuffLevel;
/// <summary>
/// buff的层数
/// </summary>
public int BuffCount;
/// <summary>
/// buff的最大层数
/// </summary>
public int BuffMaxCount;
/// <summary>
/// 移除时是否移除所有层
/// </summary>
public bool RemoveAllCountOnRemove;
/// <summary>
/// 能否叠加
/// </summary>
public bool IsSuperposition;
/// <summary>
/// buff值
/// </summary>
public int BuffValue;
/// <summary>
/// buff生命周期标记
/// </summary>
public BuffFlag BuffFlag;
/// <summary>
/// buff的时间
/// </summary>
public BuffTime BuffTime;
/// <summary>
/// 为none就是默认的
/// </summary>
public bool IsDefault()
{
return BuffType == BuffType.None;
}
/// <summary>
/// 移除 --> 设置成默认即可
/// </summary>
public void SetDefault()
{
BuffType = BuffType.None;
}
public BuffData(BuffType buffType, Entity owner, Entity target,int buffCount, int buffLevel,double startTime,double duration,BuffTableData tableData)
{
BuffType = buffType;
Owner = owner;
Target = target;
BuffCount = buffCount;
BuffLevel = buffLevel;
BuffMaxCount = tableData.BuffMaxCount;
BuffValue = tableData.BuffValue;
RemoveAllCountOnRemove = tableData.RemoveAllCountOnRemove;
IsSuperposition = tableData.IsSuperposition;
Name = tableData.Name;
Info = tableData.Info;
BuffTime = new BuffTime
{
StartTime = startTime,
EndTime = startTime + duration + tableData.Duration,
Duration = tableData.Duration + duration,
CurDuration = tableData.Duration + duration,
TriggerTime = tableData.TriggerTime,
TriggerTimer = 0
};
BuffFlag = new BuffFlag
{
IsNewAdd = true
};
}
public void Add(BuffData otherBuff)
{
if (IsSuperposition)
{
BuffCount += otherBuff.BuffCount;
}
else
{
BuffCount = otherBuff.BuffCount;
}
Owner = otherBuff.Owner;
BuffLevel = otherBuff.BuffLevel;
BuffMaxCount = otherBuff.BuffMaxCount;
BuffValue = otherBuff.BuffValue;
BuffFlag.IsAdded = otherBuff.BuffCount;
BuffTime.Duration = otherBuff.BuffTime.Duration;
BuffTime.EndTime = BuffTime.StartTime + BuffTime.Duration;
BuffTime.TriggerTime = otherBuff.BuffTime.TriggerTime;
BuffTime.CurDuration = BuffTime.Duration;
}
/// <summary>
/// 移除:标记移除,在执行完isRemove标记后,需要手动调用SetDefault
/// </summary>
public void Remove()
{
BuffFlag.IsRemove = true;
BuffFlag.IsAdded = 0;
BuffFlag.IsNewAdd = false;
BuffFlag.IsReduce = 0;
BuffFlag.IsTrigger = false;
}
}
public struct BuffTime
{
/// <summary>
/// buff添加时的时间
/// </summary>
public double StartTime;
/// <summary>
/// 上次buff改变的时间
/// 可能是添加层数也可能是减少层数
/// </summary>
public double RefreshTime;
/// <summary>
/// -1:常驻不触发
/// buff触发时间
/// 例如伤害跳动时间之类的
/// </summary>
public double TriggerTime;
public double TriggerTimer;
/// <summary>
/// 预计buff结束的时间
/// </summary>
public double EndTime;
/// <summary>
/// buff的持续时间
/// </summary>
public double Duration;
public double CurDuration;
/// <summary>
/// 设置持续时间
/// </summary>
public void SetDuration(double newDuration)
{
Duration = newDuration;
CurDuration = newDuration;
}
public override string ToString()
{
return $"BuffTime [StartTime: {StartTime}, RefreshTime: {RefreshTime}, TriggerTime: {TriggerTime}, EndTime: {EndTime}, Duration: {Duration}]";
}
}
public struct BuffFlag
{
/// <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;
public override string ToString()
{
return $"BuffFlag [IsNewAdd: {IsNewAdd}, IsAdded: {IsAdded}, IsReduce: {IsReduce}, IsTrigger: {IsTrigger}, IsRemove: {IsRemove}]";
}
}
/// <summary>
/// Buff数据表数据
/// </summary>
[Serializable]
public struct BuffTableData : IBufferElementData
{
public FixedString64Bytes Name;
public FixedString64Bytes Info;
public BuffType BuffType;
public int Level;
public int BuffMaxCount;
public int BuffValue;
public bool RemoveAllCountOnRemove;
public bool IsSuperposition;
public double Duration;
public double TriggerTime;
public BuffTableData(int buffMaxCount, int buffValue,int level, bool removeAllCountOnRemove, bool isSuperposition, double duration, double triggerTime, BuffType buffType)
{
Name = "";
Info = "";
BuffType = buffType;
BuffMaxCount = buffMaxCount;
BuffValue = buffValue;
Level = level;
RemoveAllCountOnRemove = removeAllCountOnRemove;
IsSuperposition = isSuperposition;
Duration = duration;
TriggerTime = triggerTime;
}
}
}
外部BuffTableData的构建:可以写个拓展工具,用来编辑脚本资源,例如:
BuffType:定义了一下buff类型
namespace Core.Battle.Buff
{
public enum BuffType
{
None, // 无
Damage, // 直伤
AddDamageValue, // 直伤伤害增加
}
}
BuffTimeSystem:这个system里对buffData做处理,负责时间和标记的整理
using Unity.Burst;
using Unity.Entities;
namespace Core.Battle.Buff
{
/// <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)
{
for (int i = 0; i < buffDataBuffer.Length; i++)
{
var buffData = buffDataBuffer[i];
//为None,说明会在下一帧被移除,这里不用管它 isRemove = true 说明被移除了,也不继续触发,并且应该取消其其他的所有状态来着
if (buffData.IsDefault() || buffData.BuffFlag.IsRemove)
{
continue;
}
var bt = buffData.BuffTime;
var bf = buffData.BuffFlag;
//如果没有间隔时间,则不触发
if (buffData.BuffTime.TriggerTime > 0)
{
if (buffData.BuffTime.TriggerTimer > 0)
{
bt.TriggerTimer -= deltaTime;
}
else
{
bf.IsTrigger = true;
bt.TriggerTimer = bt.TriggerTime;
}
}
// 持续时间为负数表示无限时间
if (bt.Duration > 0f)
{
bt.CurDuration -= deltaTime;
if (bt.CurDuration <= 0)
{
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
}
}
BuffRemoveSystem:负责移除空buff
using Unity.Burst;
using Unity.Entities;
namespace Core.Battle.Buff
{
/// <summary>
/// 只移除buff 古希腊死神 (移除其实也可以按照添加那么处理。)
/// </summary>
[BurstCompile]
[UpdateAfter(typeof(BuffChangeSystem))]
public partial struct BuffRemoveSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
new RemoveBuffJob()
{
}.ScheduleParallel();
}
#region 古希腊死神
[BurstCompile]
public partial struct RemoveBuffJob : IJobEntity
{
[BurstCompile]
void Execute(DynamicBuffer<BuffData> buffDataBuffer)
{
for (int i = 0; i < buffDataBuffer.Length; i++)
{
if (buffDataBuffer[i].IsDefault())
{
buffDataBuffer.RemoveAt(i);
i--;
}
}
}
}
#endregion
}
}
BuffChangeSystem: 这个比较重要,他处理了几件事:
1、管理了外部新增、移除的buff,所有的变动请求都会由他来处理。
2、它持有BuffTableData列表,用于通过配置来构建buffData。
3、同时他还负责处理buff之间相互影响的效果(例如它有一个宝物(可以看做buff),让中毒效果增加…)
using Core.Config;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
namespace Core.Battle.Buff
{
public enum ChangeBuffState
{
Add,
/// <summary>
/// 减少x层
/// </summary>
Reduce,
Remove,
}
/// <summary>
/// 备注: 这里的AddBuffData,只是用来添加一个buff的,所以只会有runtime属性,例如类型、来源、时间、层数等
/// Buff剩余的属性,应该由构建的时候从表里读取。
/// </summary>
public struct ChangeBuffData : IBufferElementData
{
/// <summary>
/// 更改的类型
/// </summary>
public ChangeBuffState ChangeBuffState;
/// <summary>
/// buff的类型
/// </summary>
public BuffType BuffType;
/// <summary>
/// 来源Entity ID
/// </summary>
public Entity Owner;
/// <summary>
/// 来源Entity ID
/// </summary>
public Entity Target;
/// <summary>
/// buff添加时的时间
/// </summary>
public double StartTime;
/// <summary>
/// 持续时间 //TODO: 这种额外的东西,例如获得了某些宝物,增加伤害、持续时间等,都不该放在这里,而是有一个计算的system处理 这里临时处理了
/// </summary>
public double Duration;
/// <summary>
/// 层数 增加/删除的层数
/// </summary>
public int Count;
/// <summary>
/// buff level
/// </summary>
public int BuffLevel;
public ChangeBuffData(ChangeBuffState changeBuffState, BuffType buffType, Entity owner, Entity target, double startTime, double duration, int count = 1, int buffLevel = 1)
{
ChangeBuffState = changeBuffState;
BuffType = buffType;
Owner = owner;
Target = target;
StartTime = startTime;
Duration = duration;
Count = count;
BuffLevel = buffLevel;
}
}
public struct BuffTableConfig : IComponentData
{
public NativeList<BuffTableData> _buffTables;
}
[BurstCompile]
public partial struct BuffChangeSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<GameConfigFlag>();
state.EntityManager.AddComponentData(state.SystemHandle, new BuffTableConfig()
{
_buffTables = new NativeList<BuffTableData>(Allocator.Persistent),
});
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
BuffTableConfig buffTableConfig =
state.EntityManager.GetComponentData<BuffTableConfig>(state.SystemHandle);
//初始化使用的Data数据
if (buffTableConfig._buffTables.Length == 0)
{
var configEntity = SystemAPI.GetSingletonEntity<GameConfigFlag>();
DynamicBuffer<BuffTableData> buffTableData = state.EntityManager.GetBuffer<BuffTableData>(configEntity);
foreach (var t in buffTableData)
{
buffTableConfig._buffTables.Add(t);
}
state.EntityManager.SetComponentData(state.SystemHandle, buffTableConfig);
}
foreach (var (buffDataBuffer,changeBuffData,entity) in SystemAPI.Query<DynamicBuffer<BuffData>,DynamicBuffer<ChangeBuffData>>().WithEntityAccess())
{
var buffDataBufferCopy = buffDataBuffer;
//遍历addBuffDataBuffer,判断条件并构建添加到buffDataBuffer中。
for (int i = 0; i < changeBuffData.Length; i++)
{
switch (changeBuffData[i].ChangeBuffState)
{
case ChangeBuffState.Add:
{
var buff = BuffFactory.Get(buffTableConfig._buffTables,changeBuffData[i].BuffType,changeBuffData[i].Owner,
changeBuffData[i].Target,changeBuffData[i].StartTime,
changeBuffData[i].Duration,changeBuffData[i].Count,changeBuffData[i].BuffLevel);
if (buff.BuffType == BuffType.None)
{
continue;
}
// 添加时处理额外效果
buff = AddBuffDataHandle(ref state, buff,changeBuffData[i].Owner,changeBuffData[i].Target);
bool inList = false;
for (int j = 0; j < buffDataBufferCopy.Length; j++)
{
if (buffDataBufferCopy[j].BuffType == buff.BuffType)
{
inList = true;
var buffData = buffDataBufferCopy[j];
//处理entity
buffData.Add(buff);
buffDataBufferCopy[j] = buffData;
break;
}
}
if (!inList)
{
buffDataBufferCopy.Add(buff);
}
break;
}
case ChangeBuffState.Remove:
{
for (int j = 0; j < buffDataBufferCopy.Length; j++)
{
if (buffDataBufferCopy[j].BuffType == changeBuffData[i].BuffType)
{
var buff = buffDataBufferCopy[j];
//设置为default后,在BuffRemoveSystem中会进行移除
buff.SetDefault();
buffDataBufferCopy[j] = buff;
break;
}
}
break;
}
case ChangeBuffState.Reduce:
{
for (int j = 0; j < buffDataBufferCopy.Length; j++)
{
if (buffDataBufferCopy[j].BuffType == changeBuffData[i].BuffType)
{
var buff = buffDataBufferCopy[j];
buff.BuffCount -= changeBuffData[i].Count;
if (buff.BuffCount <= 0) //层数小于0 则移除
{
buff.SetDefault();
}
buffDataBufferCopy[j] = buff;
break;
}
}
break;
}
}
}
//加完之后,把他清空
changeBuffData.Clear();
}
}
/// <summary>
/// 添加buff额外处理,判断敌我身上buff之类的
/// </summary>
private BuffData AddBuffDataHandle(ref SystemState state, BuffData buff, Entity owner, Entity target)
{
var addBuff = buff;
switch (addBuff.BuffType)
{
case BuffType.None:
break;
case BuffType.Damage:
addBuff = DamageBuffHandle(ref state, buff, owner, target);
break;
}
return addBuff;
}
private BuffData DamageBuffHandle(ref SystemState state, BuffData buff, Entity owner, Entity target)
{
var ownerBuffs = SystemAPI.GetBuffer<BuffData>(owner);
for (int i = 0; i < ownerBuffs.Length; i++)
{
if (ownerBuffs[i].BuffType == BuffType.AddDamageValue)
{
buff.BuffValue += ownerBuffs[i].BuffValue;
}
}
return buff;
}
}
}
BuffTemplateSystem:用来复制的脚本,可以理解为buff的实际效果,但由于无法继承,则每次新建可以从他身上复制。。
using Core.Battle.Role.Enemy;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
namespace Core.Battle.Buff
{
/// <summary>
/// 这是一个用来被复制的脚本。
/// </summary>
[BurstCompile]
[UpdateAfter(typeof(BuffTimeSystem))]
public partial struct BuffTemplateSystem : ISystem
{
private EntityCommandBuffer ecb;
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<BeginSimulationEntityCommandBufferSystem.Singleton>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
ecb = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>()
.CreateCommandBuffer(state.EntityManager.WorldUnmanaged);
foreach (var (buffData,changeBuff, owner) in SystemAPI.Query<DynamicBuffer<BuffData>,DynamicBuffer<ChangeBuffData>>().WithEntityAccess())
{
var buffDataBuffer = buffData;
for (int i = 0; i < buffDataBuffer.Length; i++)
{
if (Filter(buffDataBuffer[i].BuffType))
{
if (!state.EntityManager.Exists(owner))
{
continue;
}
#region 生命周期
var tmpDataBuffer = buffDataBuffer[i];
var bf = buffDataBuffer[i].BuffFlag;
// 新加的
if (buffDataBuffer[i].BuffFlag.IsNewAdd)
{
bf.IsNewAdd = false;
}
// 新加x层
if (buffDataBuffer[i].BuffFlag.IsAdded > 0)
{
bf.IsAdded = 0;
}
// 减少x层
if (buffDataBuffer[i].BuffFlag.IsReduce > 0)
{
bf.IsReduce = 0;
}
// 每次触发
if (buffDataBuffer[i].BuffFlag.IsTrigger)
{
bf.IsTrigger = false; //手动修改触发类型
}
// 移除事件
if (buffDataBuffer[i].BuffFlag.IsRemove)
{
//触发移除事件...
//设置默认移除
buffDataBuffer[i].SetDefault();
}
//赋值回去
tmpDataBuffer.BuffFlag = bf;
buffDataBuffer[i] = tmpDataBuffer;
#endregion
}
}
}
}
/// <summary>
/// 是否对该类型做处理
/// </summary>
private bool Filter(BuffType bType)
{
return false;
return bType == BuffType.None;
}
public void OnDestroy(ref SystemState state)
{
}
/// <summary>
/// 也是个用来复制的模板
/// </summary>
[BurstCompile]
public partial struct JobEntityTemplate : IJobEntity
{
public float3 ownerPosition;
public EntityCommandBuffer.ParallelWriter ecb;
[BurstCompile]
void Execute(Entity e,in LocalTransform roleTrans,in EnemyFlag enemyFlag,[EntityIndexInQuery]int index)
{
if (math.distance(ownerPosition,roleTrans.Position) <= 5f)
{
// 说明碰到了,消灭它。
}
}
}
}
}
BuffHelper:提供封装方法,能够快速的实现增删改查。
using Unity.Burst;
using Unity.Entities;
namespace Core.Battle.Buff
{
public static class BuffHelper
{
/// <summary>
/// 添加个buff
/// </summary>
[BurstCompile]
public static void AddBuff_Battle(ref this SystemState state,
Entity target, BuffType bt, Entity owner, double startTime, double duration,int buffCount = 1,int buffLevel = 1)
{
var changeInfo = state.EntityManager.GetBuffer<ChangeBuffData>(target);
changeInfo.Add(new ChangeBuffData()
{
ChangeBuffState = ChangeBuffState.Add,
BuffType = bt,
Owner = owner,
StartTime = startTime,
Duration = duration,
Count = buffCount,
BuffLevel = buffLevel
});
}
/// <summary>
/// 直接移除个buff
/// </summary>
[BurstCompile]
public static void RemoveBuff_Battle(ref this SystemState state, Entity target, BuffType bt)
{
var changeInfo = state.EntityManager.GetBuffer<ChangeBuffData>(target);
changeInfo.Add(new ChangeBuffData()
{
ChangeBuffState = ChangeBuffState.Remove,
BuffType = bt
});
}
/// <summary>
/// 移除某个buff多少层
/// </summary>
[BurstCompile]
public static void RemoveBuffCount_Battle(ref this SystemState state, Entity target, BuffType bt, int count)
{
var changeInfo = state.EntityManager.GetBuffer<ChangeBuffData>(target);
changeInfo.Add(new ChangeBuffData()
{
ChangeBuffState = ChangeBuffState.Reduce,
BuffType = bt,
Count = count
});
}
[BurstCompile]
public static bool ContainsBuff_Battle(ref this SystemState state, Entity target, BuffType bt)
{
var buffData = state.EntityManager.GetBuffer<BuffData>(target);
for (int i = 0; i < buffData.Length; i++)
{
if (buffData[i].BuffType == bt)
{
return true;
}
}
return false;
}
/// <summary>
/// 为none就是没有
/// </summary>
[BurstCompile]
public static BuffData GetBuff_Battle(ref this SystemState state, Entity target, BuffType bt)
{
var buffData = state.EntityManager.GetBuffer<BuffData>(target);
for (int i = 0; i < buffData.Length; i++)
{
if (buffData[i].BuffType == bt)
{
return buffData[i];
}
}
return new BuffData(){BuffType = BuffType.None};
}
}
}
代码基本就是这样了,最后可以这样很方便的使用这一套buff系统:
using Core.Battle.Buff;
using Unity.Entities;
namespace Core.Battle.Role.Player
{
//用于初始化player
public partial struct PlayerInitSystem : ISystem
{
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<PlayerFlag>();
}
public void OnUpdate(ref SystemState state)
{
//暂时只初始化一次 之后有重开等再说
state.Enabled = false;
foreach (var (playerFlag, e) in SystemAPI
.Query<RefRO<PlayerFlag>>().WithEntityAccess())
{
// 给player加个buff
state.AddBuff_Battle(e,BuffType.NormalAtk,e,SystemAPI.Time.ElapsedTime,10000,1,2); //加了个lv2的普通攻击
state.AddBuff_Battle(e,BuffType.AddDamageValue,e,SystemAPI.Time.ElapsedTime,-1); //加了个lv1的伤害增幅
}
}
}
}
最后我定义了一个5攻击的直伤BUFF,外加一个额外附加5伤害的BUFF,成功对范围内敌人造成10点伤害