阅读 57

ECS Entity简介

Entity的创建

GameContext gameContext = Contexts.sharedInstance.game;
var entity = gameContext.CreateEntity();
复制代码

entity只可以通过Contexts创建,任何Contexts都可创建其对应的entity,例如GameContext创建GameEntity,InputContext创建InputEntity。为什么至于必须要用Contexts创建entity,可以查看其创建代码,如下:

//Context.cs
public TEntity CreateEntity()
{
  TEntity entity;
  if (this._reusableEntities.Count > 0)
  {
    entity = this._reusableEntities.Pop();
    entity.Reactivate(this._creationIndex++);
  }
  else
  {
    entity = this._entityFactory();
    entity.Initialize(this._creationIndex++, this._totalComponents, this._componentPools, this._contextInfo, this._aercFactory((IEntity) entity));
  }
  this._entities.Add(entity);
  entity.Retain((object) this);
  this._entitiesCache = (TEntity[]) null;
  entity.OnComponentAdded += this._cachedEntityChanged;
  entity.OnComponentRemoved += this._cachedEntityChanged;
  entity.OnComponentReplaced += this._cachedComponentReplaced;
  entity.OnEntityReleased += this._cachedEntityReleased;
  entity.OnDestroyEntity += this._cachedDestroyEntity;
  // ISSUE: reference to a compiler-generated field
  if (this.OnEntityCreated != null)
  {
    // ISSUE: reference to a compiler-generated field
    this.OnEntityCreated((IContext) this, (IEntity) entity);
  }
  return entity;
}
复制代码
  • 可以很明显的看出,entity的创建使用的对象池技术,销毁的对象可重复利用的entity保存在名为_reusableEntities的栈中。利用对象池的好处相信每个人都明白。
  • 另一个原因利用Contexts创建的对象都可以通过该Context获取到,保存在名为_entities的HashSet中(不可以有重复对象,保证了对象的唯一想),可以通过如下代码获取该Contexts中所有的entity。
var entities = gameContext.GetEntities();
复制代码

Entity 的销毁

有entity的创建,自然就有entity的销毁,可通过entity自身调用Destroy方法销毁,如下:

entity.Destroy();
复制代码

看一下调用过程中发生了什么:

//Entity.cs
public void Destroy()
{
  if (!this._isEnabled)
    throw new EntityIsNotEnabledException("Cannot destroy " + (object) this + "!");
  // ISSUE: reference to a compiler-generated field
  if (this.OnDestroyEntity == null)
    return;
  // ISSUE: reference to a compiler-generated field
  this.OnDestroyEntity((IEntity) this);
}
复制代码

是不是对OnDestroyEntity这个事件名称很熟悉,因为在创建entity时,对其进行了赋值,如下:

//Context.cs
public TEntity CreateEntity()
{
  ......//省略前面代码
  entity.OnDestroyEntity += this._cachedDestroyEntity;
  ......//省略后面代码
  return entity;
}
复制代码

_cachedDestroyEntity这个事件在Contexts初始化时进行了赋值,最终OnDestroyEntity监听的方法为Contexts中的onDestroyEntity(IEntity entity)方法,如下:

//Context.cs
private void onDestroyEntity(IEntity entity)
{
  ......//省略前面代码
  if (entity1.retainCount == 1)  //判断包含该entity的对象的数量是否为1.(也就是否只用该Contexts包含了该entity)
  {
    entity1.OnEntityReleased -= this._cachedEntityReleased;
    this._reusableEntities.Push(entity1); //放入对象池中
    entity1.Release((object) this); //将引用删除
    entity1.RemoveAllOnEntityReleasedHandlers();
  }
  else
  {
    this._retainedEntities.Add(entity1);
    entity1.Release((object) this);
  }
}
复制代码

可以看到,有且只有创建该entity的Contexts包含这个entity时,删除时才会证明这个entity可以重复利用,那这个Contexts是什么时候包含的这个entity呢,也是在创建的时候:

public TEntity CreateEntity()
{
 ......//省略前面代码
  entity.Retain((object) this);
  ......//省略后面代码
}
复制代码

接下来看一下entity1.retainCount中的属性retainCount:

public int retainCount
{
  get
  {
    return this._aerc.retainCount;
  }
}
复制代码

_aerc是一个SafeAERC对象,其具体实现细节如下所示:

public sealed class SafeAERC : IAERC
{
  private readonly HashSet<object> _owners = new HashSet<object>();
  private readonly IEntity _entity;

  public int retainCount
  {
    get
    {
      return this._owners.Count;
    }
  }

  public HashSet<object> owners
  {
    get
    {
      return this._owners;
    }
  }

  public SafeAERC(IEntity entity)
  {
    this._entity = entity;
  }

  public void Retain(object owner)
  {
    if (!this.owners.Add(owner))
      throw new EntityIsAlreadyRetainedByOwnerException(this._entity, owner);
  }

  public void Release(object owner)
  {
    if (!this.owners.Remove(owner))
      throw new EntityIsNotRetainedByOwnerException(this._entity, owner);
  }
}
复制代码

综上所述,如果entity1.retainCount大于1时,也就是我们自行调用了entity.Retain(a)方法,在Destroy entity 之前,要先调用entity.Release(a),将这个entity从那个对象a中删除,否则entity不会进入对象池,也不会被重复利用,而永久存在内存中,造成内存泄漏。

Entity与Component的关系

Entity相当于一个Component的集合,一个Entity可以有多个Component,我们可以通过Generate Code很方便的添加,删除和替换Component。例如:

 public void Initialize()
 {
     var entity = game.CreateEntity();
     entity.AddAssetName("Player");
     entity.AddPosition(Vector3.zero);
     entity.AddSpeed(0,0.03f);
     entity.isSpecial = true;
     entity.RemoveSpeed();
 }
复制代码