当前位置: 首页 > article >正文

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的双向数据通讯
应该还有别的更好的方法,欢迎大家的留言交流


http://www.kler.cn/a/580877.html

相关文章:

  • DOCKER模式部署GITLAB
  • 回溯-子集
  • Java集合_八股场景题
  • vue2动态增删表单+表单验证
  • WPF预览并打印FlowDocument
  • Python数据分析之数据处理与分析
  • 修改 Docker 网桥的 IP 范围
  • Oracle RAC配置原理详解:构建高可用与高性能的数据库集群
  • HTML 超链接(简单易懂较详细)
  • NO.29十六届蓝桥杯备战|string九道练习|reverse|翻转|回文(C++)
  • AI算法与应用 全栈开发 前端开发 后端开发 测试开发 运维开发
  • 【阿里云】操作系统控制台——体验与测评
  • c#面试题整理3
  • 探索高性能AI识别和边缘计算 | NVIDIA Jetson Orin Nano 8GB 开发套件的全面测评
  • FreeRTOS第18篇:FreeRTOS链表实现细节06_遍历指针(pxIndex)与调度器的高效协同
  • 2路模拟量同步输出卡、任意波形发生器卡—PCIe9100数据采集卡
  • Flutter中网络图片加载显示Image.network的具体用法
  • [免费]微信小程序(图书馆)自习室座位预约管理系统(SpringBoot后端+Vue管理端)(高级版)【论文+源码+SQL脚本】
  • Vue前端开发-Coupon组件
  • 时序数据库 InfluxDB 3.0 版本性能实测报告:写入吞吐量提升效果验证