Unity Dots从入门到精通 Mono和Dots通讯
文章目录
- 前言
- 安装 DOTS 包
- Mono To Dots
- Dots To Mono
前言
DOTS(面向数据的技术堆栈)是一套由 Unity 提供支持的技术,用于提供高性能游戏开发解决方案,特别适合需要处理大量数据的游戏,例如大型开放世界游戏。
本文讲解我们的Mono代码部分,如何与Dots的System部分进行通讯。比如:交换数据,发送事件等。
- Unity 2022.3.52f1
- Entities 1.3.10
安装 DOTS 包
要安装 DOTS 包,请按照以下步骤操作:
(1)从菜单“窗口 → 包管理器”打开包管理器。
(2)搜索“ Entities” 并安装 Entities和Entities Graphics。
(3)搜索“ Universal RP” 并安装 Universal RP,并设置Graphics/Scriptable Render Pipeline Settings。
这会添加“实体”作为依赖项,并递归添加它所依赖的关联包( Burst、Collections、Jobs、Mathematics等)。
Mono To Dots
如何从Mono发送控制指令,比如创建士兵的命令到Dots中,让Dots创建指定的实体呢?
我们需要使用到DynamicBuffer,一个Dots中的消息Buffer队列,我们在Mono中可以通过World.EntityManager,获取这个共享的消息队列,并往里面添加消息数据,Dots只需要在System下接受数据并解析即可。
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
private EntityManager _entityManager;
private Entity _commandEntity;
private void Start()
{
Instance = this;
_entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
//创建一个专门用于存储游戏命令的"信箱实体",相当于在 ECS 世界中创建了一个命令接收站
_commandEntity = _entityManager.CreateEntity();
// 为该实体附加一个可动态增长的缓冲区,用于存储多个 GameCommand 命令
_entityManager.AddBuffer<GameCommand>(_commandEntity);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha1))
AddSpawnCommand(Faction.Red, SolderType.Melee);
}
void AddSpawnCommand(Faction faction, SolderType solder, int num = 1)
{
DynamicBuffer<GameCommand> buffer = _entityManager.GetBuffer<GameCommand>(_commandEntity);
// 解析网络数据为命令(示例)
var command = new GameCommand
{
CMD = CommandType.SpawnUnit,
Faction = faction,
Solder = solder,
Number = num
};
buffer.Add(command);
}
//UnitSpanerSystem系统类
public partial struct UnitSpawnerSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<EndSimulationEntityCommandBufferSystem.Singleton>();
state.RequireForUpdate<EntitiesReferences>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var ecbSingleton = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbSingleton.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter();
var entitiesReferences = SystemAPI.GetSingleton<EntitiesReferences>();
var job = new UnitSpawnerJob
{
EntitiesReferences = entitiesReferences,
ECB = ecb
};
var jobHandle = job.ScheduleParallel(state.Dependency);
jobHandle.Complete();
}
}
//UnitSpawnerJob类,用来实现多线程执行任务
[BurstCompile]
public partial struct UnitSpawnerJob : IJobEntity
{
public EntityCommandBuffer.ParallelWriter ECB;
public EntitiesReferences EntitiesReferences;
public void Execute([EntityIndexInQuery] int index, ref DynamicBuffer<GameCommand> buffer)
{
//buffer中会接收到来自Mono端的消息
foreach (GameCommand cmd in buffer)
{
ProcessCommand(index, cmd);
}
buffer.Clear();
}
//解析命令
private void ProcessCommand(int index, GameCommand cmd)
{
switch (cmd.CMD)
{
case CommandType.SpawnUnit:
var prefabEntity = GetPrefab(cmd);
float3 startPosition = GetBornPos(cmd);
float3 targetPosition = GetTargetPos(cmd);
Entity zombieEntity = ECB.Instantiate(index, prefabEntity);
ECB.SetComponent(index, zombieEntity, LocalTransform.FromPosition(startPosition));
ECB.SetComponent(index, zombieEntity, new UnitMover
{
moveSpeed = 2,
rotationSpeed = 2,
targetPosition = targetPosition
});
break;
case CommandType.DestroyUnit:
break;
case CommandType.ChangeTeam:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
Dots To Mono
从Dots把消息发送给Mono层,比如我们的Dots系统中,有士兵死亡,或者游戏的成功/失败,我们需要通知Mono层的主UI更新界面显示等。我们需要用到从Dots往Mono层发送事件。
新建一个数据结构,用来从Dots往Mono发送的消息结构体
public struct RcvData
{
public Faction faction;
public int type;
public float value;
public RcvData(Faction faction,int type,float value)
{
this.faction = faction;
this.type = type;
this.value = value;
}
}
新建一个全局的共性数据队列静态类Facade
它主要维护了一个NativeQueue 的共享队列
public static class Facade
{
public static NativeQueue<RcvData> SharedQueue { get; } = new(Allocator.Persistent);
public static void Cleanup()
{
if (SharedQueue.IsCreated)
{
SharedQueue.Dispose();
}
}
}
比如我们的阵营有一个血量,血量 变化会通知Mono端的UGUI更新界面
public partial struct HealthSystem : ISystem
{
private NativeQueue<RcvData>.ParallelWriter _writer;
private EntityCommandBuffer.ParallelWriter _ecb;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<EndSimulationEntityCommandBufferSystem.Singleton>();
_writer = Facade.SharedQueue.AsParallelWriter();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
_ecb = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>()
.CreateCommandBuffer(state.WorldUnmanaged).AsParallelWriter();
var job = new HealthJob
{
Writer = _writer,
ECB = _ecb,
};
var jobHandle = job.ScheduleParallel(state.Dependency);
jobHandle.Complete(); // 确保Job执行完成
}
}
[BurstCompile]
public partial struct HealthJob : IJobEntity
{
public NativeQueue<RcvData>.ParallelWriter Writer;
public EntityCommandBuffer.ParallelWriter ECB;
public void Execute([EntityIndexInQuery] int index,ref Health health)
{
float healthNormalized = health.healthAmount / (float)health.healthAmountMax;
//发送数据到UI层
Writer.Enqueue(new RcvData(health.faction, 1, healthNormalized));
}
}
Mono端,我们如何接受这个数据呢?
//Update 检测Dots事件
private void Update()
{
QueryDotsEvent();
}
void QueryDotsEvent()
{
while (Facade.SharedQueue.TryDequeue(out RcvData value))
{
// 主线程安全读取
switch (value.type)
{
case 1:
UIManager.Instance.SetHP(value.faction, value.value);
break;
case 2:
Money += (int)value.value;
UIManager.Instance.SetMoney(value.faction, Money);
break;
}
}
}
//销毁时,注意释放共享队列
private void OnDestroy()
{
Facade.Cleanup();
}
好了,这样我们就实现了Mono和Dots的双向数据通讯
应该还有别的更好的方法,欢迎大家的留言交流