与外部组件管理者一起组织实体系统?


13

我正在为自上而下的多人2D射击游戏设计游戏引擎,我希望该引擎可以合理地重用于其他自上而下的射击游戏。目前,我正在考虑应如何设计其中的实体系统。首先,我想到了这一点:

我有一个叫做EntityManager的类。它应该实现一种称为Update的方法,以及另一个称为Draw的方法。之所以将Logic和Rendering分开是因为,如果运行独立服务器,则可以省略Draw方法。

EntityManager拥有BaseEntity类型的对象列表。每个实体都拥有一个组件列表,例如EntityModel(实体的可绘制表示),EntityNetworkInterface和EntityPhysicalBody。

EntityManager还拥有一个组件管理器列表,例如EntityRenderManager,EntityNetworkManager和EntityPhysicsManager。每个组件管理器都保留对实体组件的引用。有多种原因将此代码移出实体自己的类,而改为集体进行。例如,我使用游戏的外部物理库Box2D。在Box2D中,首先将实体和形状添加到一个世界(在本例中为EntityPhysicsManager拥有),然后添加碰撞回调(将在我的系统中分派给实体对象本身)。然后,您运行一个函数来模拟系统中的所有内容。我发现很难找到比在这样的外部组件管理器中更好的解决方案。

实体创建是这样完成的:EntityManager实现方法RegisterEntity(entityClass,factory),该方法注册如何创建该类的实体。它还实现了CreateEntity(entityClass)方法,该方法将返回BaseEntity类型的对象。

现在出现了我的问题:如何将对组件的引用注册到组件管理器?我不知道如何从工厂/关闭处引用组件管理器。


我不知道这是否意味着要成为一个混合系统,但听起来您的“经理”就是我通常所说的“系统”。即实体是一个抽象ID;组件是一个数据池;您所说的“经理”就是通常所说的“系统”。我是否正确解释了词汇?
BRPocock 2011年


看看gamadu.com/artemis,看看他们的方法是否回答您的问题。
Patrick Hughes 2012年

1
没有一种设计实体系统的方法,因为对其定义几乎没有共识。@BRPocock所描述的内容以及Artemis所使用的内容已在此博客上进行了更深入的描述:t-machine.org/index.php/category/entity-systems以及Wiki:entity-systems.wikidot.com
user8363

Answers:


6

系统应该以某种Map,Dictionary Object或Associative Array(取决于所使用的语言)将Entity到Component的键值对存储。此外,当您创建实体对象时,除非您需要能够从任何系统中注销它,否则我不必担心将其存储在管理器中。实体是组件的组合,但是它不应该处理任何组件更新。这应该由系统来处理。而是将您的实体视为映射到其包含在系统中的所有组件的密钥,以及将这些组件彼此通信的通信中心。

关于实体组件系统模型的重要部分是,您可以实现轻松地将消息从一个组件传递到实体的其余组件的功能。这样一来,一个组件就可以与另一个组件通信,而无需实际知道该组件是谁或如何处理该组件的变化。相反,它传递消息并让组件自行更改(如果存在)

例如,位置系统中没有太多代码,仅跟踪映射到其位置组件的实体对象。但是,当职位发生变化时,他们可以向相关实体发送一条消息,而该消息又会转交给该实体的所有组件。职位变动是出于什么原因?Position System向Entity发送一条消息,指出位置已更改,并且该实体的图像呈现组件在某处获取了该消息并更新了下一步绘制自身的位置。

相反,物理系统需要知道其所有对象在做什么。它必须能够看到所有世界物体以测试碰撞。发生冲突时,它将通过向实体发送某种“方向更改消息”来更新实体的方向组件,而不是直接引用实体的组件。这使经理无需通过使用消息来知道如何更改方向,而不必依赖那里的特定组件(它可能根本不存在),在这种情况下,消息只会落在耳边而不会出现一些错误因为缺少预期的对象而发生)。

由于提到了您具有网络接口,因此您将注意到这一点的巨大优势。网络组件将侦听其他人都应该知道的所有传入消息。它爱八卦。然后,当网络系统更新时,网络组件会将这些消息发送到其他客户端计算机上的其他网络系统,然后这些消息将这些消息重新发送给所有其他组件以更新玩家位置,等等。可能需要特殊的逻辑,以便仅某些实体可以通过网络发送消息,但这就是系统的优点,您只需通过向其注册正确的东西就可以控制该逻辑。

简而言之:

实体是可以接收消息的组件的组合。实体可以接收消息,将所述消息委派给其所有组件以对其进行更新。(位置更改的消息,速度更改方向等)就像一个中央邮箱,所有组件都可以彼此听到而不是彼此直接交谈。

组件是实体的一小部分,存储实体的某些状态。它们能够解析某些消息并丢弃其他消息。例如,“方向组件”将仅关注“方向更改消息”,而不关注“位置更改消息”。组件根据消息更新自己的状态,然后通过从其系统发送消息来更新其他组件的状态。

系统管理某种类型的所有组件,并负责在每个帧中更新所述组件,以及从它们管理的组件的调度消息到这些组件所属的实体

系统可以并行更新其所有组件,并随时存储所有消息。然后,当所有系统的更新方法的执行完成时,您要求每个系统以特定顺序调度其消息。首先可能是控件,然后是物理控件,然后是方向,位置,渲染等。它们的分配顺序很重要,因为“物理方向更改”应始终优先考虑基于控件的方向更改。

希望这可以帮助。这是一个设计模式的地狱,但如果做对的话,它的功能可笑。


0

我在引擎中使用的是类似的系统,而这样做的方式是每个实体都包含一个组件列表。从EntityManager中,我可以查询每个实体,并查看哪些实体包含给定的组件。例:

class Component
{
    private uint ID;
    // etc...
}

class Entity
{
    List<Component> Components;
    // etc...
    public bool Contains(Type type)
    {
        foreach(Component comp in Components)
        {
            if(typeof(comp) == type)
                return true;
        }
        return false;
    }
}

class EntityManager
{
    List<Entity> Entities;
    // etc...
    public List<Entity> GetEntitiesOfType(Type type)
    {
        List<Entity> results = new List<Entity>();
        foreach(Entity entity in Entities)
        {
            if(entity.Contains(type))
                results.Add(entity);
        }
        return results;
    }
}

显然,这不是确切的代码(实际上,您需要使用模板函数来检查不同的组件类型,而不是使用typeof),但是这里有一个概念。然后,您可以获取这些结果,引用所需的组件,然后在工厂中进行注册。这样可以防止组件与其管理器之间的任何直接耦合。


3
告急者:在您的实体包含数据的那一刻,它是一个对象,而不是一个实体……在这种结构中,人们失去了ECS的大部分并行化优势。“纯” E / C / S系统是关系的,而不是面向对象的……在某些情况下,它不一定是“坏的”,但肯定是“破坏了关系模型”
BRPocock 2011年

2
我不确定我是否理解你。我的理解(如果我错了,请纠正我)是基本的Entity-Component-System,它具有一个Entity类,该类可容纳Components,并且可能具有ID,名称或某些标识符。我认为我们可能对实体的“类型”有误解。当我说实体“类型”时,是指组件的类型。也就是说,如果实体包含Sprite组件,则它是“ Sprite”类型。
Mike Cluck

1
在纯实体/组分体系中,一个实体通常是原子:例如typedef long long int Entity; 一个Component是一条记录(它可以实现为一个对象类,或者只是一个实现struct),它引用了它所连接的实体;系统将是一个方法或方法的集合。ECS模型与OOP模型不是很兼容,尽管Component可以是(主要是)仅数据对象,而System可以是状态位于组件中的仅代码单例Object ...尽管“混合”系统是比“纯”的更普遍,它们失去了许多先天的好处。
BRPocock 2011年

2
@BRPocock重新“纯”实体系统。我认为实体作为对象是完全可以的,它不必是简单的id。一件事是序列化表示,另一件事是对象/概念/实体的内存布局。只要您可以保持数据驱动性,就不应将其与非惯用代码联系在一起,因为这是“纯”方式。
Raine 2012年

1
@BRPocock这是一个公平的警告,但对于类似“ t机”的实体系统。我知道为什么,但是这些并不是建模基于组件的实体的唯一方法。演员是一个有趣的选择。我倾向于更欣赏它们,尤其是对于纯逻辑实体。
Raine 2012年

0

1)您的Factory方法应传递对调用它的EntityManager的引用(我将使用C#作为示例):

delegate BaseEntity EntityFactory(EntityManager manager);

2)让CreateEntity除了实体的类/类型之外,还接收一个ID(例如,字符串,整数,由您决定),并使用该ID作为键在Dictionary上自动注册创建的实体:

class EntityManager
{
    // Rest of class omitted

    BaseEntity CreateEntity(string id, Type entityClass)
    {
        BaseEntity entity = factories[entityClass](this);
        registry.Add(id, entity);
        return entity;
    }

    Dictionary<Id, BaseEntity> registry;
}

3)向EntityManager添加Getter以通过ID获取任何实体:

class EntityManager
{
    // Rest of class omitted

    BaseEntity GetEntity(string id)
    {
        return registry[id];
    }
}

这就是从Factory方法中引用任何ComponentManager的全部。例如:

BaseEntity CreateSomeSortOfEntity(EntityManager manager)
{
    // Create and configure entity
    BaseEntity entity = new BaseEntity();
    RenderComponent renderComponent = new RenderComponent();
    entity.AddComponent(renderComponent);

    // Get a reference to the render manager and register component
    RenderEntityManager renderer = manager.GetEntity("RenderEntityManager") as RenderEntityManager;
    if(renderer != null)
        renderer.Register(renderComponent)

    return entity;
}

除了Id之外,您还可以使用某种Type属性(自定义枚举,或仅依赖于语言的类型系统),并创建一个getter来返回特定类型的所有BaseEntities。


1
并不是学究,而是……在纯实体(关系)系统中,实体没有类型,只有凭借其成分赋予它们的类型……
BRPocock 2011年

@BRPocock:您能创建一个遵循纯美德的示例吗?
Zolomon

1
@Raine也许,我没有亲身经历,但这就是我读到的。您可以实施一些优化来减少通过id查找组件所花费的时间。至于缓存一致性,我认为这是有道理的,因为您将相同类型的数据连续存储在内存中,尤其是当组件是轻量级或简单属性时。我已经读到PS3上的单个高速缓存未命中可能与一千条CPU指令一样昂贵,并且这种连续存储相似类型数据的方法是现代游戏开发中非常常见的优化技术。
David Gouveia 2012年

2
在参考文献:“纯粹的”实体系统:实体ID通常是这样的:typedef unsigned long long int EntityID;; 理想的情况是,每个系统都可以驻留在单独的CPU或主机上,并且只需要获取与该系统相关/活动的组件。对于Entity对象,可能必须在每个主机上实例化相同的Entity对象,这使得扩展更加困难。纯实体组件系统模型通常按系统而不是按实体在节点(进程,CPU或主机)之间划分处理。
BRPocock 2012年

1
@DavidGouveia提到“优化……通过ID查找实体”。实际上,我以这种方式实现的(很少)系统往往没有这样做。更常见的是,仅通过跨实体联接使用实体(ID),通过某种指示组件对特定系统感兴趣的模式来选择它们。
BRPocock 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.