添加ASpect

Query联合查询

接上一节,我们需要应用之前创建的Speed Component,可以这样写代码

MovingSystemBase:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;

public partial class MovingSystemBase : SystemBase
{
protected override void OnUpdate()
{
foreach ((TransformAspect transformAspect,Speed speed) in SystemAPI.Query<TransformAspect,Speed>())
{
transformAspect.LocalPosition += new float3(SystemAPI.Time.DeltaTime * speed.value, 0, 0);

}
//可选写法
//foreach (var transformAspect in SystemAPI.Query<TransformAspect, Speed>())
//{
// transformAspect.Item1.LocalPosition += new float3(SystemAPI.Time.DeltaTime * transformAspect.Item2.value, 0, 0);

//}
}
}

这样我们运行,在Speed Authoring的脚本上修改Speed,Entity的移动速度能就及时修改。

Unity Entities的Component大多数为struct,为此Unity提供了RefRW(reference Read Write)和RefRO(reference ReadOnly)两个工具Struct,能避免struct值在内存中总是复制而导致的一些问题。我们可以在SystemAPI.Query中直接使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;

public partial class MovingSystemBase : SystemBase
{
protected override void OnUpdate()
{
foreach ((TransformAspect transformAspect,RefRO<Speed> speed) in SystemAPI.Query<TransformAspect,RefRO<Speed>>())
{
transformAspect.LocalPosition += new float3(SystemAPI.Time.DeltaTime * speed.ValueRO.value, 0, 0);

}
}
}

添加TargetPosition Component

在Scripts文件夹中新建TargetPosition脚本:

1
2
3
4
5
6
using Unity.Entities;
using Unity.Mathematics;
public struct TargetPosition : IComponentData
{
public float3 value;
}

在Scripts文件夹下新建TargetPositionAuthoring脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using UnityEngine;
using Unity.Mathematics;
using Unity.Entities;

public class TargetPositionAuthoring : MonoBehaviour
{
public float3 targetPosition;
}
public class TargetPositionBaking : Baker<TargetPositionAuthoring>
{
public override void Bake(TargetPositionAuthoring authoring)
{
AddComponent(new TargetPosition() { value = authoring.targetPosition});
}
}

将TargetPositionAuthoring脚本挂载在之前的“GameObject”上。

添加MoveToPositionAspect

Create an aspect | Entities | 1.0.0-pre.15 (unity3d.com)

我们之前说过,Aspect是Component子集的新集合,能够定义一些方法,来简化各种System里的逻辑,在这里演示一下创建方法:

在Scripts文件夹中新建MoveToPositionAspect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;

public readonly partial struct MoveToPositionAspect : IAspect
{
private readonly Entity entity;

private readonly TransformAspect transformAspect;
private readonly RefRO<Speed> speed;
private readonly RefRW<TargetPosition> targetPosition;

public void Move(float deltaTime)
{
float3 direction = math.normalize(targetPosition.ValueRW.value - transformAspect.WorldPosition);

transformAspect.WorldPosition += direction * deltaTime * speed.ValueRO.value;
}
}

创建Aspect必须是“readonly partial struct

Aspect内部不能使用SystemAPI

在一个Aspect中可以声明一个Entity字段,用来访问对应的实体,注意,一个Aspect只能声明一个Entity字段,也就是说一个Aspect不能混合多个Entity的多个Component

在Aspect内声明的Component必须使用RefRORefRW来界定,对应地,如果想在代码中引用Aspect,需要使用in关键字或ref关键字,使用in关键字会让这个aspect内部所有的引用都变成readonly,而使用ref关键字则不会

如果想让自定义的Aspect在Entity Inspector(Runtime模式)的Aspect选项卡中出现,需要编写相应的编辑器脚本

修改MovingSystemBase,在这里将SystemAPI.Time.DeltaTime传递:

1
2
3
4
5
6
7
8
9
10
11
12
using Unity.Entities;

public partial class MovingSystemBase : SystemBase
{
protected override void OnUpdate()
{
foreach (MoveToPositionAspect moveToPositionAspect in SystemAPI.Query<MoveToPositionAspect>())
{
moveToPositionAspect.Move(SystemAPI.Time.DeltaTime);
}
}
}

为MoveToPositionAspect添加Random逻辑

我们希望每个Entity靠近targetPosition时,targetPosition再随机一个新位置。我们使用Unity.Mathematics提供的random方法来进行随机。

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
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;

public readonly partial struct MoveToPositionAspect : IAspect
{
private readonly Entity entity;

private readonly TransformAspect transformAspect;
private readonly RefRO<Speed> speed;
private readonly RefRW<TargetPosition> targetPosition;

public void Move(float deltaTime)
{
float3 direction = math.normalize(targetPosition.ValueRW.value - transformAspect.WorldPosition);

transformAspect.WorldPosition += direction * deltaTime * speed.ValueRO.value;

float reachedTargetDistance = .5f;
if( math.distance(transformAspect.WorldPosition,targetPosition.ValueRW.value) < reachedTargetDistance)
{
targetPosition.ValueRW.value = GetRandomPosition();//随机新位置
}
}

private float3 GetRandomPosition()//添加方法
{
var random = new Random(1);

return new float3(random.NextFloat(0f, 15f), 0, random.NextFloat(0f, 15f));
}
}

这样写是不对的,因为每次调用时都会用相同的seed创建random,这会导致每次返回的位置都相同。

我们需要想个办法,每次调用Random时,返回不同的数值。这里介绍一个使用SystemAPI.GetSingleton来制作全局Random生成器的方法,这样能够还原UnityEngine.Random那样的体验。

创建RandomComponent

在Scripts文件夹中新建RandomComponent文件

1
2
3
4
5
6
7
using Unity.Mathematics;
using Unity.Entities;

public struct RandomComponent : IComponentData
{
public Random random;
}

在Scripts文件夹中新建RandomComponentAuthoring文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using Unity.Entities;
using UnityEngine;

public class RandomComponentAuthoring : MonoBehaviour
{

}
public class RandomComponentBaker : Baker<RandomComponentAuthoring>
{
public override void Bake(RandomComponentAuthoring authoring)
{
AddComponent(new RandomComponent { random = new Unity.Mathematics.Random(1) });
}
}

在“TestUnitySubScene”内新建一个空对象,命名为“Random”,并挂载RandomComponentAuthoring

获取RandomComponent单例

我们先修改MoveToPositionAspect:

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
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;

public readonly partial struct MoveToPositionAspect : IAspect
{
private readonly Entity entity;

private readonly TransformAspect transformAspect;
private readonly RefRO<Speed> speed;
private readonly RefRW<TargetPosition> targetPosition;

public void Move(float deltaTime,Random random)//添加Random引用
{
float3 direction = math.normalize(targetPosition.ValueRW.value - transformAspect.WorldPosition);

transformAspect.WorldPosition += direction * deltaTime * speed.ValueRO.value;

float reachedTargetDistance = .5f;
if( math.distance(transformAspect.WorldPosition,targetPosition.ValueRW.value) < reachedTargetDistance)
{
targetPosition.ValueRW.value = GetRandomPosition(random);
}
}

private float3 GetRandomPosition(Random random)//添加Random引用
{
return new float3(random.NextFloat(0f, 15f), 0, random.NextFloat(0f, 15f));
}
}

我们再修改MovingSystemBase:

1
2
3
4
5
6
7
8
9
10
11
12
13
using Unity.Entities;

public partial class MovingSystemBase : SystemBase
{
protected override void OnUpdate()
{
var random = SystemAPI.GetSingleton<RandomComponent>().random;//使用SystemAPI.GetSingleton
foreach (MoveToPositionAspect moveToPositionAspect in SystemAPI.Query<MoveToPositionAspect>())
{
moveToPositionAspect.Move(SystemAPI.Time.DeltaTime,random);
}
}
}

我们运行一下,会发现物体移动到第二次之后就不会随机一个新位置了。

修改出现的问题

Unity.Mathematics.Random是一个struct,如果我们不做一些界定,每次引用它都会复制一个Random:

1
2
3
var random = SystemAPI.GetSingleton<RandomComponent>().random;//复制一次
public void Move(float deltaTime,Random random)//又复制了一次
private float3 GetRandomPosition(Random random)//又复制了一次

如果我们不做界定,第二次调用SystemAPI.GetSingleton这个方法时,它会使用复制的Random struct,也就是说每次一都会new 一个相同的Random。

要解决这个问题,我们需要换个思路,首先,我们使用SystemAPI.GetSingletonRW这个API替换之前的SystemAPI.GetSingleton,来保证RandomComponent的唯一性

修改MovingSystemBase:

1
2
3
4
5
6
7
8
9
10
11
12
13
using Unity.Entities;

public partial class MovingSystemBase : SystemBase
{
protected override void OnUpdate()
{
var randomComponent = SystemAPI.GetSingletonRW<RandomComponent>();//使用SystemAPI.GetSingletonRW
foreach (MoveToPositionAspect moveToPositionAspect in SystemAPI.Query<MoveToPositionAspect>())
{
moveToPositionAspect.Move(SystemAPI.Time.DeltaTime,randomComponent);
}
}
}

修改MoveToPositionAspect:

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
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;

public readonly partial struct MoveToPositionAspect : IAspect
{
private readonly Entity entity;

private readonly TransformAspect transformAspect;
private readonly RefRO<Speed> speed;
private readonly RefRW<TargetPosition> targetPosition;

public void Move(float deltaTime,RefRW<RandomComponent> randomComponent)
{
float3 direction = math.normalize(targetPosition.ValueRW.value - transformAspect.WorldPosition);

transformAspect.WorldPosition += direction * deltaTime * speed.ValueRO.value;

float reachedTargetDistance = .5f;
if( math.distance(transformAspect.WorldPosition,targetPosition.ValueRW.value) < reachedTargetDistance)
{
targetPosition.ValueRW.value = GetRandomPosition(randomComponent);
}
}

private float3 GetRandomPosition(RefRW<RandomComponent> randomComponent)
{
return new float3(
randomComponent.ValueRW.random.NextFloat(0f, 15f),
0,
randomComponent.ValueRW.random.NextFloat(0f, 15f)
);
}
}

我们全部都在引用RefRW<RandomComponent>,这样就能保证唯一性,问题就能得到解决了。