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

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

相关文章:

  • 论文阅读笔记——π0: A Vision-Language-Action Flow Model for General Robot Control
  • 【GPT入门】第14课 openai调用高德地图案例实现多轮会话与多轮接口调用
  • ai之qwq 32B部署在 linux 与拓展使用在web参考
  • 【编译器】VSCODE搭建ESP32-C3
  • 【C++ vector 使用教程】
  • [通讯协议]485通信
  • DeepSeek:中国AGI破局者的技术革命与生态重构
  • 原生稀疏注意力NSA详解及代码复现
  • C++中的析构函数
  • 【cocos creator】热更新
  • SQL注入目录【绕过+布尔时间脚本】
  • 【从零开始学习计算机科学】计算机组成原理(六)异常事件处理
  • Manus无需邀请码即可使用的平替方案-OpenManus实测
  • 利用FatJar彻底解决Jar包冲突(一)
  • 【系统设计架构师】特定领域软件体系结构
  • MyBatis Mapper 接口的作用,以及如何将 Mapper 接口与 SQL 映射文件关联起来
  • 周鸿祎开始补录网安岗了
  • 2022IJCAI速读:SparseTT,使用稀疏Transformers进行视觉跟踪
  • EngineerCMS完整版发布,带freecad、math和mapus例子
  • es6+新增特性有哪些
  • 证监会:证券公司要处理好功能性和盈利性关系,切实维护好投资者利益
  • 特朗普与普京开始进行电话会谈,稍后还将致电泽连斯基
  • 世卫大会连续9年拒绝涉台提案
  • 和平会谈两天后,俄对乌发动冲突爆发以来最大规模无人机袭击
  • 慢品巴陵,看总编辑眼中的岳阳如何书写“山水人文答卷”
  • 福建、广西等地有大暴雨,国家防总启动防汛四级应急响应