您现在的位置是:首页 > 文章详情

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

日期:2018-09-26点击:416

书接上回: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; } } } 

解析一

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

img_8afdcdcd2f70586d16ee433dd9392e2f.png

解析二

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

img_a37152fe916c1140cef3032d8ffbccef.png

解析三

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

img_8a4e51bea085d4dfa9013cc125cc65a6.png

解析四

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

img_3ac7928f355c04b2d8a323d7c06724a3.png

解析五

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

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

img_a1af015c5b56eb529137a25c3df2dfa8.png

创建脚本 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); } } } 

解析一

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

img_958cb497c45e6b96926c03b2a8fb4ccb.png

解析二

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

img_404be2e4ddeb67e21936d8ef64eb9acc.png

为了增加互动性,添加脚本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

img_091879237ec539473362c7f337ddfa50.png
img_ea80e1f2a99c334aed9a976e3feb0012.png

最终效果

img_2b174266e47c573b5e6957c742eb7eb1.gif

打完收工~~~

img_569463b7983f645f063dd7b1bef93e0c.png
原文链接:https://yq.aliyun.com/articles/666163
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章