fundo
Faço o desenvolvimento de jogos como um hobby e estou procurando uma maneira melhor de projetá-los. Atualmente, estou usando uma abordagem padrão de POO (desenvolvo empresas há 8 anos, por isso é necessária a cada ano). Tomemos, por exemplo, um "vilão"
public class Baddie:AnimatedSprite //(or StaticSprite if needed, which inherit Sprite)
{
//sprite base will have things like what texture to use,
//what the current position is, the base Update/Draw/GetInput methods, etc..
//an AnimatedSprite contains helpers to animated the player while
//a StaticSprite is just one that will draw whatever texture is there
}
O problema
Digamos que eu estou fazendo um jogo de plataformas 2D e preciso do vilão para poder pular. Normalmente, o que faço é adicionar o código apropriado nos métodos Update / GetInput. Então, se eu precisar fazer o jogador engatinhar, pular, subir, etc ... o código vai para lá.
Se eu não tomar cuidado, esses métodos ficam desordenados, então acabo criando pares de métodos como este
CheckForJumpAction(Input input)
e DoJump()
CheckforDuckAction(Input input)
e DoDuck()
então GetInput parece
public void DoInput(Input input)
{
CheckForJumpAction(input);
CheckForDuckAction(input);
}
e atualização parece
public void Update()
{
DoJump();
DoDuck();
}
Se eu for criar outro jogo em que o jogador precise pular e pular, geralmente entro em um jogo que tem a funcionalidade e o copio. Bagunçado, eu sei. É por isso que estou procurando algo melhor.
Solução?
Eu realmente gosto de como o Blend tem comportamentos que posso anexar a um elemento. Eu tenho pensado em usar o mesmo conceito nos meus jogos. Então, vamos olhar para os mesmos exemplos.
Eu criaria um objeto Behavior básico
public class Behavior
{
public void Update()
Public void GetInput()
}
E eu posso criar comportamentos usando isso. JumpBehavior:Behavior
eDuckBehavior:Behavior
Posso adicionar uma coleção de comportamentos à base do Sprite e adicionar o que preciso para cada entidade.
public class Baddie:AnimatedSprite
{
public Baddie()
{
this.behaviors = new Behavior[2];
this.behaviors[0] = new JumpBehavior();
//etc...
}
public void Update()
{
//behaviors.update
}
public GetInput()
{
//behaviors.getinput
}
}
Então agora, se eu quisesse usar o Jump and Duck em muitos jogos, posso apenas trazer os comportamentos. Eu poderia até criar uma biblioteca para os comuns.
Funciona?
O que não consigo descobrir é como compartilhar o estado entre eles. Olhando Jump e Duck, ambos afetam não apenas a parte atual da textura que está sendo desenhada, mas também o estado do jogador. (O salto aplicará uma quantidade decrescente de força para cima ao longo do tempo, enquanto o duck apenas interromperá o movimento, mudará a textura e o tamanho da colisão do bandido.
Como posso amarrar isso para que funcione? Devo criar propriedades de dependência entre os comportamentos? Devo que cada comportamento conheça o pai e o modifique diretamente? Uma coisa que pensei foi poder passar um delegado em cada comportamento para ser executado quando ele é acionado.
Estou certo de que estou analisando mais problemas, mas o objetivo é poder reutilizar facilmente esses comportamentos entre jogos e entidades no mesmo jogo.
Então eu entrego a você. Importa-se de explicar como / se isso pode ser feito? Você tem uma ideia melhor? Sou todo ouvidos.
Respostas:
Dê uma olhada nesta apresentação. Parece muito próximo ao tipo de padrão que você está procurando. Esse padrão suporta comportamentos e propriedades anexáveis. Eu não acho que a apresentação mencione isso, mas você também pode criar eventos anexáveis. Essa ideia é semelhante às propriedades de dependência usadas no WPF.
fonte
Para mim, isso soa como um caso quase manual para o uso do Padrão de Estratégia . Nesse padrão, seus comportamentos genéricos podem ser definidos em interfaces que permitem trocar diferentes implementações de comportamentos em tempo de execução (pense em como uma ligação pode afetar a capacidade de pular ou correr do seu personagem).
Para um exemplo (artificial):
Agora você pode implementar vários tipos de saltos que seu personagem pode usar, sem necessariamente conhecer os detalhes sobre cada salto específico:
Todos os caracteres que você deseja ter a capacidade de pular agora podem conter uma referência a um IJumpable e podem começar a consumir suas várias implementações
Então seu código pode ser algo como:
Agora você pode facilmente reutilizar esses comportamentos, ou até estendê-los mais tarde, se desejar adicionar mais tipos de saltos ou criar mais jogos criados em uma única plataforma de rolagem lateral.
fonte
Dê uma olhada na arquitetura DCI, uma visão interessante da programação OO que pode ajudar.
A implementação de uma arquitetura de estilo DCI em C # requer o uso de classes de domínio simples e objetos de Contexto (comportamentos) com Métodos de Extensão e Interfaces para permitir que as classes de domínio colaborem sob diferentes funções. As interfaces são usadas para marcar as funções necessárias para um cenário. As classes de domínio implementam as interfaces (funções) que se aplicam ao comportamento pretendido. A colaboração entre as funções ocorre nos objetos de contexto (comportamento).
Você pode criar uma biblioteca de comportamentos e funções comuns que podem ser compartilhados entre objetos de domínio de projetos separados.
Veja DCI em C # para exemplos de código em C #.
fonte
Que tal passar um objeto de contexto para um comportamento que forneça métodos para alterar o estado dos sprites / objetos afetados por um comportamento? Agora, se um objeto tem vários comportamentos, cada um deles é chamado em loop, dando a chance de mudar o contexto. Se uma certa modificação de uma propriedade excluir / restringir a modificação de outras propriedades, o objeto de contexto pode fornecer métodos para definir algum tipo de sinalizador para indicar esse fato, ou pode simplesmente negar modificações indesejadas.
fonte