您的位置:首页 > 移动开发 > Unity3D

Unity ECS+Jobs System笔记 访问数据3(七)

2019-08-19 19:55 1556 查看
原文链接:https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/index.html

来源:https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/index.html
我会对官方文档内容略作整理,有需要可以查看官方文档

4、ComponentSystem

你可以使用ComponentSystem处理数据,ComponentSystem在主线程上运行,因此不利用多个CPU内核,在以下情况下使用ComponentSystems:

  • 调试模式,有时更容易在主线程上观察代码运行时发生了什么,例如,可以记录Debug并绘制图形
  • 当系统需要访问只能在主线程上运行的API时,这可以帮助你逐步将游戏系统转换为ECS,而不必从一开始就重写所有内容
  • 系统执行的工作量所需的开销比使用Job系统少时

特别注意: 进行结构更改会强制完成所有作业,这一事件称为同步点,因为系统在等待同步点时无法利用所有可用的CPU内核所以可能导致性能下降。在ComponentSystem中,你应该使用一个post-update指令缓冲,虽然同步点仍然会出现,但所有结构变化都是批量发生的,因此影响略小。为了获得最大的运行效率,请使用JobComponentSystem和EntityCommandBuffer。在创建大量实体时,还可以使用单独的World创建实体,然后将这些实体传输到主游戏世界

通过ForEach进行迭代

ComponentSystem提供了Entities.ForEach函数,该函数简化了迭代一组实体的任务,在系统的OnUpdate()函数中调用ForEach,传入一个lambda函数,该函数会将相关组件作为参数并执行必要的工作
这有一个HelloCube的ForEach例子,为具有RotationQuaternion和RotationSpeed组件的实体设置旋转动画:

public class RotationSpeedSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.ForEach( (ref RotationSpeed rotationSpeed, ref RotationQuaternion rotation) =>
{
var deltaTime = Time.deltaTime;
rotation.Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * deltaTime));
});
}

ForEach的lambda函数有最多六种类型的组件作为参数进行使用
如果你需要改变现有实体的结构,你可以添加一个实体组件到你的lambda函数的参数上,然后使用它来向你的ComponentSystem的PostUpdateCommands缓冲添加指令(如果允许在lambda函数内部进行结构更改,则可能会更改正在迭代的数组中的数据,从而导致错误)
例如,如果要从旋转速度当前为零的任何实体中删除RotationSpeed组件,可以按如下方式更改ForEach函数:

Entities.ForEach( (Entity entity, ref RotationSpeed rotationSpeed, ref RotationQuaternion rotation) =>
{
var __deltaTime __= Time.deltaTime;
rotation.Value = math.mul(math.normalize(rotation.Value),
quaternion.AxisAngle(math.up(), rotationSpeed.RadiansPerSecond * __deltaTime__));

if(math.abs(rotationSpeed.RadiansPerSecond) <= float.Epsilon) //Speed effectively zero
PostUpdateCommands.RemoveComponent(entity, typeof(RotationSpeed));
});

OnUpdate()函数完成后,系统在更新后缓冲区后执行命令

Fluent Queries(快速查询)

你可以使用fluent-style查询来约束ForEach的lambda函数,使其只对那些满足某些约束的实体作用,这些查询可以指定是否对any、all或none性质的实体作用,约束可以链接在一起,对于C#的LINQ系统的用户来说应该非常熟悉
请注意,作为参数传递给ForEach的lambda函数的任何组件都会自动包含在WithAll中,并且不能显式包含在WithAll,WithAny或WithNone部分中

  • 一个WithAll约束可以指定拥有所有组件的实体,例如在下面这个查询中,ComponentSystem为具有“Rotation”和“Scale”组件的所有实体执行lambda函数
Entities.WithAll<Rotation, Scale>().ForEach( (Entity e) =>
{
// do stuff
});

将WithAll用于必须存在于实体上但不需要读取或写入的组件(添加一个作为访问ForEach的lambda函数的参数所需要的组件),例如:

Entities.WithAll<SpinningTag>().ForEach( (Entity e, ref Rotation r) =>
{
// do stuff
});
  • 一个WithAny约束可以指定在组件中至少拥有一个组件的实体,例如:ComponentSystem为同时具有Rotation和Scale组件,以及具有RenderDataA或RenderDataB(或两者)的实体执行以下lambda函数:
Entities.WithAll<Rotation, Scale>().WithAny<RenderDataA, RenderDataB>().ForEach( (Entity e) =>
{
// do stuff
});

请注意,无法通过WithAny知道一个指定实体有哪些组件,如果需要根据存在的组件来分别处理实体,则必须为每种情况创建特定查询,或者将JobComponentSystem与IJobChunk一起使用

  • 一个WithNone约束可以排除在组件中至少拥有一个组件的实体,例如:ComponentSystem为没有Rotation组件的所有实体执行以下lambda函数:
Entities.WithNone<Rotation>().ForEach( (Entity e) =>
{
// do stuff
});

此外,你也可以通过指定WithAnyReadOnly和WithAllReadOnly来筛选那些组件,但是要确保它们是只读组件,这将确保它们没有标记在写入和ID被更改的时候

查询选项

你还可以对With设置一些额外选项:

条件 描述
Default 默认
IncludePrefab 该查询不会隐式的排除具有Prefab组件的实体
IncludeDisabled 该查询不会隐式的排除具有Disabled(已禁用)组件的实体
FilterWriteGroup 查询会根据查询中指定的组件的WriteGroupAttribute属性筛选所选实体

ComponentSystem查询那些没有Rotation组件,包括Disabled(被禁用)的实体:

Entities.WithNone<Rotation>().With(EntityQueryOptions.IncludeDisabled).ForEach( (Entity e) =>
{
// do stuff
});

5、Manual iteration

你还可以在NativeArray中显式请求所有chunk,并使用诸如IJobParallelFor来处理它们的Job,如果你需要以某种方式管理chunk,而不是简单地迭代EntityQuery中的所有chunk,则建议使用此方法。如:

public class RotationSpeedSystem : JobComponentSystem
{
[BurstCompile]
struct RotationSpeedJob : IJobParallelFor
{
[DeallocateOnJobCompletion] public NativeArray<ArchetypeChunk> Chunks;
public ArchetypeChunkComponentType<RotationQuaternion> RotationType;
[ReadOnly] public ArchetypeChunkComponentType<RotationSpeed> RotationSpeedType;
public float DeltaTime;

public void Execute(int chunkIndex)
{
var chunk = Chunks[chunkIndex];
var chunkRotation = chunk.GetNativeArray(RotationType);
var chunkSpeed = chunk.GetNativeArray(RotationSpeedType);
var __instanceCount __= chunk.Count;

for (int i = 0; i < instanceCount; i++)
{
var rotation = chunkRotation[i];
var speed = chunkSpeed[i];
rotation.Value = math.mul(math.normalize(rotation.Value), quaternion.AxisAngle(math.up(), speed.RadiansPerSecond * DeltaTime));
chunkRotation[i] = rotation;
}
}
}

EntityQuery m_group;

protected override void OnCreate()
{
var query = new EntityQueryDesc
{
All = new ComponentType[]{ typeof(RotationQuaternion), ComponentType.ReadOnly<RotationSpeed>() }
};

m_group = GetEntityQuery(query);
}

protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var rotationType = GetArchetypeChunkComponentType<RotationQuaternion>();
var rotationSpeedType = GetArchetypeChunkComponentType<RotationSpeed>(true);
var chunks = m_group.CreateArchetypeChunkArray(Allocator.__TempJob__);

var rotationsSpeedJob = new RotationSpeedJob
{
Chunks = chunks,
RotationType = rotationType,
RotationSpeedType = rotationSpeedType,
DeltaTime = Time.deltaTime
};
return rotationsSpeedJob.Schedule(chunks.Length,32,inputDeps);
}
}

在ComponentSystem中手动迭代

虽然不是推荐做法,但可以使用EntityManager类手动迭代实体或块,而这些迭代方法应该只用于测试或调试代码(或者只是用于测试)或者是在一个孤立的World中——在这个世界中你可以的完美控制所有实体
例如,以下这段代码遍历当前世界中的所有实体:

var entityManager = World.Active.EntityManager;
var allEntities = entityManager.GetAllEntities();
foreach (var entity in allEntities)
{
//...
}
allEntities.Dispose();

而这段代码遍历活动世界中的所有块:

var entityManager = World.Active.EntityManager;
var allChunks = entityManager.GetAllChunks();
foreach (var chunk in allChunks)
{
//...
}
allChunks.Dispose();
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: