Organizando um sistema de entidades com gerentes de componentes externos?

13

Estou projetando um mecanismo de jogo para um jogo de tiro 2D multiplayer de cima para baixo, que eu quero ser razoavelmente reutilizável para outros jogos de tiro de cima para baixo. No momento, estou pensando em como algo como um sistema de entidades deve ser projetado. Primeiro pensei sobre isso:

Eu tenho uma classe chamada EntityManager. Ele deve implementar um método chamado Update e outro chamado Draw. O motivo para eu separar a lógica e a renderização é porque posso omitir o método Draw se estiver executando um servidor independente.

O EntityManager possui uma lista de objetos do tipo BaseEntity. Cada entidade possui uma lista de componentes como EntityModel (a representação desenhável de uma entidade), EntityNetworkInterface e EntityPhysicalBody.

O EntityManager também possui uma lista de gerenciadores de componentes, como EntityRenderManager, EntityNetworkManager e EntityPhysicsManager. Cada gerenciador de componentes mantém referências aos componentes da entidade. Existem várias razões para mover esse código da classe da própria entidade e fazê-lo coletivamente. Por exemplo, estou usando uma biblioteca de física externa, Box2D, para o jogo. No Box2D, você adiciona primeiro os corpos e as formas a um mundo (de propriedade do EntityPhysicsManager nesse caso) e adiciona retornos de chamada de colisão (que seriam despachados para o próprio objeto da entidade no meu sistema). Então você executa uma função que simula tudo no sistema. Acho difícil encontrar uma solução melhor para fazer isso do que em um gerenciador de componentes externo como esse.

A criação da entidade é feita assim: EntityManager implementa o método RegisterEntity (entityClass, factory) que registra como criar uma entidade dessa classe. Ele também implementa o método CreateEntity (entityClass) que retornaria um objeto do tipo BaseEntity.

Agora vem o meu problema: como a referência a um componente seria registrada para os gerenciadores de componentes? Não tenho idéia de como referenciaria os gerentes de componentes de uma fábrica / fechamento.

Gustav
fonte
Não sei se talvez isso pretenda ser um sistema híbrido, mas parece que seus "gerentes" são o que geralmente ouvi denominado "sistemas"; ou seja, a entidade é um ID de resumo; um componente é um conjunto de dados; e o que você chama de "gerente" é o que geralmente é chamado de "sistema". Estou interpretando o vocabulário corretamente?
BRPocock
A minha pergunta aqui podem ser de interesse: Jogo Componentes, gestores cinegéticos e Propriedades do objeto
George Duckett
Dê uma olhada em gamadu.com/artemis e veja se os métodos deles respondem à sua pergunta.
Patrick Hughes
1
Não existe uma maneira de projetar um sistema de entidades, pois há pouco consenso sobre sua definição. O que o @BRPocock descreve e também o que Artemis usa está descrito mais detalhadamente neste blog: t-machine.org/index.php/category/entity-systems, juntamente com um wiki: entity-systems.wikidot.com
user8363

Respostas:

6

Os sistemas devem armazenar um par de valores-chave de Entidade para componente em algum tipo de mapa, objeto de dicionário ou matriz associativa (dependendo do idioma usado). Além disso, quando você cria seu Entity Object, não me preocupo em armazená-lo em um gerente, a menos que você precise anular o registro em qualquer um dos Sistemas. Uma entidade é um composto de componentes, mas não deve lidar com nenhuma das atualizações de componentes. Isso deve ser tratado pelos sistemas. Em vez disso, trate sua Entidade como uma chave que é mapeada para todos os componentes que ela contém nos sistemas, bem como um hub de comunicação para esses componentes se comunicarem.

A grande parte dos modelos de sistema de componentes de entidade é que você pode implementar a capacidade de passar mensagens de um componente para o restante dos componentes de uma entidade com bastante facilidade. Isso permite que um componente fale com outro componente sem realmente saber quem é esse componente ou como lidar com o componente que está sendo alterado. Em vez disso, passa uma mensagem e permite que o componente mude a si próprio (se existir)

Por exemplo, um sistema de posição não teria muito código, apenas controlando os objetos de entidade mapeados para seus componentes de posição. Porém, quando uma posição muda, eles podem enviar uma mensagem para a Entidade envolvida, que por sua vez é entregue a todos os componentes dessa entidade. Uma posição muda por alguma razão? O Sistema de Posições envia uma mensagem para a Entidade, informando que a posição foi alterada e, em algum lugar, o componente de renderização de imagem dessa entidade recebe essa mensagem e atualiza onde será a próxima.

Por outro lado, um sistema de física precisa saber o que todos os seus objetos estão fazendo; Ele deve poder ver todos os objetos do mundo para testar colisões. Quando ocorre uma colisão, ele atualiza o componente de direção da Entidade, enviando algum tipo de "Mensagem de Mudança de Direção" para a entidade, em vez de se referir diretamente ao componente da Entidade. Isso distrai o gerente de precisar saber como mudar de direção usando uma mensagem em vez de confiar em um componente específico que está lá (que pode não estar lá; nesse caso, a mensagem ficaria surda ao invés de algum erro ocorrendo porque um objeto esperado estava ausente).

Você notará uma enorme vantagem disso, pois mencionou que possui uma interface de rede. Um componente de rede ouviria todas as mensagens que todos deveriam conhecer. Ama as fofocas. Então, quando o Sistema de Rede é atualizado, os componentes de Rede enviam essas mensagens para outros Sistemas de Rede em outras máquinas clientes, que reenviam essas mensagens para todos os outros componentes para atualizar as posições dos jogadores, etc. Lógica especial pode ser necessária para que apenas determinadas entidades possam envie mensagens pela rede, mas essa é a beleza do sistema; você pode simplesmente controlar essa lógica registrando as coisas certas.

Em resumo:

Entidade é uma composição de componentes que podem receber mensagens. A entidade pode receber mensagens, delegando essas mensagens a todos os seus componentes para atualizá-las. (Mensagem de posição alterada, Direção da mudança de velocidade, etc.) É como uma caixa de correio central que todos os componentes podem ouvir um do outro em vez de falar diretamente um com o outro.

Componente é uma pequena parte de uma Entidade que armazena algum estado da entidade. Eles são capazes de analisar determinadas mensagens e jogar outras mensagens fora. Por exemplo, um "Componente de direção" se preocuparia apenas com "Mensagens de mudança de direção", mas não "Mensagens de mudança de posição". Os componentes atualizam seu próprio estado com base nas mensagens e, em seguida, atualizam os estados de outros componentes enviando mensagens de seu sistema.

O sistema gerencia todos os componentes de um determinado tipo e é responsável por atualizar os referidos componentes a cada quadro, além de enviar mensagens dos componentes que gerenciam para as entidades às quais os componentes pertencem.

Os sistemas podem atualizar todos os seus componentes em paralelo e armazenar todas as mensagens à medida que avançam. Então, quando a execução de todos os métodos de atualização dos Sistemas for concluída, você solicita a cada sistema que despache suas mensagens em uma ordem específica. Controles primeiro possivelmente, seguidos de Física, seguidos de direção, posição, renderização, etc. Importa em que ordem eles são despachados, pois uma Mudança de direção física sempre deve pesar uma mudança de direção baseada em controle.

Espero que isto ajude. É um inferno de um padrão de design, mas é ridiculamente poderoso se bem feito.

C0M37
fonte
0

Estou usando um sistema semelhante no meu mecanismo e da maneira como o fiz, cada Entidade contém uma lista de Componentes. No EntityManager, posso consultar cada uma das entidades e ver quais contêm um determinado componente. Exemplo:

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;
    }
}

Obviamente, esse não é o código exato (você realmente precisaria de uma função de modelo para verificar diferentes tipos de componentes, em vez de usar typeof), mas o conceito está lá. Então você pode pegar esses resultados, referenciar o componente que está procurando e registrá-lo na sua fábrica. Isso evita qualquer acoplamento direto entre os Componentes e seus gerentes.

Mike Cluck
fonte
3
Aviso importante: no momento em que sua Entidade contém dados, é um objeto, não uma entidade ... Perde-se a maioria dos benefícios paralelos (sic?) Do ECS nessa estrutura. "Pure" E / C / S sistemas são relacionais, não orientadas a objetos ... Não que seja necessariamente "ruim" por algum caso (s), mas certamente é "quebrar o modelo relacional"
BRPocock
2
Não sei se entendi você. Meu entendimento (e me corrija se estiver errado) é que o sistema de componentes de entidade básico tem uma classe de entidade que hospeda componentes e pode ter um ID, nome ou algum identificador. Acho que podemos ter um mal-entendido no que quero dizer com "tipo" de Entidade. Quando digo Entity "type", estou me referindo aos tipos de Component. Ou seja, uma Entidade é do tipo "Sprite" se contiver um Componente Sprite.
Mike Cluck
1
Em um sistema de Entidade / Componente puro, uma Entidade é geralmente atômica: por exemplo typedef long long int Entity; um componente é um registro (pode ser implementado como uma classe de objeto ou apenas a struct) que tem uma referência à entidade à qual está anexado; e um sistema seria um método ou coleção de métodos. O modelo ECS não é muito compatível com o modelo OOP, embora um Componente possa ser um Objeto somente de dados (principalmente) e um Sistema um Objeto singleton somente de código cujo estado mora em componentes ... embora os sistemas "híbridos" sejam mais comuns que os "puros", eles perdem muitos dos benefícios inatos.
BRPocock
2
@BRPocock re sistemas de entidade "puros". Eu acho que uma entidade como um objeto está perfeitamente bem, não precisa ser uma identificação simples. Uma coisa é a representação serializada, outra a disposição na memória de um objeto / conceito / entidade. Contanto que você possa manter a direcionamento de dados, não se deve estar vinculado ao código não idiomático apenas porque é o caminho "puro".
Raine
1
@BRPocock, este é um aviso justo, mas para sistemas de entidades do tipo "t-machine". Eu entendo o porquê, mas essas não são as únicas maneiras de modelar entidades baseadas em componentes. atores são uma alternativa interessante. Costumo apreciá-los mais, especialmente para entidades puramente lógicas.
Raine
0

1) Seu método Factory deve receber uma referência ao EntityManager que o chamou (usarei C # como exemplo):

delegate BaseEntity EntityFactory(EntityManager manager);

2) Faça com que CreateEntity também receba um ID (por exemplo, uma string, um número inteiro, depende de você) além da classe / tipo da entidade e registre automaticamente a entidade criada em um Dicionário usando esse ID como chave:

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) Adicione um Getter ao EntityManager para obter qualquer entidade por ID:

class EntityManager
{
    // Rest of class omitted

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

E é tudo o que você precisa para fazer referência a qualquer ComponentManager de dentro do seu método Factory. Por exemplo:

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;
}

Além do Id, você também pode usar algum tipo de propriedade Type (uma enum personalizada ou apenas confiar no sistema de tipos do idioma) e criar um getter que retorne todas as BaseEntities de um determinado tipo.

David Gouveia
fonte
1
Não ser pedante, mas novamente ... em um sistema de pura Entity (relacional), entidades não têm nenhum tipo, exceto que transmitido a eles em virtude de seus componentes ...
BRPocock
@ BRPocock: Você poderia criar um exemplo que segue a pura virtude?
Zolomon
1
@ Raine Talvez, eu não tenha experiência em primeira mão com isso, mas é o que eu leio. E há otimizações que você pode implementar para reduzir o tempo gasto pesquisando componentes por ID. Quanto à coerência do cache, acho que faz sentido, pois você está armazenando dados do mesmo tipo de forma contígua na memória, especialmente quando seus componentes são propriedades leves ou simples. Eu li que uma única falha de cache no PS3 pode ser tão cara quanto mil instruções de CPU, e essa abordagem de armazenar dados de tipo semelhante de forma contígua é uma técnica de otimização muito comum no desenvolvimento de jogos modernos.
David Gouveia
2
Na ref: sistema de entidade “puro”: o ID da entidade é geralmente algo como typedef unsigned long long int EntityID;:; o ideal é que cada sistema possa viver em uma CPU ou host separado e exija apenas buscar componentes que sejam relevantes para / ativos nesse sistema. Com um objeto Entity, pode ser necessário instanciar o mesmo objeto Entity em cada host, dificultando o dimensionamento. Um modelo puro de entidade-componente-sistema divide o processamento entre nós (processos, CPUs ou hosts) por sistema, em vez de por entidade, normalmente.
BRPocock 12/03/12
1
@DavidGouveia mencionou "otimizações ... procurando entidades por ID". De fato, os (poucos) sistemas que eu implementei dessa maneira tendem a não fazê-lo. Com mais freqüência, selecione Componentes por algum padrão, indicando que são do interesse de um Sistema específico, usando Entidades (IDs) apenas para Associações entre componentes.
BRPocock 12/03/12