Unity ECS+Jobs System笔记 案例解析3(十二)

本章节为大家解析ECS案例,资源来自,大家自行获取:
https://github.com/Unity-Technologies/EntityComponentSystemSamples

5、SubScene——新的场景工作流

Unity ECS+Jobs System笔记 案例解析3(十二)
这个案例演示了SubScene的工作流,SubScene提供了一个更加有效的在Unity内编辑和加载场景的方式
这个案例使用了HelloCube中IJobForEach的Components和Systems,这个场景包括了一对正在旋转的Cube,而它们是在开始之后自动从一个SubScene中加载的

  • 当你保存一个场景,Unity会将SubScene转化成一个原生的二进制格式,这个格式对内存而言是随时可以调度的,同时在RAM中也可以最小的数据更改量被加载和传输,该格式非常适合流式传输大量实体。
  • 你可以在开始之后自动加载一个Sub Scene;你也可以延迟加载知道你从你的代码中流式传输SubScene(使用RequestSceneLoaded组件)
  • 在默认情况下,即使在编辑器模式下,SubScene也是通过Entity二进制文件被加载
  • 当你选择一个SubScene然后在Unity的Inspector点击Edit按钮对该场景进行编辑,在编辑的时候,你可以在Scene界面中看到Entities的层级结构,同时也能像之前一样对其进行编辑,一个"实时链接"转换管线可以应用任何你对Scene的改变
  • 仅编辑场景的一部分并且仍然加载其他子场景的实体的功能,创建了一个可用于编辑大量场景的可扩展的工作流程

创建方式

在Unity的Hierarchy面板创建一个GameObject,添加SubScene组件,在SceneAsset中选择需要加载的场景,然后点击Rebuild Entity Cache(如果有警告就点击Clear),通过Edit按钮可以进入便编辑模式,按Close退出,使用Save保存场景,使用Load和Unload选择是否加载资源

6、SpawnFromMonoBehaviour

 	public GameObject Prefab;
    public int CountX = 100;
    public int CountY = 100;

    void Start()
    {
        Entity prefab = GameObjectConversionUtility.ConvertGameObjectHierarchy(Prefab, World.Active);
        EntityManager entityManager = World.Active.EntityManager;

        for (var x = 0; x < CountX; x++)
        {
            for (var y = 0; y < CountY; y++)
            {
                var instance = entityManager.Instantiate(prefab);

                float3 position = transform.TransformPoint(new float3(x * 1.3F, noise.cnoise(new float2(x, y) * 0.21F) * 2, y * 1.3F));
                entityManager.SetComponentData(instance, new Translation {Value = position});
            }
        }
    }

这个案例和案例2一样,通过GameObjectConversionUtility.ConvertGameObjectHierarchy函数将Prefab转化成Entity,然后使用EntityManager克隆

7、SpawnFromEntity——之后还会研究

这个案例是通过生成一个Entity,然后使用这个Entity的组件数据来生成其他Entity,可以说第一个生成的实体是一个实体生成器

7.1、组件

using Unity.Entities;
public struct Spawner_FromEntity : IComponentData
{
    public int CountX;
    public int CountY;
    public Entity Prefab;
}

组件中的数据分别代表生成的位置和预制体,这很简单

7.2、预制体生成器

using System;
using System.Collections.Generic;
using Unity.Entities;
using UnityEngine;

[RequiresEntityConversion]
public class SpawnerAuthoring_FromEntity : MonoBehaviour, IDeclareReferencedPrefabs, IConvertGameObjectToEntity
{
    public GameObject Prefab;
    public int CountX;
    public int CountY;
    public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
    {
        referencedPrefabs.Add(Prefab);
    }

实现IDeclareReferencedPrefabs接口,预先声明了Prefab的参考,让转换系统能够预先知道它

    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        var spawnerData = new Spawner_FromEntity
        {
            Prefab = conversionSystem.GetPrimaryEntity(Prefab),
            CountX = CountX,
            CountY = CountY
        };
        dstManager.AddComponentData(entity, spawnerData);
    }
}

因为需要引用的Prefab是基于DeclareReferencedPrefabs的,所以这里我们简单地将游戏对象映射到该预制件的引用上
并且这边会因为Convert To Entity组件调用Convert函数,然后添加上7.1中的组件

7.3、生成系统

using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;

这里首先要说明一下,JobComponentSystems可以在工作线程上运行,但是添加和删除实体的操作只能在主线程上执行——为了避免线程上的冲突,因此在这里需要使用EntityCommandBuffer(实体指令缓冲)来延迟进行这些操作

[UpdateInGroup(typeof(SimulationSystemGroup))]

我们把这个Job放入SimulationSystemGroup组中

public class SpawnerSystem_FromEntity : JobComponentSystem
{
    BeginInitializationEntityCommandBufferSystem m_EntityCommandBufferSystem;
    protected override void OnCreate()
    {
        m_EntityCommandBufferSystem = World.GetOrCreateSystem<BeginInitializationEntityCommandBufferSystem>();
    }

BeginInitializationEntityCommandBufferSystem可以创建一个命令缓存系统在阻塞系统被执行的时候运行,虽然这个初始化命令在SpawnJob中被记录下来,但是它并不会真正地被执行直到相应的实体命令缓存系统更新

(可能不太恰当)我现在缓存了一个“给我苹果”的操作,直到我执行了“我想吃苹果”这个操作,我发现我没有苹果,我就会执行缓存的操作,然后我就有苹果吃了

那么我们就得先声明变量并初始化

    struct SpawnJob : IJobForEachWithEntity<Spawner_FromEntity, LocalToWorld>
    {
        public EntityCommandBuffer.Concurrent CommandBuffer;

声明实体指令缓冲

        public void Execute(Entity entity, int index, [ReadOnly] ref Spawner_FromEntity spawnerFromEntity, [ReadOnly] ref LocalToWorld location)
        {
            for (var x = 0; x < spawnerFromEntity.CountX; x++)
            {
                for (var y = 0; y < spawnerFromEntity.CountY; y++)
                {
                    var instance = CommandBuffer.Instantiate(index, spawnerFromEntity.Prefab);

                    var position = math.transform(location.Value,
                        new float3(x * 1.3F, noise.cnoise(new float2(x, y) * 0.21F) * 2, y * 1.3F));
                    CommandBuffer.SetComponent(index, instance, new Translation {Value = position});
                }
            }
            CommandBuffer.DestroyEntity(index, entity);
        }
    }

缓冲“生成预制体,然后销毁自己”这个指令

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        var job = new SpawnJob
        {
            CommandBuffer = m_EntityCommandBufferSystem.CreateCommandBuffer().ToConcurrent()
        }.Schedule(this, inputDeps);
        m_EntityCommandBufferSystem.AddJobHandleForProducer(job);
        return job;
    }
}

将缓冲指令作为参数传入,然后在缓冲区添加Job句柄

4.4、小结

  • 为了避免直接的结构更改(增删操作),可以将这个命令加入到EntityCommandBuffer
  • 使用命令缓存可以在执行增删操作之后再执行任何消耗大的计算
  • 目前该任务在被阻塞之前时没有同步点的
  • 当阻塞系统执行时,我们要告诉阻塞系统——在阻塞系统能继续执行前需要完成什么

8、SpawnAndRemove

这个案例是之前的综合案例,非常类似案例7,重点在于LifeTime,也就是在LifeTime小于0之后销毁实例,其实也是通过命令缓冲执行的,这里就不再赘述了

上一篇:KNE240 Reliability Engineering 2019


下一篇:Android Service的两种启动方式