您当前的位置: 首页 > 

CloudHu1989

暂无认证

  • 5浏览

    0关注

    89博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

ECS进阶:Boids

CloudHu1989 发布时间:2019-08-12 12:18:28 ,浏览量:5

基于Unity2019最新ECS架构开发MMO游戏笔记8
  • 官方ECS进阶案例解析之大群
      • 开始之前的准备工作:
    • ECS进阶:Boids
      • 小结
        • DOTS 逻辑图表
  • 更新计划
    • 作者的话
  • ECS系列目录
    • ECS官方示例1:ForEach
    • ECS官方案例2:IJobForEach
    • ECS官方案例3:IJobChunk
    • ECS官方案例4:SubScene
    • ECS官方案例5:SpawnFromMonoBehaviour
    • ECS官方案例6:SpawnFromEntity
    • ECS官方案例7:SpawnAndRemove
    • ECS进阶:FixedTimestepWorkaround
    • ECS进阶:Boids
    • ECS进阶:场景切换器
    • ECS进阶:MegaCity0
    • ECS进阶:MegaCity1
    • UnityMMO资源整合&服务器部署
    • UnityMMO选人流程
    • UnityMMO主世界

官方ECS进阶案例解析之大群 开始之前的准备工作:

0下载Unity编辑器(2019.1.0f1 or 更新的版本),if(已经下载了)continue; 1下载官方案例,打开Git Shell输入: git clone https://github.com/Unity-Technologies/EntityComponentSystemSamples.git --recurse or 点击Unity官方ECS示例下载代码 if(已经下载了)continue; 2用Unity Hub打开官方的项目:ECSSamples 3在Assets目录下找到Advanced/Boids,并打开其下的BoidExample场景

ECS进阶:Boids

Boids其实是类鸟群的意思,在咱们IT领域叫做“集群模拟算法”,但我是个漫威粉,所以称之大群,LOL。 在这里插入图片描述 如上图所示,这个案例向我们展示了大量(50000)实体的风采,虽然鲨鱼的模型不怎么精致,但是大群巍巍壮观,这才是ECS的真正用法吧!下面一起来看看这样震撼的场面是如何实现的吧:

        /// 
        /// E:把大群的数据传递给C
        /// 
        [RequiresEntityConversion]
        public class Boid : MonoBehaviour, IConvertGameObjectToEntity
        {
            public float CellRadius;
            public float SeparationWeight;
            public float AlignmentWeight;
            public float TargetWeight;
            public float ObstacleAversionDistance;
            public float MoveSpeed;
    
            // Lets you convert the editor data representation to the entity optimal runtime representation
            public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
            {
                dstManager.AddSharedComponentData(entity, new Samples.Boids.Boid
                {
                    CellRadius = CellRadius,
                    SeparationWeight = SeparationWeight,
                    AlignmentWeight = AlignmentWeight,
                    TargetWeight = TargetWeight,
                    ObstacleAversionDistance = ObstacleAversionDistance,
                    MoveSpeed = MoveSpeed
                });
            }
        }

以上是大群的实体Entity(以后注释统一用E缩略,表明其身份)部分,E将数据传递给C保存,下面我们一起看C:

	/// 
    /// C:大群的共享数据
    /// 
    [Serializable]
    [WriteGroup(typeof(LocalToWorld))]
    public struct Boid : ISharedComponentData
    {
        /// 
        /// 单元半径
        /// 
        public float CellRadius;
        /// 
        /// 间隔宽度
        /// 
        public float SeparationWeight;
        /// 
        /// 对齐宽度
        /// 
        public float AlignmentWeight;
        /// 
        /// 目标宽度
        /// 
        public float TargetWeight;
        /// 
        /// 排斥距离
        /// 
        public float ObstacleAversionDistance;
        /// 
        /// 移动速度
        /// 
        public float MoveSpeed;
    }

C(Component组件,以后简写C,表明身份即可)和以前不一样了,C实现了空接口 ISharedComponentData,唯一目的只是为了表明其储存的数据是共享的,一直以来C都是最简单的脚本。

下面一起来看看最复杂的S(System系统,今后简写S),大群系统BoidSystem。 由于大群系统比较复杂,因此我画了一副图来描摹几个概念,如图: 在这里插入图片描述 解释下脚本中用到的几个定语: [RequireComponentTag(typeof(组件))]:这里的组件是指C [BurstCompile]:Burst编译,编译速度更快 [ReadOnly]:只读的方式访问,速度更快 [DeallocateOnJobCompletion]:在任务完成的时候释放内存(解除分配内存)

/// 
/// 麦克在GDC上面的讲话‘面向数据的方式来使用组件系统’对于开发大群的案例代码来说有非常大的参考价值
/// https://youtu.be/p65Yt20pw0g?t=1446 (这是油管链接,需要翻墙观看,有兴趣的朋友可以在参考)
/// 它解释了这个案例之前版本的实现,但是几乎所有的信息仍然具有参考价值。
/// 目标(2条红鱼)和对手(1条鲨鱼)基于Unity UI里的ActorAnimation(角色动画)栏移动
/// 这样它们才能基于关键帧动画来移动
/// 
namespace Samples.Boids
{
    /// 
    /// S:大群系统,在模拟系统组中更新,在Transform系统组之前更新
    /// 
    [UpdateInGroup(typeof(SimulationSystemGroup))]
    [UpdateBefore(typeof(TransformSystemGroup))]
    public class BoidSystem : JobComponentSystem
    {
        private EntityQuery  m_BoidQuery;//大群实体查询缓存
        private EntityQuery  m_TargetQuery;//目标实体查询缓存
        private EntityQuery  m_ObstacleQuery;//对手实体查询缓存

        /// 
        /// 在这个案例中总共有3个独特的大群变体,各有各的共享组件值
        /// (提示:这包含了在索引0上默认未初始化的值,该值并未被实际用在这个案例中,)
        /// 
        private List                               m_UniqueTypes = new List(3);
        private List m_PrevFrameHashmaps = new List();

        /// 
        /// `CopyPositions`和`CopyHeadings`都是为了提取相对位置、导向组件到NativeArrays(原生数组)
        /// 这样它们才能被`MergeCells`(合并单元)和`Steer`(导航)任务随机访问到 (下面是这两个结构体)
        /// 
        [BurstCompile]
        struct CopyPositions : IJobForEachWithEntity
        {
            /// 
            /// 位置,原生数组
            /// 
            public NativeArray positions;
            /// 
            /// 把本地位置放到原生数组里
            /// 
            /// 实体
            /// 索引
            /// 只读本地位置
            public void Execute(Entity entity, int index, [ReadOnly]ref LocalToWorld localToWorld)
            {
                positions[index] = localToWorld.Position;
            }
        }
        //同上
        [BurstCompile]
        struct CopyHeadings : IJobForEachWithEntity
        {
            public NativeArray headings;

            public void Execute(Entity entity, int index, [ReadOnly]ref LocalToWorld localToWorld)
            {
                headings[index] = localToWorld.Forward;
            }
        }
        /// 
        /// 生成一个哈希表,每一栏里包含了所有大群的索引,其位置量化成同一个值,即提供的单元半径
        /// 这样它们才能被`MergeCells`(合并单元)和`Steer`(导航)任务随机访问到 
        /// 
        [BurstCompile]
        [RequireComponentTag(typeof(Boid))]
        struct HashPositions : IJobForEachWithEntity
        {
            public NativeMultiHashMap.ParallelWriter hashMap;
            public float                                       cellRadius;

            public void Execute(Entity entity, int index, [ReadOnly]ref LocalToWorld localToWorld)
            {
                var hash = (int)math.hash(new int3(math.floor(localToWorld.Position / cellRadius)));
                hashMap.Add(hash, index);
            }
        }
        
        /// 
        /// 合并所有单元
        /// 这里收集了所有单元大群的位置和导向,为了做以下三件事情:
        /// 1.计算每个单元的数量
        /// 2.找到最近的对手并将互相标记为目标
        /// 3.找到包含已收集每个大群单元值的数组索引
        /// 
        [BurstCompile]
        struct MergeCells : IJobNativeMultiHashMapMergedSharedKeyIndices
        {
            public NativeArray                 cellIndices;//单元索引原生数组
            public NativeArray              cellAlignment;//单元对齐原生数组
            public NativeArray              cellSeparation;//单元间隔
            public NativeArray                 cellObstaclePositionIndex;//单元对手位置索引
            public NativeArray               cellObstacleDistance;//单元对手距离
            public NativeArray                 cellTargetPositionIndex;//单元目标位置索引
            public NativeArray                 cellCount;//单元数量
            [ReadOnly] public NativeArray   targetPositions;//目标位置
            [ReadOnly] public NativeArray   obstaclePositions;//对手位置
            /// 
            /// 循环数组找到最近的位置
            /// 
            /// 目标
            /// 位置
            /// 最近位置索引
            /// 最近距离
            void NearestPosition(NativeArray targets, float3 position, out int nearestPositionIndex, out float nearestDistance )
            {
                nearestPositionIndex = 0;
                nearestDistance      = math.lengthsq(position-targets[0]);
                for (int i = 1; i             
关注
打赏
1664096582
查看更多评论
0.1118s