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
最终效果
打完收工~~~
关注公众号
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
Unity 之 Pure版Entity Component System【ECS】 案例【GalacticConquest】解析【上】
以往没有了解过Unity ECS的小伙伴建议先看看我写过的两篇ECS文章 Unity之浅析 Entity Component System (ECS) Unity 之 Pure版Entity Component System (ECS) 官方Rotation示例解析 最近两月Unity官方一直在更新ECS的版本,有一些原来的工程在新的版本中是无法运行的,所以今天再写一篇示例解析,虽然ECS目前是测试版本,可能还会有很多的改变,正式版本上线的日期也没有明确说明,但还是希望能帮助喜欢新技术的小伙伴,互相帮助,互相学习~ 有说的不准确和不正确的地方欢迎留言指正 大家的帮助是我写下去最有效的动力 点击下载工程 示例效果展示如下 这个示例的规则是这样的,启动时随机生成大小位置不同的球体,然后从球体周围发射小飞船去攻击其他的星球,飞船分为红绿两队,占领后星球变成指定队伍的颜色 此次使用的Unity版本为 2018.2.9f1 Entities版本为0.0.12-preview.15。而且在启动Unity加载Entities的时候保持网路畅通,因为有朋友反映在内网无法使用Entities的情况。【最...
-
下一篇
timed out waiting for to be synced
程序操作kubernetes资源时, 没有等旧的资源删除完毕就立即创建了同名的新资源, 导致出现了一些错误. 具体表现为kubernetes命令行删除StatefulSet时报错, timed out waiting for "mysql" to be synced, 删除多次卡住, 然后报这个超时错误. 查看这个StatefulSet: root@kub3:~# kubectl -n admin-d2069c get statefulset mysql -o yaml ... spec: podManagementPolicy: OrderedReady replicas: 0 revisionHistoryLimit: 10 selector: matchLabels: app: mysql appname: mysql name: mysql serviceName: mysql template: metadata: creationTimestamp: null labels: app: mysql appname: mysql name: mysql nam...
相关文章
文章评论
共有0条评论来说两句吧...

微信收款码
支付宝收款码