【Unity】基于Dots + Luban的Buff模块

1. Dots资源包导入

Window--->Package Manager,导入Jobs,Entities,Hybrid Renderer,Burst,Entities Graphics

2.Luban导入

按照Luban官方文档导入即可:https://www.datable.cn/docs/guide/install

3. 方案与思路

配置表上以Buff表和BuffEffect表为核心

Buff表字段,配置Buff的时间,单次触发时间,是否叠加层数等

BuffEffect表字段,配置Buff的具体效果

枚举值需要用到Luban的__enums__表

Dots方面设计思路

Component有AddBuffComponent,BuffComponent,BuffTriggerEventComponent

System有AddBuffSystem,BuffExpireSystem,BuffTickSystem,BuffEffectSystem

执行顺序为:

给某个实体添加AddBuffComponent ------> AddBuffSystem添加具体Buff ------> BuffExpireSystem检查Buff是否过期 ------> BuffTickSystem检查OnTick触发条件的Buff ------> BuffEffectSystem具体实现Buff效果

AddBuffComponent

csharp 复制代码
    public struct AddBuffComponent : IBufferElementData
    {
        public Entity Target;
        public Entity Caster;
        public int BuffId;
    }

BuffTriggerEventComponent

csharp 复制代码
    public struct BuffTriggerEventComponent : IBufferElementData
    {
        public Entity Caster;
        public Entity Target;
        
        public int BuffId;
        public uint BuffInstanceId;

        // 触发瞬间的快照;即使 Buff 已移除,OnExpire 仍能正确结算。
        public int Stack;
        public BuffTriggerType TriggerType;
    }
    
    public enum BuffTriggerType : byte
    {
        OnApply = 1,
        OnTick = 2,
        OnExpire = 8,
        OnRemove = 3,
        OnHit = 5,
        OnDamaged = 6,
        OnKill = 7,
        OnStackChanged = 9
    }

BuffComponent

csharp 复制代码
    public struct BuffComponent : IBufferElementData
    {
        public int BuffID;
        public Entity Caster;
        public uint InstanceId;
        public int Stack;
        public float StartTime;
        public float EndTime;
        public float NextTickTime;
    }

AddBuffSystem

csharp 复制代码
    [UpdateInGroup(typeof(BuffSystemGroup))]
    [UpdateBefore(typeof(BuffExpireSystem))]
    public partial struct AddBuffSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<AddBuffComponent>();
        }

        public void OnUpdate(ref SystemState state)
        {
            foreach (var (addBuffs, entity) in SystemAPI.Query<DynamicBuffer<AddBuffComponent>>().WithEntityAccess())
            {
                var time = (float)SystemAPI.Time.ElapsedTime;
                for (int i = 0; i < addBuffs.Length; i++)
                {
                    var addBuff = addBuffs[i];
                    if (!BuffUtility.TryGetBuffConfig(addBuff.BuffId, out var buffConfig))
                    {
                        DotsLogger.Info($"AddBuffSystem Buff config `{addBuff.BuffId}` doesn't exist");
                        continue;
                    }

                    if (!SystemAPI.HasBuffer<BuffComponent>(addBuff.Target))
                    {
                        DotsLogger.Info($"AddBuffSystem Buff `{addBuff.Target}` doesn't exist");
                        continue;
                    }
                    var buffs = SystemAPI.GetBuffer<BuffComponent>(addBuff.Target);
                    var newBuff = BuffUtility.AddBuff(buffs, addBuff.BuffId, addBuff.Caster, time, buffConfig);
                    
                    BuffUtility.TryGetBuffEffectConfig(buffConfig.BuffEffectID, out var buffEffectConfig);
                    if (buffEffectConfig.TriggerType == (int)BuffTriggerType.OnApply)
                    {
                        var eventBuffer = SystemAPI.GetSingletonBuffer<BuffTriggerEventComponent>();
                        eventBuffer.Add(new BuffTriggerEventComponent
                        {
                            Target = addBuff.Target,
                            Caster = addBuff.Target,
                            BuffId = addBuff.BuffId,
                            BuffInstanceId = newBuff.InstanceId,
                            Stack = newBuff.Stack,
                            TriggerType = BuffTriggerType.OnApply
                        });
                    }
                }
                addBuffs.Clear();
            }
        }

        [BurstCompile]
        public void OnDestroy(ref SystemState state)
        {

        }
    }

BuffExpireSystem

csharp 复制代码
    [UpdateInGroup(typeof(BuffSystemGroup))]
    [UpdateAfter(typeof(AddBuffSystem))]
    public partial struct BuffExpireSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            
        }

        public void OnUpdate(ref SystemState state)
        {
            var now = (float)SystemAPI.Time.ElapsedTime;
            var triggerEvents =
                SystemAPI.GetSingletonBuffer<BuffTriggerEventComponent>();

            foreach (var (buffs, target) in SystemAPI.Query<DynamicBuffer<BuffComponent>>().WithEntityAccess())
            {
                // 倒序移除,避免索引错位。
                for (var i = buffs.Length - 1; i >= 0; i--)
                {
                    var buff = buffs[i];

                    if (!BuffUtility.IsExpired(buff, now))
                    {
                        continue;
                    }

                    // 必须在移除前记录快照。
                    triggerEvents.Add(new BuffTriggerEventComponent
                    {
                        Target = target,
                        Caster = buff.Caster,
                        BuffId = buff.BuffID,
                        BuffInstanceId = buff.InstanceId,
                        Stack = buff.Stack,
                        TriggerType = BuffTriggerType.OnExpire
                    });

                    var modifiers = SystemAPI.GetBuffer<AttributeModifier>(target);
                    for (var modifierIndex = modifiers.Length - 1; modifierIndex >= 0; modifierIndex--)
                    {
                        if (modifiers[modifierIndex].SourceId == buff.InstanceId)
                        {
                            modifiers.RemoveAt(modifierIndex);
                        }
                    }
                    
                    buffs.RemoveAt(i);
                }
            }

        }
        
        [BurstCompile]
        public void OnDestroy(ref SystemState state)
        {

        }
    }

BuffTickSystem

csharp 复制代码
    [UpdateInGroup(typeof(BuffSystemGroup))]
    [UpdateAfter(typeof(BuffExpireSystem))]
    [UpdateBefore(typeof(BuffEffectSystem))]
    public partial struct BuffTickSystem : ISystem
    {
        private const int MaxTicksPerFrame = 3;
        
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            
        }

        public void OnUpdate(ref SystemState state)
        {
            var now = (float)SystemAPI.Time.ElapsedTime;
            var eventBuffer = SystemAPI.GetSingletonBuffer<BuffTriggerEventComponent>();
            foreach (var (buffs, target) in SystemAPI.Query<DynamicBuffer<BuffComponent>>().WithEntityAccess())
            {
                var buffBuffer = buffs;
                var time = (float)SystemAPI.Time.ElapsedTime;
                for (int i = buffs.Length - 1; i >= 0 ; i--)
                {
                    var buff = buffs[i];

                    if (!BuffUtility.TryGetBuffConfig(buff.BuffID, out var config))
                    {
                        continue;
                    }

                    if (config.TickInterval <= 0f || now < buff.NextTickTime)
                    {
                        continue;
                    }
                    
                    BuffUtility.TryGetBuffEffectConfig(config.BuffEffectID, out var effectConfig);
                    if (effectConfig.TriggerType != (int)BuffTriggerType.OnTick)
                    {
                        continue;
                    }
                    
                    var tickCount = 0;
                    while (now >= buff.NextTickTime &&
                           tickCount < MaxTicksPerFrame)
                    {
                        eventBuffer.Add(new BuffTriggerEventComponent
                        {
                            Target = target,
                            Caster = buff.Caster,
                            BuffId = buff.BuffID,
                            BuffInstanceId = buff.InstanceId,
                            Stack = buff.Stack,
                            TriggerType = BuffTriggerType.OnTick
                        });

                        // 从旧 Tick 时间推进,避免掉帧导致 Tick 时间漂移。
                        buff.NextTickTime += config.TickInterval;
                        tickCount++;
                    }

                    buffBuffer[i] = buff;
                }
            }
        }

        [BurstCompile]
        public void OnDestroy(ref SystemState state)
        {

        }
    }

BuffEffectSystem

csharp 复制代码
    [UpdateInGroup(typeof(BuffSystemGroup))]
    [UpdateAfter(typeof(BuffTickSystem))]
    public partial struct BuffEffectSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            
        }

        public void OnUpdate(ref SystemState state)
        {
            
            var triggers = SystemAPI.GetSingletonBuffer<BuffTriggerEventComponent>();
            for (int i = 0; i < triggers.Length; i++)
            {
                var trigger = triggers[i];

                bool hasConfig = BuffUtility.TryGetBuffConfig(trigger.BuffId, out var buffConfig);
                if (!hasConfig)
                {
                    continue;
                }
                bool hasEffectConfig = BuffUtility.TryGetBuffEffectConfig(buffConfig.BuffEffectID, out var buffEffectConfig);
                if (!hasEffectConfig || buffEffectConfig.TriggerType != (int)trigger.TriggerType)
                {
                    continue;
                }
                DotsLogger.Info($"Buff Effect Config: {buffEffectConfig.BuffEffectID}, effectType : {buffEffectConfig.EffectType}");

                switch ((BuffEffectConfigEffectType)buffEffectConfig.EffectType)
                {
                   case BuffEffectConfigEffectType.Damage:
                       AddDamage(ref state, trigger.Target, buffEffectConfig.Value);
                       break;
                   case BuffEffectConfigEffectType.Health:
                       AddHealth(ref state, trigger.Target, buffEffectConfig.Value);
                       break;
                   case BuffEffectConfigEffectType.AttributeModifier:
                       var modifiers = SystemAPI.GetBuffer<AttributeModifier>(trigger.Target);
                       modifiers.Add(new AttributeModifier
                       {
                           AttributeType = (AttributeType)buffEffectConfig.AttributeType,
                           Operation = (AttributeModifierOperation)buffEffectConfig.ValueMode,
                           Value = buffEffectConfig.Value,
                           SourceId = trigger.BuffInstanceId,
                           SourceEntity = trigger.Caster
                       });
                       break;
                }
            }
            
            triggers.Clear();
        }

        [BurstCompile]
        public void OnDestroy(ref SystemState state)
        {

        }

        private void AddDamage(ref SystemState state, Entity entity, float damage)
        {
            ChangeHealth(ref state, entity, damage);
        }

        private void AddHealth(ref SystemState state, Entity entity, float heal)
        {
            ChangeHealth(ref state, entity, heal);
        }

        private void ChangeHealth(ref SystemState state, Entity entity, float value)
        {
            if (!state.EntityManager.HasComponent<VitalComponent>(entity))
            {
                return;
            }

            var vital = state.EntityManager.GetComponentData<VitalComponent>(entity);

            var maxHealth = state.EntityManager.HasComponent<AttributeComponent>(entity)
                ? state.EntityManager.GetComponentData<AttributeComponent>(entity).MaxHealth
                : float.MaxValue;

            vital.Health = math.clamp(vital.Health + value, 0f, maxHealth);
            state.EntityManager.SetComponentData(entity, vital);
        }
    }

由于Luban导出的模板表数据不能在Dots层直接使用,因此做了一层转换

csharp 复制代码
    public static class BuffUtility
    {
        private static BuffConfigData _buffConfig;
        private static bool _isCreateConfig;

        public static void InitBuffConfig()
        {
            if (!_isCreateConfig)
            {
                var allTemp = TemplateManager.Instance.Tables.TbBuff.DataList;
                NativeHashMap<int, BuffRunTimeConfig> buffConfig = new NativeHashMap<int, BuffRunTimeConfig>(allTemp.Count, Allocator.Persistent);
                for (int i = 0; i < allTemp.Count; i++)
                {
                    var runTimeConfig = new BuffRunTimeConfig();
                    runTimeConfig.BuffId = allTemp[i].Id;
                    runTimeConfig.Duration = allTemp[i].Duration;
                    runTimeConfig.TickInterval = allTemp[i].TickInterval;
                    runTimeConfig.MaxStack = allTemp[i].MaxStack;
                    runTimeConfig.StackType = allTemp[i].StackType;
                    runTimeConfig.BuffEffectID = allTemp[i].Effect;
                    buffConfig.Add(allTemp[i].Id, runTimeConfig);
                }

                var buffEffectTempList = TemplateManager.Instance.Tables.TbBuffEffect.DataList;
                NativeHashMap<int, BuffEffectConfig> buffEffectConfigList = new NativeHashMap<int, BuffEffectConfig>(buffEffectTempList.Count, Allocator.Persistent);
                for (int i = 0; i < buffEffectTempList.Count; i++)
                {
                    var buffEffectConfig = new BuffEffectConfig();
                    buffEffectConfig.BuffEffectID = buffEffectTempList[i].Id;
                    buffEffectConfig.TriggerType = (int)buffEffectTempList[i].Trigger;
                    buffEffectConfig.TargetSelecor = (int)buffEffectTempList[i].TargetSelecor;
                    buffEffectConfig.EffectType = (int)buffEffectTempList[i].EffectType;
                    buffEffectConfig.ValueMode = (int)buffEffectTempList[i].ValueMode;
                    buffEffectConfig.AttributeType = (int)buffEffectTempList[i].AttributeType;
                    buffEffectConfig.Value = buffEffectTempList[i].Value;
                    buffEffectConfigList.Add(buffEffectTempList[i].Id, buffEffectConfig);
                }
                
                
                _buffConfig = new BuffConfigData
                {
                    Config = buffConfig, EffectConfig = buffEffectConfigList
                };
                _isCreateConfig = true;
            }
        }
        
        public static bool TryGetBuffConfig(
            int buffId,
            out BuffRunTimeConfig config)
        {
            if (!_isCreateConfig)
            {
                InitBuffConfig();
            }

            return _buffConfig.Config.TryGetValue(buffId, out config);
        }

        public static bool TryGetBuffEffectConfig(int effectID, out BuffEffectConfig config)
        {
            if (!_isCreateConfig)
            {
                InitBuffConfig();
            }
            return _buffConfig.EffectConfig.TryGetValue(effectID, out config);
        }
    }
相关推荐
紫龙大侠2 小时前
Unity许可证文件(激活成功后)默认保存位置
unity
Msir_Modern6 小时前
Noita 元胞沙盒笔记
算法·unity·c#·游戏引擎·元胞自动机
Oiiouui10 小时前
Godot-GridGame架构逻辑层与实体/物品层篇
架构·游戏引擎·godot
天天进步201510 小时前
Unity 2020.3 反复提示安全模式:一次 CS0009 与 DLL 文件占用的排查记录
unity·1024程序员节
true...10 小时前
虚幻还原默认视角
游戏引擎·虚幻
淡海水12 小时前
07-04-源码-.NET Core GC清扫与压缩
unity·架构·c#·.netcore·gc·1024程序员节
小贺儿开发1 天前
《长征 · 征途》Unity 三维互动沙盘
科技·3d·unity·1024程序员节·地形·互动·沙盘
解决小子1 天前
【虚幻引擎】【C++】【GAS框架】构建角色基类、属性集、数值钳制
c++·笔记·游戏引擎·虚幻·gas·能力系统
上班摸鱼下班游戏1 天前
Unity 仿生智能算法 从蚁群算法 开始
算法·unity·游戏引擎