
Esta página explica como usar ScriptableObjects como contêineres de lógica. Ao fazer isso, você pode tratá-los como objetos delegados, ou pequenos pacotes de ações que você pode chamar quando necessário.
Este é o quarto de uma série de seis mini-guias criadas para ajudar desenvolvedores Unity com o demo que acompanha o e-book, Crie arquitetura de jogo modular no Unity com ScriptableObjects.
O demo é inspirado na mecânica clássica de jogos de arcade de bola e raquete, e mostra como ScriptableObjects podem ajudá-lo a criar componentes que são testáveis, escaláveis e amigáveis para designers.
Juntos, o e-book, o projeto demo e estas mini-guias fornecem as melhores práticas para usar padrões de design de programação com a classe ScriptableObject em seu projeto Unity. Essas dicas podem ajudá-lo a simplificar seu código, reduzir o uso de memória e promover a reutilização de código.
Esta série inclui os seguintes artigos:
Antes de você mergulhar no projeto de demonstração do ScriptableObject e nesta série de mini-guias, lembre-se de que, em sua essência, os padrões de design são apenas ideias. Eles não se aplicam a todas as situações. Essas técnicas podem ajudá-lo a aprender novas maneiras de trabalhar com Unity e ScriptableObjects.
Cada padrão tem prós e contras. Escolha apenas aqueles que beneficiam significativamente seu projeto específico. Seus designers dependem muito do Unity Editor? Um padrão baseado em ScriptableObject pode ser uma boa escolha para ajudá-los a colaborar com seus desenvolvedores.
No final, a melhor arquitetura de código é aquela que se adapta ao seu projeto e equipe.

Usando o padrão de estratégia, você pode definir uma interface ou classe base ScriptableObject e, em seguida, tornar esses objetos delegados intercambiáveis em tempo de execução.
Uma aplicação é encapsular algoritmos para realizar tarefas específicas em um ScriptableObject e, em seguida, usar esse ScriptableObject no contexto de algo mais.
Por exemplo, se você estivesse escrevendo um sistema de IA ou de busca de caminho para uma classe EnemyUnit, você poderia criar um ScriptableObject com uma técnica de busca de caminho (como A*, Dijkstra, etc.).
A própria EnemyUnit não conteria nenhuma lógica de busca de caminho. Em vez disso, ela manteria uma referência a um ScriptableObject "estratégia" separado. A vantagem desse design é que você pode trocar para um algoritmo diferente simplesmente trocando objetos. Esta é uma maneira de escolher comportamentos diferentes em tempo de execução.
Quando o MonoBehaviour precisa realizar uma tarefa, ele chama os métodos externos no ScriptableObject em vez de seus próprios. Por exemplo, o ScriptableObject pode conter métodos públicos para MoverUnidade ou DefinirAlvo para controlar a unidade inimiga e especificar um destino.
Você pode melhorar esse padrão com uma classe base abstrata ou uma interface. Fazer isso significa que qualquer ScriptableObject que implementa a estratégia pode ser trocado por outro. Esse ScriptableObject intercambiável "se conecta" ao MonoBehaviour que o referencia – mesmo em tempo de execução.
Se você precisar que o EnemyUnit mude comportamentos devido a condições do jogo, o contexto externo (o MonoBehaviour) pode verificar essas condições. Então, ele pode conectar um ScriptableObject diferente como resposta.
Ao separar os detalhes de implementação em um ScriptableObject, você também pode facilitar uma melhor divisão de responsabilidades entre sua equipe. Um desenvolvedor pode se concentrar no algoritmo dentro do ScriptableObject, enquanto outro trabalha no contexto do MonoBehaviour.
Para criar esse comportamento plugável, certifique-se de:
Organizar seu código dessa forma pode facilitar a troca entre diferentes implementações da mesma estratégia. Esse comportamento plugável se torna mais fácil de depurar e manter.
Um algoritmo ou estratégia não precisa ser complicado. O projeto PaddleBallSO, por exemplo, demonstra um sistema de reprodução de áudio bastante básico no SimpleAudioDelegate.
A classe abstrata, AudioDelegateSO, define um único método Play que aceita um parâmetro AudioSource. A implementação concreta então substitui isso.
A subclasse SimpleAudioDelegateSO define um array de AudioClips. Ela escolhe um clipe aleatório e o reproduz usando a implementação do método Play sobrescrito. Isso adiciona uma variação na tonalidade e no volume dentro de um intervalo personalizado.
Embora sejam apenas algumas linhas, você pode criar muitos efeitos de áudio diferentes com o trecho de código abaixo.
Embora este exemplo específico não seja realmente adequado para uso intenso de áudio, ele é apresentado aqui como uma demonstração básica de uso de ScriptableObjects em um padrão de estratégia.
Um designer pode criar muitos ScriptableObjects diferentes para representar efeitos sonoros sem tocar no código. Novamente, isso requer suporte mínimo de um desenvolvedor uma vez que o ScriptableObject base esteja completo.
Em PaddleBallSO, qualquer um pode agora configurar um novo array de sons para reproduzir quando a bola atinge uma das paredes do nível. Os designers ganham independência criativa e flexibilidade porque estão trabalhando inteiramente no Editor. Essa abordagem libera recursos de programação, já que os desenvolvedores não precisam mais ajudar com cada decisão de design.

Você também pode ver o exemplo de áudio na demonstração de Padrões. Cada som deriva de um ativo SimpleAudioDelegateSO ligeiramente diferente, com pequenas variações entre as instâncias.
Neste exemplo, cada canto inclui um AudioSource. Um MonoBehaviour AudioModifier personalizado usa um delegado baseado em ScriptableObject para reproduzir som.
As diferenças de tom decorrem apenas das configurações em cada ativo ScriptableObject (BeepHighPitched_SO, BeepLowPitched_SO, etc.).
Usar um ScriptableObject para controlar a lógica de ação pode facilitar para sua equipe de design experimentar ideias. Isso permite que um designer trabalhe de forma mais independente de um desenvolvedor.

O projeto PaddleBallSO também usa o padrão de estratégia em seu sistema de objetivos. Embora isso não seja algo que precise variar em tempo de execução, encapsular cada objeto em um ScriptableObject fornece uma maneira flexível de testar condições de vitória e derrota.
A classe base abstrata, ObjectiveSO, mantém valores como o nome do objetivo e se ele foi concluído.
As subclasses concretas, como ScoreObjectiveSO, implementam a lógica real sobre como completar cada objetivo. Eles fazem isso sobrescrevendo o método CompleteObjective do ObjectiveSO e adicionando a lógica da condição de vitória.
O jogador precisa atingir uma pontuação específica ou derrotar um certo número de inimigos? Eles precisam chegar a um local específico ou pegar um item específico? Estas são condições de vitória comuns que poderiam se tornar objetivos baseados em ScriptableObject.
O ObjectiveManager serve como o contexto maior para os ScriptableObjects. Ele mantém uma lista de ObjectiveSOs e depende de cada ScriptableObject para determinar se está completo. Quando cada ObjectiveSO mostra um estado de conclusão, o jogo termina.
Por exemplo, o ScoreObjectiveSO mostra uma maneira de implementar um objetivo de pontuação:
O ObjectiveManager só se preocupa que todos os objetivos dados estejam completos. Ele não está ciente dos detalhes dentro de cada objetivo em si.
Novamente, o objetivo aqui é a modularidade. Isso permite que você personalize cada ObjectiveSO sem afetar os já existentes.
O jogo PaddleBallSO realmente tem apenas um objetivo. Se um dos jogadores atingir a meta de pontuação vencedora, o jogo termina.
No entanto, você pode estender isso ou combinar objetivos para criar um sistema de objetivos mais complexo. Experimente e veja se você consegue criar novos modos de jogo (por exemplo, marcar um número mínimo de pontos antes que o tempo acabe).
Como a lógica está encapsulada dentro de um ScriptableObject, você pode trocar qualquer ObjectiveSO por outro. Criar uma nova condição de vitória envolve simplesmente reconfigurar a lista no ObjectiveManager. De certa forma, o objetivo é "plugável" no contexto ao redor.
Observe que um aspecto útil do ObjectiveSO é o evento usado para enviar mensagens entre GameObjects. Em seguida, vamos explorar como usar ScriptableObjects para implementar essa arquitetura orientada a eventos.

Leia mais sobre padrões de design com ScriptableObjects no e-book Criar arquitetura de jogo modular no Unity com ScriptableObjects. Você também pode descobrir mais sobre padrões de design comuns no desenvolvimento do Unity em Melhore seu código com padrões de programação de jogos.