Para que os componentes possam atualizar todos os quadros (e deixar essa funcionalidade fora dos componentes que não precisam), tive a ideia de criar um componente UpdateComponent. Outros componentes como MovableComponent
(que mantém a velocidade) herdariam da IUpdatable
classe abstrata. Isso força MovableComponent
a implementar um Update(gametime dt)
método e outro RegisterWithUpdater()
que fornece UpdateComponent
um ponteiro para o MovableComponent
. Muitos componentes poderiam fazer isso e, em seguida, UpdateComponent
poderiam chamar todos os seus Update(gametime dt)
métodos sem precisar se preocupar com quem ou o que são.
Minhas perguntas são:
- Parece algo normal ou usado por alguém? Não consigo encontrar nada sobre o assunto.
- Como eu poderia manter uma ordem para os componentes como física e depois mudar de posição? Isso é mesmo necessário?
- Quais são outras maneiras de garantir que os componentes que devem ser processados em cada quadro sejam realmente processados?
EDIT
Acho que vou considerar como fornecer ao gerente da entidade uma lista dos tipos que são atualizáveis. Todos os componentes desse tipo podem ser atualizados em vez de gerenciá-los por entidade (que são apenas índices no meu sistema).
Ainda. Minhas perguntas permanecem válidas para mim. Não sei se isso é razoável / normal ou o que os outros tendem a fazer.
Além disso, o pessoal da Insomniac é incrível! /EDITAR
Código resumido para o exemplo anterior:
class IUpdatable
{
public:
virtual void Update(float dt) = 0;
protected:
virtual void RegisterAsUpdatable() = 0;
};
class Component
{
...
};
class MovableComponent: public Component, public IUpdatable
{
public:
...
virtual void Update(float dt);
private:
...
virtual void RegisterWithUpdater();
};
class UpdateComponent: public Component
{
public:
...
void UpdateAll();
void RegisterUpdatable(Component* component);
void RemoveUpdatable(Component* component);
private:
...
std::set<Component*> updatables_;
};
fonte
Respostas:
Um dos principais benefícios de um sistema de componentes é a capacidade de tirar proveito dos padrões de armazenamento em cache - bom icache e previsão porque você executa o mesmo código repetidamente, bom dcache porque pode alocar os objetos em conjuntos homogêneos e porque as tabelas v, se qualquer, fique quente.
Da maneira como você estruturou seus componentes, essa vantagem desaparece completamente e, de fato, pode se tornar um risco de desempenho em comparação com um sistema baseado em herança, à medida que você faz muito mais chamadas virtuais e mais objetos com vtables.
O que você deve fazer é armazenar pools por tipo e iterar cada tipo independentemente para executar atualizações.
Não é tão comum em jogos grandes, porque não é vantajoso. É comum em muitos jogos, mas não é tecnicamente interessante, por isso ninguém escreve sobre isso.
Código em linguagens como C ++ tem uma maneira natural de ordenar a execução: digite as instruções nessa ordem.
Na realidade, isso não faz sentido, porque nenhum sistema físico robusto está estruturado dessa maneira - você não pode atualizar um único objeto de física. Em vez disso, o código seria mais parecido com:
fonte
O que você está falando é razoável e bastante comum, eu acho. Isso pode lhe dar mais algumas informações.
fonte
Eu gosto dessas abordagens:
Resumidamente: Evite manter o comportamento de atualização nos componentes. Componentes não são comportamento. O comportamento (incluindo atualizações) pode ser implementado em alguns subsistemas de instância única. Essa abordagem também pode ajudar a processar em lote comportamentos semelhantes para vários componentes (talvez usando instruções parallel_for ou SIMD nos dados dos componentes).
A ideia IUpdatable e o método Update (gametime dt) parecem um pouco restritivos demais e apresentam diferenças adicionais. Pode ser bom se você não estiver usando a abordagem de subsistemas, mas se você os usar, IUpdatable é um nível de hierarquia redundante. Afinal, o MovingSystem deve saber que deve atualizar os componentes Location e / ou Velocity diretamente para todas as entidades que possuem esses componentes, portanto, não há necessidade de algum componente IUpdatable intermediário.
Mas você pode usar o componente Atualizável como um mecanismo para ignorar a atualização de algumas entidades, apesar delas possuírem componentes Localização e / ou Velocidade. O componente Updatable pode ter um sinalizador bool que pode ser definido como
false
e que sinalizaria para cada subsistema compatível com Updatable que essa entidade específica atualmente não deveria ser atualizada (embora, nesse contexto, Freezable pareça ser o nome mais apropriado para o componente).fonte