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.
fonte
Respostas:
Considere este exemplo:
Você está fazendo um RTS. Em um ajuste de lógica completa, você decide criar uma classe base
GameObject
e, em seguida, duas subclasses,Building
eUnit
. 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
Unit
você 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 quenew
é umWeaponComponent
, conecta-o a umAimingAIComponent
e um -ParticleEmitterComponent
maravilhoso!E, é claro, porque ainda leva a lógica aos componentes, quando você finalmente decide que deseja adicionar uma
Tower
classe que é um edifício, mas tem uma arma, você pode gerenciá-la. Você pode adicionar umAimingAIComponent
, aWeaponComponent
e aParticleEmitterComponent
à sua subclasse deBuilding
. Obviamente, você deve examinar e extrair o código de inicialização daUnit
classe 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:
Isso silenciosamente não funciona para o seu
Tower
prédio, mesmo que você realmente queira que ele funcione para qualquer coisa com uma arma. Agora tem que se parecer com isso: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
getComponent
método, que pode funcionar com qualquer subclasse. (Como alternativa, você pode adicionar todos os tiposget*Component()
à superclasseGameObject
e substituí-los nas subclasses para retornar o componente. Porém, assumirei o primeiro.)Agora, suas classes
Building
eUnit
sã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
Unit
classe, você tem umaddUnitComponents
método que faz umAnimationComponent
e também chamaaddWeaponComponents
, que faz umAimingAIComponent
, aWeaponComponent
e aParticleEmitterComponent
. 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 verificarinstanceof
ou transmitir ao seu tipo de classe.fonte
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.
fonte
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.
fonte