添加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); }
} }
|
这样我们运行,在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必须使用RefRO
、RefRW
来界定,对应地,如果想在代码中引用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) { 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) { 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; 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>(); 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>
,这样就能保证唯一性,问题就能得到解决了。