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