控制组件状态关闭/开启

在平时开发的需求中,我们可能会遇到需要单独控制某一个组件开启关闭,我们可以将其从 entity 上面 Add/Remove ,但是这其实是一个 Structural changes (即会导致 unity 重新组织 chunk 或者 chunk 的内容的操作)。这是非常耗时的,因此我们就有了需要开启/关闭组件状态的需求。

RotationSpeedAuthoring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
namespace HelloCube.EnableableComponents
{
public class RotationSpeedAuthoring : MonoBehaviour
{
public bool StartEnable;
public float DegreesPerSecond = 360.0f;

public class Baker : Baker<RotationSpeedAuthoring>
{
public override void Bake(RotationSpeedAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.Dynamic | TransformUsageFlags.NonUniformScale);

AddComponent(entity, new RotationSpeed
{
RadiansPerSecond = math.radians(authoring.DegreesPerSecond)
});
SetComponentEnabled<RotationSpeed>(entity, authoring.StartEnable);
}
}
}

struct RotationSpeed : IComponentData, IEnableableComponent
{
public float RadiansPerSecond;
}
}

第一个改动是 authoring,主要有三个改动的点:

  • 添加了一个变量 StartEnbale 来控制开始的时候是否启用组件
  • Bake 的最后使用 SetComponentEnabled 来设置了组件状态
  • RotationSpeed 实现了一个新的接口 IEnableableComponent

RotationSystem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
namespace HelloCube.EnableableComponents
{
public partial struct RotationSystem : ISystem
{
private float timer;
private const float interval = 1.3f;

[BurstCompile]
public void OnCreate(ref SystemState state)
{
timer = interval;
state.RequireForUpdate<Execute.EnableableComponents>();
}

[BurstCompile]
public void OnUpdate(ref SystemState state)
{
float deltaTime = SystemAPI.Time.DeltaTime;
timer -= deltaTime;

// 切换 RotationSpeed 组件的状态
if (timer < 0)
{
foreach (var rotationSpeedEnabled in SystemAPI.Query<EnabledRefRW<RotationSpeed>>()
.WithOptions(EntityQueryOptions.IgnoreComponentEnabledState))
{
rotationSpeedEnabled.ValueRW = !rotationSpeedEnabled.ValueRO;
}

timer = interval;
}

foreach (var (transform, speed) in
SystemAPI.Query<RefRW<LocalTransform>, RefRO<RotationSpeed>>())
{
transform.ValueRW = transform.ValueRO.RotateY(speed.ValueRO.RadiansPerSecond * deltaTime);
}
}
}
}

第二处改动位于 RotationSystem,此处实现了应该小的计时器,也就是每次组件停用/启动时的间隔。其他的代码基本上不用管,直接看 if (timer < 0) 里面的组件状态切换逻辑。此处有两个值得注意的地方:

  • EnableRefRW 查询的是 RotationSpeed 组件的 Enable 状态值而不是其组件本身功能。
  • 后面添加了一个 EntityQueryOptions.IgnoreComponentEnabledState 的 option 来确保不会只查询到 Enable 已启用的组件,而忽略了没有启用的组件。