Isso geralmente é feito usando mensagens. Você pode encontrar muitos detalhes em outras perguntas neste site, como aqui ou ali .
Para responder seu exemplo específico, um caminho a seguir é definir uma Message
classe pequena que seus objetos possam processar, por exemplo:
struct Message
{
Message(const Objt& sender, const std::string& msg)
: m_sender(&sender)
, m_msg(msg) {}
const Obj* m_sender;
std::string m_msg;
};
void Obj::Process(const Message& msg)
{
for (int i=0; i<m_components.size(); ++i)
{
// let components do some stuff with msg
m_components[i].Process(msg);
}
}
Dessa forma, você não está "poluindo" sua Obj
interface de classe com métodos relacionados a componentes. Alguns componentes podem optar por processar a mensagem, outros podem ignorá-la.
Você pode começar chamando esse método diretamente de outro objeto:
Message msg(obj1, "EmitForce(5.0,0.0,0.0)");
obj2.ProcessMessage(msg);
Nesse caso, obj2
o usuário Physics
selecionará a mensagem e fará o processamento necessário. Quando terminar, ele irá:
- Envie uma mensagem "SetPosition" para si mesmo, informando que o
Position
componente será escolhido;
- Ou acesse diretamente o
Position
componente para modificações (bastante errado para um design baseado em componente puro, pois você não pode assumir que todo objeto tem um Position
componente, mas o Position
componente pode ser um requisito Physics
).
Geralmente, é uma boa idéia adiar o processamento real da mensagem para a próxima atualização do componente. Processá-lo imediatamente pode significar enviar mensagens para outros componentes de outros objetos; portanto, enviar apenas uma mensagem pode significar rapidamente uma pilha de espaguete inextricável.
Você provavelmente terá que optar por um sistema mais avançado posteriormente: filas de mensagens assíncronas, envio de mensagens para um grupo de objetos, registro / cancelamento de registro por componente de mensagens etc.
A Message
classe pode ser um contêiner genérico para uma sequência simples, como mostrado acima, mas o processamento de sequências em tempo de execução não é realmente eficiente. Você pode optar por um contêiner de valores genéricos: cadeias, números inteiros, flutuantes ... Com um nome ou, melhor ainda, um ID, para distinguir diferentes tipos de mensagens. Ou você também pode derivar uma classe base para atender às necessidades específicas. No seu caso, você pode imaginar um EmitForceMessage
que deriva Message
e adiciona o vetor de força desejado - mas cuidado com o custo de tempo de execução do RTTI, se você fizer isso.
dynamic_cast
podem se tornar um gargalo, mas não me preocupo com isso por enquanto. Você ainda pode otimizar isso mais tarde, se isso se tornar um problema. Os identificadores de classe baseados em CRC funcionam como um encanto.O que eu fiz para resolver um problema semelhante ao que você mostra é adicionar alguns manipuladores de componentes específicos e algum tipo de sistema de resolução de eventos.
Portanto, no caso do seu objeto "Física", quando ele é inicializado, ele se adiciona a um gerente central de objetos de Física. No ciclo do jogo, esses tipos de gerentes têm sua própria etapa de atualização; portanto, quando este PhysicsManager é atualizado, calcula todas as interações da física e as adiciona a uma fila de eventos.
Depois de produzir todos os seus eventos, você pode resolver sua fila de eventos simplesmente verificando o que aconteceu e realizando ações de acordo. No seu caso, deve haver um evento dizendo que os objetos A e B interagiram de alguma forma, para que você chame seu método emitForceOn.
Prós deste método:
Contras:
Eu espero que isso ajude.
PS: Se alguém tiver uma maneira mais limpa / melhor de resolver isso, eu realmente gostaria de ouvir.
fonte
Algumas coisas a serem observadas neste design:
fonte