【DOTS】基于DOTS的BUFF系统实现

9 篇文章 0 订阅
3 篇文章 0 订阅

正常的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  
    
}

最后的结果对比:
之前:
在这里插入图片描述
之后:
在这里插入图片描述

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Dots(Data-Oriented Technology Stack)是Unity引擎的一种数据导向技术栈,它可以让游戏的数据更加高效地处理和组织。KDTree是一种经典的数据结构,可以用于高效地处理多维空间数据的查询。本篇文章将介绍如何使用C#实现基于Dots的KDTree。 首先,我们需要定义一个点的数据结构。假设我们要处理二维空间中的点,我们可以定义一个名为“Point”的结构体: ``` public struct Point { public float x; public float y; public Point(float x, float y) { this.x = x; this.y = y; } } ``` 接下来,我们需要定义一个节点的数据结构。每个节点包含一个点、一个左子树和一个右子树。如果该节点没有子树,则对应的子树为空: ``` public struct Node { public Point point; public Node left; public Node right; } ``` 我们使用递归方法构建KDTree。具体来说,对于一个给定的点集合,我们首先找到X坐标的中位数,并将其作为根节点。然后,我们将点集合分成两个子集,一个包含所有X坐标小于中位数的点,另一个包含所有X坐标大于中位数的点。接着,我们递归地在每个子集中构建左子树和右子树,直到子集为空。在构建子树时,我们使用Y坐标的中位数来确定左右子树的分裂方式。 下面是构建KDTree的代码: ``` public static Node BuildKdTree(Point[] points, int depth = 0) { if (points == null || points.Length == 0) { return default(Node); } int axis = depth % 2; int medianIndex = points.Length / 2; Array.Sort(points, (a, b) => a.x.CompareTo(b.x)); Node node = new Node(); node.point = points[medianIndex]; node.left = BuildKdTree(points.Take(medianIndex).ToArray(), depth + 1); node.right = BuildKdTree(points.Skip(medianIndex + 1).ToArray(), depth + 1); return node; } ``` 我们可以使用以下代码测试构建KDTree的效果: ``` Point[] points = new Point[] { new Point(2, 3), new Point(5, 4), new Point(9, 6), new Point(4, 7), new Point(8, 1), new Point(7, 2) }; Node root = BuildKdTree(points); ``` 现在,我们已经成功地构建了一个KDTree。接下来,我们需要实现一个查询方法来查找最近邻点。查询方法的思想是从根节点开始向下遍历,直到叶子节点。在遍历的过程中,我们计算当前节点和目标点之间的距离,并将其与当前最近邻点的距离进行比较。如果当前节点更接近目标点,则更新最近邻点。接着,我们根据当前节点和目标点的关系,递归地遍历左子树或右子树。当我们到达叶子节点时,我们将该叶子节点作为当前最近邻点,并将其距离与当前最近邻点的距离进行比较。最终,我们找到了最近邻点。 以下是查询方法的代码: ``` public static Point FindNearestPoint(Node node, Point target) { if (node.left == default(Node) && node.right == default(Node)) { return node.point; } Point best = node.point; if (node.left != default(Node) && target.x < node.point.x) { Point leftBest = FindNearestPoint(node.left, target); if (Distance(leftBest, target) < Distance(best, target)) { best = leftBest; } } if (node.right != default(Node) && target.x > node.point.x) { Point rightBest = FindNearestPoint(node.right, target); if (Distance(rightBest, target) < Distance(best, target)) { best = rightBest; } } return best; } private static float Distance(Point a, Point b) { return Mathf.Sqrt(Mathf.Pow(a.x - b.x, 2) + Mathf.Pow(a.y - b.y, 2)); } ``` 我们可以使用以下代码测试查询方法的效果: ``` Point target = new Point(3, 5); Point nearest = FindNearestPoint(root, target); Debug.Log(nearest.x + ", " + nearest.y); // 输出 "2, 3" ``` 这就是基于Dots的KDTree的实现方法。它可以被用于高效地处理多维空间数据的查询,并且可以很容易地扩展到更高维度的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值