赞
踩
1,Entity
Entity(本质上)是一个ID。你可以把它当作一种超级轻量级的GameObject,默认情况下它甚至没有名字。
您可以在运行时从Entity动态添加或删除Component。Entity ID是可靠的。 实际上,如果你想要存储对另一个Component或Entity的引用,使用Entity ID去存储也是唯一可靠的方式。
译注:作者说把Entity当作GameObject有一定的误导性,实际上它和GameObject非常不同。Entity本质是数据(就是Component)分类分组以后得到的一个ID而已,我们用这个ID去操作一组相同类型的数据。
2,IComponentData
传统的Unity Component(包括MonoBehaviour)是面向对象的类,所以说它包含了数据和定义行为的方法。IComponentData是纯粹的ECS类型的组件,意味着他不定义行为,只包含数据。IComponentData是结构体(struct)而非类(class),所以他们进行值拷贝而不是引用拷贝。
你一般需要使用如下模式去修改IComponentData的值:
- var transform = group.transform[index]; //读取
-
- transform.heading = playerInput.move; //修改
- transform.position += deltaTime * playerInput.move * settings.playerMoveSpeed;
-
- group.transform[index] = transform; //写入
注意:ECS很快就要基于C#7.0编译了,如果使用C#7.0的 ref return特性就使额外的内存分配变得没有必要了。
IComponentData结构体不能包含对托管对象的引用,因为组件里所有的数据都存在于简单的没有垃圾回收的块(chunk)里。
译注:IComponentData作为struct直接在栈里分配,它不像class一样分配在托管堆里,不进行垃圾回收,是实现所谓“连续紧凑的内存布局”的好结构。
纯粹ECS下,Component名为Component,但要抛弃过去的认识,实际上它就只是纯粹的数据而已,与逻辑完全无关。
3,EntityArchetype
一个EntityArchetype是一个ComponentType构成的独特数组。EntityManager使用它来对所有使用相同ComponentType的Entity进行分组。
- // 使用typeof关键字从一组Component中创建一个EntityArchetype
- EntityArchetype archetype = EntityManager.CreateArchetype(typeof(MyComponentData), typeof(MySharedComponent));
-
- // 相同功能的API,但是更轻巧也更高效
- EntityArchetype archetype = EntityManager.CreateArchetype(ComponentType.Create<MyComponentData>(), ComponentType.Create<MySharedComponent>());
-
- // 从EntityArchetype创建一个Entity
- var entity = EntityManager.CreateEntity(archetype);
-
- // 方便起见,隐式地创建一个EntityArchetype
- var entity = EntityManager.CreateEntity(typeof(MyComponentData), typeof(MySharedComponent));
4,EntityManager
EntityManager拥有EntityData、EntityArchetypes、 SharedComponentData和ComponentGroup。
你在EntityManager 里面可以找到API来创造Entity、检查一个Entity是否存活、实例化Entity以及添加或移除组件。
使用EntityManager对Entity进行操作实例如下:
- // 创建一个不包含Component的Entity
- var entity = EntityManager.CreateEntity();
-
- // 在运行时向Entity动态添加Component
- EntityManager.AddComponent(entity, new MyComponentData());
-
- // 获取Entity的ComponentData
- MyComponentData myData = EntityManager.GetComponentData<MyComponentData>(entity);
-
- // 设置Entity的ComponentData
- EntityManager.SetComponentData(entity, myData);
-
- // 在运行时从Entity动态移除Component
- EntityManager.RemoveComponent<MyComponentData>(entity);
-
- // 是否存在拥有指定Component的Entity?
- bool has = EntityManager.HasComponent<MyComponentData>(entity);
-
- // Entity是否alive?
- bool has = EntityManager.Exists(entity);
-
- // 实例化Entity
- var instance = EntityManager.Instantiate(entity);
-
- // 销毁这个实例
- EntityManager.DestroyEntity(instance);
EntityManager也提供了批处理的API来一次创建或销毁多个Entity,这些API显然更快,出于性能考虑,我们应该尽可能地使用这些API。
- // 实例化500个Entity并它们的ID写到instances这个数组
- var instances = new NativeArray<Entity>(500, Allocator.Temp);
- EntityManager.Instantiate(entity, instances);
-
- // 销毁这500个实例
- EntityManager.DestroyEntity(instances);
5,内存块(chunk)的实现详情
每个Entity的ComponentData都储存于我们称之为内存块(chunk)的地方。ComponentData以流的方式布局,意味着:所有类型为A的Component,都被紧紧地包裹在一个数组里面。紧跟着的是包裹着B类型Component的数组,再紧跟着的是包裹着C类型Component的数组,以此类推。
每个内存块总是链接到一个具体的EntityArchetype上,从而在一个块里的所有Entity都遵循完全相同的内存布局。在遍历Component时,块内Component的内存访问总是完全线性的,并没有浪费加载到缓存行中。这点必须要保证。
ComponentDataArray本质上是一个迭代器,它能够遍历全部与所需Component兼容的EntityArchetype。就访问EntityArchetype来说,就是去遍历所有与它相匹配的块;就访问块来说,就是去遍历所有在那个块里的Entity。
译注:原文<ComponentDataArray is essentially an iterator over all EntityArchetypes compatible with the set of required components; for each EntityArchetype iterating over all chunks compatible with it and for each chunk iterating over all Entities in that chunk.>。
一旦一个块里的Entity都被访问过了,我们就找到下一个匹配的块,然后访问那里面的那些Entity。
当Entity被销毁了,我们就把别的Entity移到它的位置去,然后再更新Entity表。
如上文所说,要对Entity的线性布局做出有力保证,所以这么做是必需的。Component数据在内存中移动的代码也经过了高度优化。
译注:Unity官方ECS样例: Unity-Technologies/EntityComponentSystemSamples
所以,Entity、Component只相当于纯粹的数据,一点逻辑处理都没有。写逻辑处理的部分的是System和Job。第二部分的“System、Job和多线程处理”的部分会进行阐述,这一部分有点复杂,很多地方我也没有搞懂作者在说些什么(假如作者自己知道自己在说什么的话),为了避免误人子弟,这一部分的译文过几天再出。。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。