ecs中的parent

关于转换系统

ConvertToEntitySystem运行在默认世界中..每次update的时候会处理ConvertToEntity添加到system中的gameobject

最终调用的时候是使用.

1
GameObjectConversionUtility.Convert(gameObjectWorld);

然后具体的转换过程会新疆一个转换world主要执行GameObjectConversionMappingSystem,然后转换完成后销毁这个world

  • GameObjectConversionUtility.ConvertGameObjectHierarchy(unitView.gameObject, settings)会转换所有child.

  • GameObjectConversionMappingSystem.conversion.MappingSystem.AddGameObjectOrPrefab(root);会递归所有child

parent

localtoworld是一个矩阵

1
2
3
4
EntityManager.SetComponentData(myCube, new LocalToWorld
{
Value = new float4x4(rotation: quaternion.identity, translation:new float3(1,2,3))
});

如果人肉单纯修改 LocalToWorld 这个 4x4 Transform 矩阵将会无比痛苦, 所以 Unity 帮我们实现了一堆有用的系统来修改它:

img

使用Translation,Rotation,Scale/NonUniformScale组件将会极大简化我们的操作, 上图这些系统 会将这些组件的改动反应到 LocalToWorld 中.你甚至可以使用 Parent 来实现层级结构. 比如一个 Entity 的 Transform 为 0, 但同时有一个 Parent 组件指向了父 Entity, 那么最终该 Entity 的 LocalToWorld 值将会和父 Entity的 LocalToWorld 一样.

我们新建一个场景

image-20201116024147417

查看分析器.就能看到如下关系图

image-20201116024105067

  • CUBE child,physicsCollider

  • SPHER parent ,localtoparent ,previousparent

由于物理的转化规则,子物体没有physicscollider.

LinkedEntityGroup

LinkedEntityGroup是一个 dynamic buffer , 通常它会影响:

  • 调用Instantiate方法时, 会同时实例化所有 buffer 中的 entity, 同时也会创建相同的LinkedEntityGroup. 注意实例化并不一定和ECS中的Prefab component 直接关联.

  • 调用DestroyEntity时也会同时销毁 LinkedEntityGroup中的所有 entity. 类似在编辑器中删除GameObject

  • 调用 entityManager.SetEnabled 加上的 Disabledcomponent 会告知 ECS 的查询系统忽略它们, 而 LinkedEntityGroup 中的 entity 也会受到同样的影响. 有点类似禁用GameObject 时同时会禁用整个层级树.

注意如果buffer 中的 entity 也有LinkedEntityGroup, 系统不会递归地执行instantiation/destroy/disabled 过程.

这些过程在具体执行当中也有一些细微不同.InstantiateSetEnabled只要检测到 buffer 便在所有成员上一次性执行, 不会做其他更多事. 这意味着关联该 buffer 的 entity 必须要把自己包括在内才能正常工作**.** 然而DestroyEntity则无所谓, 因为它会先销毁传入的entity, 然后再迭代 buffer 中的 entity 进行销毁.

要注意LinkedEntityGroupParent并不一样 (虽然它们经常同时出现). 后者是递归地工作, 循环依赖也是不允许的.

从非 prefab conversion 中获取LinkedEntityGroup

目前, 我们使用ConvertToEntity并不能得到 LinkedEntityGroup. 所以当销毁转换后的 entity时, 并不能连锁地销毁相关联的entity(比如Child/Parent), 不管你是否认同, 目前这是默认的行为. (ConvertToEntity却可以正常得到基于Parent 的层级结构).

如果你需要该 buffer 正常添加, 在 mapping sysytem 中有一个方法可以使用:

1
public void DeclareLinkedEntityGroup(GameObject gameObject)

调用后 primary entity 会得到该 buffer, 并包含所有子对象(递归地查询, 线性排列的结果).

1
2
3
4
5
6
7
public class CubeConvert : MonoBehaviour, IConvertGameObjectToEntity
{
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
conversionSystem.DeclareLinkedEntityGroup(this.gameObject);
}
}

img

从 debbuger 现在可以看到已经正常添加了LinkedEntityGroup, 目前它已经支持InstantiateSetEnable 的正常工作了:

img

LinkedEntityGroup 总是会包含它自己, 包括 disable 这种情况. 因此如果你在叶子对象上禁用了 GameObject , 你也会得到包含它自己的LinkedEntityGroup.

关于手动添加LinkedEntityGroup

如果我添加了一个 conversion script 到 SpecialCube, 它的Convert 方法会在 CubeHead 被转换时调用, 该代码意图在于记住 Cube(3) 的 Entity:

img

1
2
3
4
5
6
7
8
9
10
public class SpecialCube : MonoBehaviour, IConvertGameObjectToEntity
{
public GameObject itsChild;
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponent<LinkedEntityGroup>(entity);
var leg = dstManager.GetBuffer<LinkedEntityGroup>(entity);
leg.Add(conversionSystem.GetPrimaryEntity(itsChild));
}
}

img

这时我们检查一下conversion 的Prefab entity 的结果(烘焙对于单个 Entity 或者 Entity buffer 都有效), 我们期望我们人工添加的 LinkedEntityGroup 包含对于 Prefab 同级的引用.

img

关于parent和LinkedEntityGroup

  • parent 涉及到localtoworld, 以及一些物理和其他的转换规则

  • linkedEntityGroup 和 Instantiate SetEnabled DestroyEntity 有关

被disable的组件不会被foreach到,除非主动申请选取disable

最佳的方式去关联entity和prefab

[Hybrid ECS] Best way to handle Convert and Inject Game Object Prefab? - Unity Forum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using UnityEngine;

public class Prefabs : MonoBehaviour
{
private static Prefabs m_Instance;

public static GameObject PlayerModel => m_Instance.playerModel;

public GameObject playerModel;

private void Awake()
{
if (m_Instance == null)
{
m_Instance = this;
}
else
{
Debug.LogError("Prefab Singleton loaded twice");
}
}
}
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.NetCode;
using Unity.Transforms;
using UnityEngine;

[UpdateInGroup(typeof(ClientSimulationSystemGroup))]

public class PlayerModelSystem : ComponentSystem
{
protected override void OnUpdate()
{
Entities.WithNone<Transform>().ForEach((Entity playerEntity, ref PlayerComponent playerComponent) => {
if (Prefabs.PlayerModel != null)
{
var playerModel = Object.Instantiate(Prefabs.PlayerModel);
EntityManager.AddComponentObject(playerEntity, playerModel.GetComponent<Transform>());
EntityManager.AddComponentData(playerEntity, new CopyTransformToGameObject());
}
});
}
}

Unity Tech - Where is the SIMPLE Bridge Between MonoBehaviours and ECS?? - Unity Forum

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using UnityEngine;

// Attach to your Game Object to be converted, the GameObjectPrefab is a pure Game Object with no conversion that may contain MonoBehaviour components such as the particle system.
public class GameObjectPrefabAuthoring : MonoBehaviour, IConvertGameObjectToEntity
{
public GameObject GameObjectPrefab;

public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponentObject(entity, new GameObjectPrefab
{
Value = GameObjectPrefab
});
}
}

public class GameObjectPrefab : IComponentData
{
public GameObject Value;
}

// Instantiate and destroy the referenced prefab when the entity is created or destroyed. You can even pool the Game Object.
public class GameObjectPrefabSystem : JobComponentSystem
{
public class GameObjectPrefabInstance : ISystemStateComponentData
{
public GameObject Value;
}

protected override JobHandle OnUpdate(JobHandle inputDeps)
{
Entities
.WithNone<GameObjectPrefabInstance>()
.ForEach((Entity entity, GameObjectPrefab gameObjectPrefab) =>
{
var gameObjectPrefabInstance = Object.Instantiate(gameObjectPrefab.Value);

EntityManager.AddComponentData(entity, new GameObjectPrefabInstance
{
Value = gameObjectPrefabInstance
});

// Just an example to make the GameObject Prefab instance follow the entity.
EntityManager.AddComponentObject(entity, gameObjectPrefabInstance.transform);
EntityManager.AddComponent<CopyTransformToGameObject>(entity);
})
.WithStructuralChanges()
.Run();

Entities
.WithNone<GameObjectPrefab>()
.ForEach((Entity entity, GameObjectPrefabInstance gameObjectPrefabInstance) =>
{
Object.Destroy(gameObjectPrefabInstance.Value);

EntityManager.RemoveComponent<GameObjectPrefabInstance>(entity);
})
.WithStructuralChanges()
.Run();

return default;
}
}

// A dummy system for testing purposes.
public class DummyEntityWithGameObjectControllerSystem : JobComponentSystem
{
EntityQuery m_Query;

protected override JobHandle OnUpdate(JobHandle inputDeps)
{
var deltaTime = Time.DeltaTime;
var speed = 3;
var direction = new float2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
var destroy = Input.GetKeyDown(KeyCode.Delete);

if (destroy)
{
EntityManager.DestroyEntity(m_Query);
}

return Entities
.WithStoreEntityQueryInField(ref m_Query)
.WithAll<GameObjectPrefab>()
.ForEach((ref Translation translation) =>
{
translation.Value += math.normalizesafe(new float3(direction, 0).xzy) * speed * deltaTime;
})
.Schedule(inputDeps);
}
}