Unity 之 Pure版Entity Component System【ECS】 案例【GalacticConquest】解析【下】

书接上回:Unity 之 Pure版Entity Component System 案例【GalacticConquest】解析【上】

点击下载工程

我们已经完成飞船的实例化,下面就是让飞船动起来~~~

创建脚本ShipMovementSystem飞船的运动系统,作用:

  • 让飞船朝着目标移动
  • 达到目标是添加标签
using Data;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using UnityEngine;
using Unity.Mathematics;

namespace Systems
{
    [UpdateAfter(typeof(ShipArrivalSystem))]
    public class ShipMovementSystem : JobComponentSystem
    {
        //所有飞船
        struct Ships
        {
            public readonly int Length;
            public ComponentDataArray<Position> Positions;
            public ComponentDataArray<Rotation> Rotations;
            public ComponentDataArray<ShipData> Data;
            public EntityArray Entities;
        }
        //所有星球
        struct Planets
        {
            public readonly int Length;
            public ComponentDataArray<PlanetData> Data;
        }
        /// <summary>
        /// 计算飞船位移 多核心运算
        /// </summary>
        [BurstCompile]
        struct CalculatePositionsJob : IJobParallelFor
        {
            public float DeltaTime;
            [ReadOnly]
            public ComponentDataArray<ShipData> Ships;
            [ReadOnly] public EntityArray Entities;
            public ComponentDataArray<Position> Positions;
            public ComponentDataArray<Rotation> Rotations;

            [ReadOnly] public ComponentDataArray<PlanetData> Planets;
            [ReadOnly] public ComponentDataFromEntity<PlanetData> TargetPlanet;

            //并发版本 因为使用的是IJobParallelFor
            public NativeQueue<Entity>.Concurrent ShipArrivedQueue;

            public void Execute(int index)
            {
                //飞船的数据
                var shipData = Ships[index];
                //对应需要攻击星球的位置
                var targetPosition = TargetPlanet[shipData.TargetEntity].Position;
                //飞船的位置
                var position = Positions[index];
                //飞船的角度
                var rotation = Rotations[index];

                //逐渐靠近需要攻击的星球
                var newPos = Vector3.MoveTowards(position.Value, targetPosition, DeltaTime * 4.0f);
                //逐一遍历所有找到的星球
                for (var planetIndex = 0; planetIndex < Planets.Length; planetIndex++)
                {
                    var planet = Planets[planetIndex];
                    //如果与星球的距离小于半径
                    if (Vector3.Distance(newPos, planet.Position) < planet.Radius)
                    {
                        //判断这个是不是需要攻击的星球
                        if (planet.Position == targetPosition)
                        {
                            //添加到飞船队列里面
                            ShipArrivedQueue.Enqueue(Entities[index]);
                        }
                        //防止飞船进入到星球内部,重新计算。很精确啊~
                        var direction = (newPos - planet.Position).normalized;
                        newPos = planet.Position + (direction * planet.Radius);
                        break;
                    }
                }
                //计算飞船朝向
                var shipCurrentDirection = math.normalize((float3)newPos - position.Value);
                rotation.Value = quaternion.LookRotation(shipCurrentDirection, math.up());
                //将计算完的结果赋值
                position.Value = newPos;
                Positions[index] = position;
                Rotations[index] = rotation;
            }
        }
        //单核心运算 IJob允许您安排与其他作业和主线程并行运行的单个作业
        struct ShipArrivedTagJob : IJob
        {
            public EntityCommandBuffer EntityCommandBuffer;
            public NativeQueue<Entity> ShipArrivedQueue;

            public void Execute()
            {
                Entity entity;
                while (ShipArrivedQueue.TryDequeue(out entity))
                {
                    //添加已经到达指定星球的标记
                    EntityCommandBuffer.AddComponent(entity, new ShipArrivedTag());
                }
            }
        }

        [Inject]
        EndFrameBarrier m_EndFrameBarrier;

        [Inject]
        Ships m_Ships;    //所有飞船
        [Inject]
        Planets m_Planets;//所有星球

        NativeQueue<Entity> m_ShipArrivedQueue;

        protected override void OnCreateManager()
        {
            m_ShipArrivedQueue = new NativeQueue<Entity>(Allocator.Persistent);
        }

        protected override void OnDestroyManager()
        {
            m_ShipArrivedQueue.Dispose();
            base.OnDestroyManager();
        }

        protected override JobHandle OnUpdate(JobHandle inputDeps)
        {
            if (m_Ships.Length == 0)
                return inputDeps;

            //在 IJobParallelFor 中的逻辑
            var handle = new CalculatePositionsJob
            {
                Ships = m_Ships.Data,
                Planets = m_Planets.Data,
                TargetPlanet = GetComponentDataFromEntity<PlanetData>(),
                DeltaTime = Time.deltaTime,
                Entities = m_Ships.Entities,
                Positions = m_Ships.Positions,
                Rotations = m_Ships.Rotations,
                ShipArrivedQueue = m_ShipArrivedQueue.ToConcurrent()//并发
            }.Schedule(m_Ships.Length, 32, inputDeps);

            //在 IJob 中执行的逻辑
            handle = new ShipArrivedTagJob
            {
                //自动执行的队列
                EntityCommandBuffer = m_EndFrameBarrier.CreateCommandBuffer(),
                ShipArrivedQueue = m_ShipArrivedQueue
            }.Schedule(handle);

            return handle;
        }
    }
}

解析一

筛选指定的飞船和星球实体

解析二

因为是IJobParalleFor,所以队列用的事并行版本

解析三

通过多核并发计算出飞船的位移,然后对接触到目标的飞船添加到已经到达的队列中

解析四

单核心处理需要添加到达标记的队列

解析五

需要注意的事声明时不是NativeQueue<Entity>.Concurrent这种带有【Concurrent】类型需要多核心并行执行时需要在赋值时用【.ToConcurrent()】转换下。

EntityCommandBuffer我理解的是这种数据结构,适合并发且安全的不断改变数据长度的缓冲。且可有效较少因为不断的改变造成GC。


创建脚本 ShipMovementSystem ,作用:

  • 处理被添加ShipArrivedTag的飞船
  • 处理星球被攻击到达一定程度时的转换颜色逻辑
using Data;
using Unity.Collections;
using Unity.Entities;
using Unity.Rendering;

namespace Systems
{
    public class ShipArrivalSystem : ComponentSystem
    {
        EntityManager _entityManager;

        //初始化
        public ShipArrivalSystem()
        {
            _entityManager = World.Active.GetOrCreateManager<EntityManager>();
        }

        //到达星球的飞船
        struct Ships
        {
            public readonly int Length;
            public ComponentDataArray<ShipData> Data;
            public EntityArray Entities;
            public ComponentDataArray<ShipArrivedTag> Tag;
        }
        [Inject]
        Ships _ships;

        protected override void OnUpdate()
        {
            if (_ships.Length == 0)
                return;

            var arrivingShipTransforms = new NativeList<Entity>(Allocator.Temp);
            var arrivingShipData = new NativeList<ShipData>(Allocator.Temp);

            for (var shipIndex = 0; shipIndex < _ships.Length; shipIndex++)
            {
                var shipData = _ships.Data[shipIndex];
                var shipEntity = _ships.Entities[shipIndex];
                arrivingShipData.Add(shipData);
                arrivingShipTransforms.Add(shipEntity);
            }

            HandleArrivedShips(arrivingShipData, arrivingShipTransforms);

            arrivingShipTransforms.Dispose();
            arrivingShipData.Dispose();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="arrivingShipData">到达飞船的数据集合</param>
        /// <param name="arrivingShipEntities">到达飞船的实体集合</param>
        void HandleArrivedShips(NativeList<ShipData> arrivingShipData, NativeList<Entity> arrivingShipEntities)
        {
            //逐一遍历所有飞船数据
            for (var shipIndex = 0; shipIndex < arrivingShipData.Length; shipIndex++)
            {

                var shipData = arrivingShipData[shipIndex];
                //获取对应飞船需要攻击的星球
                var planetData = _entityManager.GetComponentData<PlanetData>(shipData.TargetEntity);

                //不同队伍减少
                if (shipData.TeamOwnership != planetData.TeamOwnership)
                {
                    planetData.Occupants = planetData.Occupants - 1;
                    if (planetData.Occupants <= 0)
                    {
                        //本地飞船没有时转换队伍
                        planetData.TeamOwnership = shipData.TeamOwnership;
                        PlanetSpawner.SetColor(shipData.TargetEntity, planetData.TeamOwnership);
                    }
                }
                else//相同队伍相加
                {
                    planetData.Occupants = planetData.Occupants + 1;
                }
                //星球重新赋值
                _entityManager.SetComponentData(shipData.TargetEntity, planetData);
            }
            //删除这些已经到达的飞船
            _entityManager.DestroyEntity(arrivingShipEntities);
        }
    }
}

解析一

获取所有到达飞船的实体集合 与对应的数据集合

解析二

根据得到的数据集合判断到达的星球是否为同一队伍,相同增加飞船,不同减少飞船,星球的飞船小于0变换队伍和颜色


为了增加互动性,添加脚本UserInputSystem

  • 左键点击需要发射的星球
  • 右键点击需要攻击的星球
  • 点击空白处取消
using System.Collections.Generic;
using System.Linq;
using Data;
using Other;
using Unity.Entities;
using UnityEngine;

namespace Systems
{

    public class UserInputSystem : ComponentSystem
    {
        struct Planets
        {
            public readonly int Length;
            public ComponentDataArray<PlanetData> Data;
        }
        [Inject] Planets planets;

        //添加?是表示可空类型,可自行Google
        Dictionary<GameObject, PlanetData?> FromTargets = new Dictionary<GameObject, PlanetData?>();
        GameObject ToTarget = null;

        EntityManager _entityManager;

        /// <summary>
        /// 构造函数中初始化 EntityManager
        /// </summary>
        public UserInputSystem()
        {
            _entityManager = World.Active.GetOrCreateManager<EntityManager>();
        }

        protected override void OnUpdate()
        {
            if (Input.GetMouseButtonDown(0))
            {
                var planet = GetPlanetUnderMouse();
                if (planet == null)
                {
                    FromTargets.Clear();
                    Debug.Log("点击外部,我们清除了选择");
                }
                else
                {
                    if (FromTargets.ContainsKey(planet))
                    {
                        Debug.Log("取消选择的目标Deselecting from target " + planet.name);
                        FromTargets.Remove(planet);
                    }
                    else
                    {
                        var data = PlanetUtility.GetPlanetData(planet, _entityManager);
                        //原来的目标
                        var previousTarget = FromTargets.Values.FirstOrDefault();
                        if ((previousTarget == null || previousTarget.Value.TeamOwnership == data.TeamOwnership) && data.TeamOwnership != 0)
                        {
                            Debug.Log("选择的目标 " + planet.name);
                            FromTargets[planet] = data;
                            Debug.Log("数量:" + FromTargets.Count);
                        }
                        else
                        {
                            if (data.TeamOwnership == 0)
                            {
                                Debug.LogWarning("不能设置中性行星");
                            }
                            else
                            {
                                Debug.Log("从目标中添加行星,但是清除之前的列表,因为它是一个不同的团队");
                                FromTargets.Clear();
                                FromTargets[planet] = data;
                            }
                        }

                    }
                }

            }
            if (Input.GetMouseButtonDown(1))
            {
                var planet = GetPlanetUnderMouse();
                if (planet == null)
                {
                    Debug.Log("取消选中目标 ");
                    ToTarget = null;
                }
                else
                {
                    if (!FromTargets.Any())
                    {
                        Debug.Log("没有任何选中的发射星球");
                        return;
                    }
                    Debug.Log($"需要攻击的星球名称为{planet.name}" );
                    ToTarget = planet;
                    foreach (var p in FromTargets.Keys)
                    {
                        if (p == ToTarget)
                            continue;
                        PlanetUtility.AttackPlanet(p, ToTarget, _entityManager);

                    }
                }
            }
        }

        GameObject GetPlanetUnderMouse()
        {
            RaycastHit hit;
            var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit, Mathf.Infinity, 1 << LayerMask.NameToLayer("Planet")))
            {
                return hit.collider.transform.gameObject;
            }
            return null;
        }
    }
}

为了保证逻辑能够按照指定顺序执行:添加顺序特性UpdateAfter
UserInputSystem--->ShipSpawnSystem
ShipArrivalSystem--->ShipMovementSystem

最终效果

打完收工~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容