Entity介绍
Entity是ECS框架中三大基本概念之一 :Entity(实体)。ECS中所有的操作对象都是它。它本身对象并不带有任何属性与表达含义,它只有一个在它所有Context中唯一的ID(_creationIndex
)。它所有的属性与含义都是通过挂在它身上的组件(Component
)来组合表达的。所以说Entity本身不包含数据,它只是数据的搬运工。它也不管理任何数据,不对Component上的数据做任何的运算,只是提供对Conponent的增、删、查、改等方法,以及在自身Component发生变化时,通知其他的系统(如:Context,Group)。
一、Entity初始化
我们在介绍Context的文章中说过,Entity的创建必须通过Context的CreateEntity()
方法。在这个方法中我们会对Entity进行初始化。
//判断对象池是否有空闲对象
if (_reusableEntities.Count > 0) {
entity = _reusableEntities.Pop();
//如果有,则拿出来,同时赋予新的ID
entity.Reactivate(_creationIndex++);
} else {
//如果没有,则创建,并且初始化
entity = _entityFactory();
entity.Initialize(_creationIndex++, _totalComponents, _componentPools,_contextInfo, _aercFactory(entity));
}
在上面的代码中我们可以看到,我们通过Initialize(int creationIndex, int totalComponents, Stack<IComponent>[] componentPools, ContextInfo contextInfo = null, IAERC aerc = null)
方法对Entity进行初始化。
creationIndex
:当前Context中Entity的唯一ID。
totalComponents
:当前Context中Component的类型数量。用于初始化Componet数组的长度。
componentPools
:Component栈类型的数组,作为当前Context中的组件对象池,将Component的类型序号作为数组下标。
contextInfo
:当前Context的基本信息。
aerc
:Entity的引用计数对象。
public void Initialize(int creationIndex, int totalComponents, Stack<IComponent>[] componentPools, ContextInfo contextInfo = null, IAERC aerc = null) {
//激活Entity,设置标识ID
Reactivate(creationIndex);
//初始化组件数组
_totalComponents = totalComponents;
_components = new IComponent[totalComponents];
_componentPools = componentPools;
_contextInfo = contextInfo ?? createDefaultContextInfo();
_aerc = aerc ?? new SafeAERC(this);
二、Component的创建
创建Component时,我们根据Component在ComponentsLookup里生成的索引,先在_componentPools
对象池中查看是否有空闲的Component,有则直接Pop()出来使用,没有则创建。所有的Entity都是共用一个Component对象池。
//创建Component
public IComponent CreateComponent(int index, Type type) {
var componentPool = GetComponentPool(index);
return componentPool.Count > 0
? componentPool.Pop()
: (IComponent)Activator.CreateInstance(type);
}
public T CreateComponent<T>(int index) where T : new() {
var componentPool = GetComponentPool(index);
return componentPool.Count > 0 ? (T)componentPool.Pop() : new T();
}
根据Component索引获得对象池
public Stack<IComponent> GetComponentPool(int index) {
var componentPool = _componentPools[index];
if (componentPool == null) {
componentPool = new Stack<IComponent>();
_componentPools[index] = componentPool;
}
return componentPool;
}
三、Component的增删查改
Entity的Component都保存在_components
数组中,_components
的长度是所有Component类型数量的长度,也就是 ComponentsLookup.TotalComponents
,这样我们就可以将Component的索引当作数组的下标,我们再查找Component或者判断是否有某个Component时,都可以通过下标快速判断。极大的提高查找效率。
当Entity的组件发生变化是时,通过代理方法统一的在Context中处理,减少了不同模块之间的耦合度。提高了代码的可读性。
1、添加组件
判断组件是否重复,给_components
对应的下标赋值,同时通知组件被添加。
public void AddComponent(int index, IComponent component) {
if (!_isEnabled) {
throw new EntityIsNotEnabledException(
"Cannot add component '" +
_contextInfo.componentNames[index] + "' to " + this + "!"
);
}
if (HasComponent(index)) {
throw new EntityAlreadyHasComponentException(
index, "Cannot add component '" +
_contextInfo.componentNames[index] + "' to " + this + "!",
"You should check if an entity already has the component " +
"before adding it or use entity.ReplaceComponent()."
);
}
_components[index] = component;
_componentsCache = null;
_componentIndicesCache = null;
_toStringCache = null;
if (OnComponentAdded != null) {
OnComponentAdded(this, index, component);
}
}
2、组件移除
public void RemoveComponent(int index) {
if (!_isEnabled) {
throw new EntityIsNotEnabledException(
"Cannot remove component '" +
_contextInfo.componentNames[index] + "' from " + this + "!"
);
}
if (!HasComponent(index)) {
throw new EntityDoesNotHaveComponentException(
index, "Cannot remove component '" +
_contextInfo.componentNames[index] + "' from " + this + "!",
"You should check if an entity has the component " +
"before removing it."
);
}
replaceComponent(index, null);
}
3、组件修改
主要是修改_components
数组对应下标的值,如果新值为空,那么就相当于remove操作,那么就需要就旧的值Push到_componentPools
对象池中。然后就是通知代理组件修改了。
public void ReplaceComponent(int index, IComponent component) {
if (!_isEnabled) {
throw new EntityIsNotEnabledException(
"Cannot replace component '" +
_contextInfo.componentNames[index] + "' on " + this + "!"
);
}
if (HasComponent(index)) {
replaceComponent(index, component);
} else if (component != null) {
AddComponent(index, component);
}
}
void replaceComponent(int index, IComponent replacement) {
var previousComponent = _components[index];
if (replacement != previousComponent) {
_components[index] = replacement;
_componentsCache = null;
if (replacement != null) {
if (OnComponentReplaced != null) {
OnComponentReplaced(
this, index, previousComponent, replacement
);
}
}
else
{
_componentIndicesCache = null;
// TODO VD PERFORMANCE
_toStringCache = null;
if (OnComponentRemoved != null) {
OnComponentRemoved(this, index, previousComponent);
}
}
GetComponentPool(index).Push(previousComponent);
}
else
{
if (OnComponentReplaced != null) {
OnComponentReplaced(
this, index, previousComponent, replacement
);
}
}
}
4、组件查询
之前有说过,组件的查询非常的简单高效,就是判断数组的下标。
public IComponent GetComponent(int index) {
if (!HasComponent(index)) {
throw new EntityDoesNotHaveComponentException(
index, "Cannot get component '" +
_contextInfo.componentNames[index] + "' from " + this + "!",
"You should check if an entity has the component " +
"before getting it."
);
}
return _components[index];
}
public bool HasComponent(int index) {
return _components[index] != null;
}
四、Entity的删除
Entity的删除是调用Destroy()
方法,但是在这个方法里并不是做真正的删除操作,它只是发送代理通知,真正的删除操作是InternalDestroy()
方法,但是我们不需要自己这个方法,我们在外部调用Destroy()
方法。
public void Destroy() {
if (!_isEnabled) {
throw new EntityIsNotEnabledException("Cannot destroy " + this + "!");
}
if (OnDestroyEntity != null) {
OnDestroyEntity(this);
}
}
public void InternalDestroy() {
_isEnabled = false;
RemoveAllComponents();
OnComponentAdded = null;
OnComponentReplaced = null;
OnComponentRemoved = null;
OnDestroyEntity = null;
}
五、代码生成器扩展Entity
当我们自定义了Component后,我们使用代码生成器生成代码,这是Entitas会为我们扩展Entity类。添加一些我们方便访问这个组件的方法。方便我们在Entity中直接使用。
例如我们定义一个AssetComponent
组件如下:
using Entitas;
public class AssetComponent : IComponent
{
public string assetName;
}
那么代码生成器就会为我们生成一个这样的扩展类:
public partial class GameEntity {
public AssetComponent asset { get { return (AssetComponent)GetComponent(GameComponentsLookup.Asset); } }
public bool hasAsset { get { return HasComponent(GameComponentsLookup.Asset); } }
public void AddAsset(string newAssetName) {
var index = GameComponentsLookup.Asset;
var component = (AssetComponent)CreateComponent(index, typeof(AssetComponent));
component.assetName = newAssetName;
AddComponent(index, component);
}
public void ReplaceAsset(string newAssetName) {
var index = GameComponentsLookup.Asset;
var component = (AssetComponent)CreateComponent(index, typeof(AssetComponent));
component.assetName = newAssetName;
ReplaceComponent(index, component);
}
public void RemoveAsset() {
RemoveComponent(GameComponentsLookup.Asset);
}
}
我们就可以通过AddAsset
,ReplaceAsset
等方法直接添加、修改和判断对应的组件了。
同时还会根据我们给组件不同的标签,为我们生成不同特性的方法,使用起来十分的方便。
六、总结
Entity通过自身添加不同的Component表达不同的含义,所以它就是一个Component的载体,提供各种各样Component的操作方法。同时,在自身Component发生改变时,会通过代理方法将改变通知到Context中,然后在Context中与改变对应的Group,以达到快速分组的功能。