A implementação de padrões comuns de design de programação de jogos em seu projeto Unity pode ajudá-lo a criar e manter com eficiência uma base de código limpa, organizada e legível. Os padrões de design reduzem o tempo de refatoração e teste, acelerando os processos de desenvolvimento e contribuindo para uma base sólida para o crescimento do jogo, da equipe e dos negócios.
Pense nos padrões de projeto não como soluções prontas que você pode copiar e colar no seu código, mas como ferramentas extras que podem ajudá-lo a criar aplicativos maiores e escalonáveis.
Esta página explica o padrão de design de fábrica.
O conteúdo aqui é baseado no livro eletrônico gratuito, Eleve o nível de seu código com padrões de programação de jogos.
Confira mais artigos da série de padrões de design de programação de jogos do Unity no hub de práticas recomendadas do Unity ou por meio destes links:
Às vezes, é útil ter um objeto especial que crie outros objetos. Muitos jogos geram uma variedade de coisas no decorrer do jogo, e muitas vezes você não sabe do que precisa em tempo de execução até que realmente precise.
O padrão de fábrica designa um objeto especial chamado - você adivinhou - uma fábrica para essa finalidade. Em um nível, ele encapsula muitos dos detalhes envolvidos na geração de seus "produtos". O benefício imediato é a organização de seu código.
No entanto, se cada produto seguir uma interface comum ou uma classe base, você poderá dar um passo adiante e fazer com que ele contenha mais de sua própria lógica de construção, ocultando-a da própria fábrica. Assim, a criação de novos objetos se torna mais extensível.
Você também pode fazer uma subclasse da fábrica para criar várias fábricas dedicadas a produtos específicos. Isso ajuda a gerar inimigos, obstáculos ou qualquer outra coisa em tempo de execução.
Há um projeto de amostra disponível no GitHub que demonstra diferentes padrões de design de programação no contexto do desenvolvimento de jogos, incluindo o padrão de fábrica.
O exemplo de padrão de fábrica consiste em um código para um jogador se movimentar em um labirinto. No labirinto, você pode gerar dois GameObjects diferentes chamados produtos clicando neles. Ambos usam a mesma interface e compartilham um formato semelhante, mas um gera partículas e o outro reproduz um som.
A cena do padrão de fábrica está na pasta denominada "6 Factory".
Imagine que você queira criar um padrão de fábrica para instanciar itens para um nível de jogo. Você pode usar Prefabs para criar GameObjects, mas talvez também queira executar algum comportamento personalizado ao criar cada instância.
Em vez de usar instruções if ou um switch para manter essa lógica, crie uma interface chamada IProduct e uma classe abstrata chamada Factory, conforme descrito no exemplo de código.
Os produtos precisam seguir um modelo específico para seus métodos, mas não compartilham nenhuma outra funcionalidade. Portanto, você define a interface IProduct.
As fábricas podem precisar de alguma funcionalidade comum compartilhada, portanto, este exemplo usa classes abstratas. Lembre-se apenas da substituição de Liskov dos princípios SOLID ao usar subclasses. Ele afirma que os objetos de uma superclasse devem poder ser substituídos por objetos de uma subclasse sem afetar a correção do programa. Em outras palavras, qualquer programa que use uma referência de superclasse deve ser capaz de usar qualquer uma de suas subclasses sem saber.
A interface IProduct define o que é comum entre seus produtos. Nesse caso, você simplesmente tem uma propriedade ProductName e qualquer lógica que o produto execute em Initialize.
Você pode então definir quantos produtos precisar(ProductA, ProductB etc.), desde que eles sigam a interface IProduct.
A classe base, Factory, tem um método GetProduct que retorna um IProduct. Ele é abstrato, portanto, você não pode criar instâncias do Factory diretamente. Você deriva um par de subclasses concretas(ConcreteFactoryA e ConcreteFactoryB), que de fato obterão os diferentes produtos.
Neste exemplo, GetProduct usa uma posição Vector3 para que você possa instanciar um Prefab GameObject mais facilmente em um local específico. Um campo em cada fábrica de concreto também armazena o modelo Prefab correspondente.
O resultado é uma estrutura que se parece com a imagem acima.
No trecho de código, você pode ver um exemplo de ProductA e ConcreteFactoryA.
Aqui, você fez com que as classes de produto MonoBehaviours que implementam IProduct aproveitassem os Prefabs na fábrica.
Observe como cada produto pode ter sua própria versão do Initialize. O exemplo ProductA Prefab contém um ParticleSystem, que é reproduzido quando o ConcreteFactoryA instancia uma cópia. A fábrica em si não contém nenhuma lógica específica para acionar as partículas; ela apenas invoca o método Initialize, que é comum a todos os produtos.
Explore o projeto de amostra para ver como o componente ClickToCreate alterna entre fábricas para criar ProductA e ProductB, que têm comportamentos diferentes. O ProductB emite um som quando surge, enquanto o ProductA aciona um efeito de partícula para ilustrar o conceito central das variações do produto.
Você se beneficiará ao máximo do padrão de fábrica ao configurar muitos produtos. A definição de novos tipos de produtos em seu aplicativo não altera os tipos existentes nem exige que você modifique o código anterior.
A separação da lógica interna de cada produto em sua própria classe mantém o código de fábrica relativamente curto. Cada fábrica só sabe invocar o Initialize em cada produto sem ter conhecimento dos detalhes subjacentes.
A desvantagem é que você cria várias classes e subclasses para implementar o padrão. Como os outros padrões, isso introduz um pouco de sobrecarga, que pode ser desnecessária se você não tiver uma grande variedade de produtos. Por outro lado, o tempo inicial gasto na configuração das classes pode ser uma vantagem a longo prazo em termos de desacoplamento do código e de facilitar a manutenção.
A implementação da fábrica pode variar muito em relação ao que é mostrado aqui. Considere os seguintes ajustes ao criar seu próprio padrão de fábrica:
Use um dicionário para pesquisar produtos: Talvez você queira armazenar seus produtos como pares de valores-chave em um dicionário. Use um identificador de cadeia de caracteres exclusivo (por exemplo, o Nome ou alguma ID) como a chave e o tipo como um valor. Isso pode tornar mais conveniente a recuperação de produtos e/ou de suas fábricas correspondentes.
Tornar a fábrica (ou um gerente de fábrica) estática: Isso facilita o uso, mas exige uma configuração adicional. As classes estáticas não aparecerão no Inspetor, portanto, você precisará tornar sua coleção de produtos estática também.
Aplique-o a objetos que não sejam do jogo e a comportamentos que não sejam do MonoBehavior: Não se limite a Prefabs ou outros componentes específicos da Unity. O padrão de fábrica pode funcionar com qualquer objeto C#.
Combine com o padrão de pool de objetos: As fábricas não precisam necessariamente instanciar ou criar novos objetos. Eles também podem recuperar os existentes na hierarquia. Se estiver instanciando muitos objetos de uma vez (por exemplo, projéteis de uma arma), use o padrão de pool de objetos para otimizar o gerenciamento de memória.
As fábricas podem gerar qualquer elemento de jogo de acordo com a necessidade. No entanto, a criação de produtos geralmente não é seu único objetivo. Você pode estar usando o padrão factory como parte de outra tarefa maior (por exemplo, configurar elementos da interface do usuário em uma caixa de diálogo de partes de um nível de jogo).
Encontre mais dicas sobre como usar padrões de design em seus aplicativos Unity, bem como os princípios SOLID, no e-book gratuito Level up your code with game programming patterns.
Você pode encontrar todos os e-books e artigos técnicos avançados do Unity no hub de práticas recomendadas. Os e-books também estão disponíveis na página de práticas recomendadas avançadas na documentação.