Composição OOP pesado versus sistemas de componentes de entidade pura? [fechadas]

13

Eu admito, cometi o pecado de abusar e até abusar da herança. O primeiro projeto de jogo (texto) que eu fiz quando estava no curso de OOP foi até "Porta trancada" e "porta destrancada" de "Porta" e "Sala com uma porta", "Sala com duas portas" e assim por diante a partir de "Room".

Com o jogo (gráfico) em que trabalhei recentemente, pensei ter aprendido minha lição e colocar um limite no uso de herança. No entanto, notei os problemas logo começando a aparecer. Minha classe raiz estava começando a inchar cada vez mais, e minhas classes folha estavam cheias de códigos duplicados.

Eu pensei que ainda estava fazendo as coisas erradas e, depois de pesquisar on-line, descobri que não era o único com esse problema. Foi assim que acabei descobrindo os sistemas de entidades após uma pesquisa completa (leia-se: googlefu)

Quando comecei a ler, pude ver com que clareza era possível resolver os problemas que eu estava tendo com a hierarquia OOP tradicional com componentes. Essas foram as primeiras leituras, no entanto. Quando me deparei com mais ... abordagens ES "radicais", como a da T-machine .

Comecei a discordar dos métodos que estavam usando. Um sistema de componentes puros parecia exagerar, ou melhor, não intuitivo, o que provavelmente é a força da OOP. O autor chega ao ponto de dizer que o sistema ES é o oposto do OOP e, embora possa ser usado junto com o OOP, realmente não deveria. Não estou dizendo que está errado, mas simplesmente não me senti uma solução que gostaria de implementar.

Então, para mim, e resolver os problemas que eu estava tendo no início do post, sem ir contra minhas intuições, é ainda usar uma hierarquia, no entanto, não será uma hierarquia monolítica como as que eu usei antes, mas sim uma polilítica (não consegui encontrar uma palavra oposta à monolítica), que consiste em várias árvores menores.

O exemplo a seguir mostra o que quero dizer (isso é inspirado em um exemplo que encontrei na Game Engine Architecture, capítulo 14).

Eu teria uma pequena árvore para veículos. A classe de veículo raiz teria um componente de renderização, um componente de colisão, componente de posição etc.

Em seguida, um tanque, uma subclasse de veículo herdaria esses componentes e receberia seu próprio componente "canhão".

O mesmo vale para os personagens. Um personagem teria seus próprios componentes, então a Classe de Jogador o herdaria e receberia um controlador de Entrada, enquanto outras classes inimigas herdariam da classe de Personagens e receberia um controlador de IA.

Realmente não vejo nenhum problema com este design. Apesar de não usar um Entity Controller System puro, o problema com o efeito borbulhante e a classe raiz grande são resolvidos usando uma hierarquia de várias árvores, e o problema das folhas pesadas e duplicadas de código desaparece, pois as folhas não tem qualquer código para começar, apenas componentes. Se for necessário fazer uma alteração no nível da folha, é tão simples quanto alterar um único componente, em vez de copiar e colar o código em qualquer lugar.

É claro que, por ser tão inexperiente quanto eu, não vi nenhum problema quando comecei a usar o modelo pesado de herança de hierarquia única; portanto, se houver problemas com o modelo que estou pensando em implementar, não seria ser capaz de vê-lo.

Suas opiniões?

PS: Estou usando Java, portanto, usar herança múltipla para implementar isso em vez de usar componentes normais não é possível.

PPS: As comunicações intercomponentes serão feitas vinculando componentes dependentes entre si. Isso levará ao acoplamento, mas acho que é uma troca aceitável.

Midori Ryuu
fonte
2
Tudo isso parece bom para mim. Existe um exemplo específico em sua hierarquia ou sistema de entidades que "cheira" a você? No final, trata-se de fazer o jogo mais do que um senso de pureza.
drhayes
Não, mas como disse, mal tenho experiência e estou longe de ser o melhor juiz para isso.
Midori Ryuu
3
Votei em encerrar esta questão porque é subjetiva e aberta a ampla discussão. Eu aprecio que tenha sido solicitado de uma posição sincera - mas escolher como proceder aqui é inteiramente uma questão de opinião pessoal. A única desvantagem real de fixar seus componentes em uma subclasse é que o arranjo dos componentes não pode ser alterado no tempo de execução - mas isso certamente deve ser aparente para você e é uma escolha que você faz.
Kylotan
4
Eu votei para fechar também. É uma pergunta boa e bem escrita, mas não apropriada para o GDSE. Você pode tentar repassar isso em GameDev.net.
21812 Sean Sean Middleditch
Como está escrito, com a pergunta sendo "Suas opiniões?", Ela é de fato aberta e sem resposta. No entanto, é responsável se você responder como se a pergunta fosse: "Existem armadilhas abertas nas quais eu poderia cair sem perceber?" (Pelo menos, se você acha que não há de fato nenhuma diferença empírica entre várias arquiteturas.)
John Calsbeek

Respostas:

8

Considere este exemplo:

Você está fazendo um RTS. Em um ajuste de lógica completa, você decide criar uma classe base GameObjecte, em seguida, duas subclasses, Buildinge Unit. Isso funciona muito bem, é claro, e você acaba com algo parecido com isto:

  • GameObject
    • ModelComponent
    • CollisionComponent
    • ...
  • Building
    • ProductionComponent
    • ...
  • Unit
    • AimingAIComponent
    • WeaponComponent
    • ParticleEmitterComponent
    • AnimationComponent
    • ...

Agora, cada subclasse que Unitvocê cria já possui todos os componentes necessários. E como bônus, você tem um único lugar para colocar todo esse código de configuração tedioso que newé um WeaponComponent, conecta-o a um AimingAIComponente um - ParticleEmitterComponentmaravilhoso!

E, é claro, porque ainda leva a lógica aos componentes, quando você finalmente decide que deseja adicionar uma Towerclasse que é um edifício, mas tem uma arma, você pode gerenciá-la. Você pode adicionar um AimingAIComponent, a WeaponComponente a ParticleEmitterComponentà sua subclasse de Building. Obviamente, você deve examinar e extrair o código de inicialização da Unitclasse e colocá-lo em outro lugar, mas isso não é uma grande perda.

E agora, dependendo da previsão, você pode ou não acabar com bugs sutis. Acontece que pode ter havido algum código em outro lugar no jogo que se parecia com isso:

if (myGameObject instanceof Unit)
    doSomething(((Unit)myGameObject).getWeaponComponent());

Isso silenciosamente não funciona para o seu Towerprédio, mesmo que você realmente queira que ele funcione para qualquer coisa com uma arma. Agora tem que se parecer com isso:

WeaponComponent weapon = myGameObject.getComponent(WeaponComponent.class);
if (weapon)
    doSomething(weapon);

Muito melhor! Depois de alterar isso, ocorre que qualquer um dos métodos de acesso que você escreveu pode acabar nessa situação. Portanto, a coisa mais segura a fazer é retirar todos eles e acessar todos os componentes através desse getComponentmétodo, que pode funcionar com qualquer subclasse. (Como alternativa, você pode adicionar todos os tipos get*Component()à superclasse GameObjecte substituí-los nas subclasses para retornar o componente. Porém, assumirei o primeiro.)

Agora, suas classes Buildinge Unitsão praticamente apenas sacos de componentes, sem sequer acessadores. E nenhuma inicialização sofisticada também, porque a maioria disso precisava ser arrastada para evitar duplicação de código com seu outro objeto de caso especial em algum lugar.

Se você seguir isso ao extremo, sempre acabará transformando suas subclasses em sacos de componentes completamente inertes; nesse momento, não há nenhum ponto em que as subclasses estejam por perto. Você pode obter quase todo o benefício de ter uma hierarquia de classes com uma hierarquia de métodos que cria os componentes adequados. Em vez de ter uma Unitclasse, você tem um addUnitComponentsmétodo que faz um AnimationComponente também chama addWeaponComponents, que faz um AimingAIComponent, a WeaponComponente a ParticleEmitterComponent. O resultado é que você tem todos os benefícios de um sistema de entidades, toda a reutilização de código de uma hierarquia e nenhuma tentação de verificar instanceofou transmitir ao seu tipo de classe.

John Calsbeek
fonte
Bem dito. Além disso, acho que a solução ideal é inicializar cada entidade com um script ou com arquivos descritores. Eu uso arquivos de texto para inicialização da entidade. É rápido e permite que não programadores testem parâmetros diferentes.
Coyote
Uau, ler o seu post me deu um pesadelo! É claro, quero dizer que, de uma maneira boa, visto que você abriu meus olhos para que tipo de pesadelo eu poderia acabar! Eu gostaria de poder responder mais a uma resposta tão detalhada, mas não há muito a acrescentar!
Midori Ryuu
De uma maneira mais simples. Você acaba usando Herança como padrão de fábrica do homem pobre.
Zardoz89 23/07
2

Composição sobre herança é a terminologia OOP (pelo menos da maneira que aprendi / fui ensinada). Para escolher entre composição e herança, você diz em voz alta "Carro é um tipo de roda" (herança) e "Carro tem rodas" (composição). Um deve parecer mais correto que o outro (composição neste caso) e, se você não puder decidir, use o padrão de composição até decidir o contrário.

Daniel Carlsson
fonte
1

Quando se trata de integrar sistemas baseados em componentes a jogos existentes, com base em objetos de jogos, há um artigo na programação do cowboy http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/ que pode fornecer algumas dicas sobre o como a transição pode ser feita.

Quanto a questões, seu modelo ainda precisará lidar com o jogador de maneira diferente de outro inimigo. Você não poderá mudar de status (ou seja, jogador se tornando controlado por IA) quando um jogador desconectar ou em casos especiais sem lidar com ele de maneira diferente dos outros personagens.

Além da pesada reutilização de componentes em diferentes entidades, a idéia de jogos baseados em componentes é a uniformidade de todo o sistema. Cada entidade possui componentes que podem ser anexados e desanexados à vontade. Todos os componentes do mesmo tipo são tratados exatamente da mesma maneira com um conjunto de chamadas estabelecidas pela interface (componente base) de cada tipo (posição, renderização, script ...)

A mistura da herança de objetos de jogo com uma abordagem baseada em componentes traz algumas vantagens da abordagem baseada em componentes com desvantagens de ambas as abordagens.

Pode funcionar para você. Mas eu não misturaria os dois fora de um período de transição de uma arquitetura para outra.

PS escrito em um telefone, por isso tenho dificuldade em vincular a recursos externos.

Coiote
fonte
Obrigado pela sua resposta, mesmo que estivesse ao telefone! Eu entendo o que você quer dizer. As coisas baseadas em componentes de movimento são, mais flexibilidade tenho para alterar as coisas. É a razão pela qual vou seguir em frente e tentar uma abordagem de composição com herança mínima. (Ainda mais do que o que eu propus.)
Midori Ryuu
@MidoriRyuu evita toda herança nos objetos de entidade. Você tem a sorte de usar uma linguagem com reflexão (e introspecção) incorporada. Você pode usar arquivos (txt ou XML) para inicializar seus objetos com os parâmetros corretos. Não é muito trabalho e permitirá um ajuste fino aprimorado do jogo sem recompilar. Mas mantenha a classe Entity, pelo menos para comunicação entre componentes e outras tarefas utilitárias. PS ainda está no telefone :(
Coyote