
Esta página explica como usar ScriptableObjects como contêineres de dados que separam os dados da lógica no código do seu jogo.
Este é o segundo de uma série de seis miniguias criados para auxiliar os desenvolvedores do Unity com a demonstração que acompanha o e-book, Crie arquitetura de jogo modular no Unity com ScriptableObjects.
A demonstração é inspirada na mecânica clássica de jogos de arcade de bola e raquete e mostra como o ScriptableObjects pode ajudar você a criar componentes testáveis, escaláveis e fáceis de usar.
Juntos, o e-book, o projeto de demonstração e esses miniguias fornecem práticas recomendadas para usar padrões de design de programação com a classe ScriptableObject no seu projeto Unity. Essas dicas podem ajudar você a simplificar seu código, reduzir o uso de memória e promover a reutilização do código.
Esta série inclui os seguintes artigos:
Antes de mergulhar no projeto de demonstração do ScriptableObject e nesta série de miniguias, lembre-se de que, em essência, os padrões de design são apenas ideias. Elas não se aplicam a todas as situações. Essas técnicas podem ajudar você a aprender novas maneiras de trabalhar com Unity e ScriptableObjects.
Cada padrão tem prós e contras. Escolha apenas aquelas 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.
Em última análise, a melhor arquitetura de código é aquela que se adapta ao seu projeto e à sua equipe.

Os desenvolvedores de software geralmente se preocupam com a modularidade – dividir um aplicativo em unidades menores e independentes. Cada módulo se torna responsável por um aspecto específico da funcionalidade do aplicativo.
No Unity, ScriptableObjects podem ajudar na separação de dados da lógica.
ScriptableObjects são excelentes para armazenar dados, especialmente quando são estáticos. Isso os torna ideais para estatísticas de jogos, valores de configuração para itens ou NPCs, diálogos de personagens e muito mais.
Isolar os dados de jogabilidade da lógica de comportamento pode tornar cada parte independente do projeto mais fácil de testar e manter. Essa “separação de preocupações” pode reduzir efeitos colaterais indesejados e não intencionais à medida que você faz as mudanças necessárias.

Se você quiser relembrar o fluxo de trabalho do ScriptableObject, este artigo do Unity Learn pode ajudar. Caso contrário, aqui vai uma explicação rápida:
Defina um ScriptableObject: Para criar um, defina uma classe C# que herde da classe base ScriptableObject com campos e propriedades para os dados que você deseja armazenar. ScriptableObjects podem armazenar os mesmos tipos de dados disponíveis para MonoBehaviours, tornando-os contêineres de dados versáteis. Adicione o CreateAssetMenuAttribute do Editor para facilitar a criação do ativo no projeto.
Criar um ativo: Depois de definir uma classe ScriptableObject, você pode criar uma instância desse ScriptableObject no projeto. Isso aparece como um recurso salvo no disco que você pode reutilizar em diferentes GameObjects e cenas.
Definir valores: Depois de criar o ativo, preencha-o com dados definindo os valores de seus campos e propriedades no Inspetor.
Use o ativo: Depois que o ativo contiver dados, faça referência a ele a partir de uma variável ou campo. Quaisquer alterações feitas no ativo ScriptableObject serão refletidas em todo o projeto.
Você pode reutilizar ScriptableObjects como contêineres de dados em diferentes partes do seu jogo. Por exemplo, você pode definir as propriedades de uma arma ou personagem dentro de um ScriptableObject e, então, referenciar esse ativo de qualquer lugar no projeto.
Observação: Você também pode gerar ScriptableObjects em tempo de execução por meio do método CreateInstance . Para armazenamento de dados, no entanto, você normalmente criará os ativos ScriptableObject com antecedência usando o CreateAssetMenuAttribute.

Para entender melhor por que ScriptableObjects são uma escolha mais adequada para armazenamento de dados do que MonoBehaviours, compare versões vazias de cada um. Certifique-se de definir sua serialização de ativos para Modo: Texto de Força nas Configurações do Projeto para visualizar a marcação YAML como texto.
Crie um novo GameObject com um MonoBehaviour vazio. Em seguida, compare isso com um ativo ScriptableObject vazio. Colocando-os lado a lado, eles devem ficar como a comparação mostrada na imagem acima.
ScriptableObjects são mais leves quando comparados aos MonoBehaviours e não carregam a sobrecarga associada a estes últimos, como o componente Transform. Isso dá aos ScriptableObjects um menor consumo de memória e os torna mais otimizados para armazenamento de dados.

ScriptableObjects são salvos como ativos, então eles persistem fora do modo Play, o que pode ser útil. Por exemplo, os dados do ScriptableObject estão disponíveis em qualquer lugar, mesmo se você carregar uma nova cena.
O exemplo de demonstração de Padrões apresenta uma tela de créditos básica que você pode testar. Modifique o ScriptableObject Credits_Data e pressione Atualizar para ver o texto armazenado aparecer.
Se você tivesse um RPG com uma grande quantidade de diálogos ou uma cena de tutorial com um roteiro pré-definido, essa seria uma maneira comum de armazenar muitos dados.
Embora os dados dentro do ScriptableObject sejam atualizados instantaneamente quando modificados, nosso projeto requer um botão Atualizar para atualizar a tela manualmente. A tela baseada no UI Toolkit é criada apenas uma vez e precisa ser notificada quando os dados forem alterados.
Crie um evento dentro do ScriptableObject se quiser sincronizar atualizações automaticamente. Por exemplo, este script ExampleSO chamaria o evento OnValueChanged toda vez que ExampleValue fosse alterado. Veja o exemplo de código abaixo.
Em seguida, faça com que seu objeto de interface de escuta assine OnValueChanged e atualize adequadamente.

ScriptableObjects brilham quando muitos objetos compartilham os mesmos dados. Por exemplo, se você estivesse criando um jogo de estratégia em que várias unidades tivessem a mesma velocidade de ataque e saúde máxima, seria ineficiente armazenar esses valores individualmente em cada GameObject.
Em vez disso, você pode consolidar dados compartilhados em um local central e fazer com que cada objeto faça referência a esse local compartilhado. No design de software, essa é uma otimização conhecida como padrão flyweight. Reestruturar seu código dessa maneira evita copiar muitos valores e reduz seu consumo de memória.
No PaddleBallSO, o GameDataSO ScriptableObject atua como armazenamento de dados compartilhado.
Em vez de manter uma cópia separada das configurações comuns (velocidade, massa, elasticidade física, etc.), os scripts Paddle and Ball fazem referência à mesma instância GameDataSO sempre que possível. Cada elemento do jogo mantém dados exclusivos, como posições e eventos de entrada, mas usa dados compartilhados como padrão quando possível.
Embora a economia de memória possa não ser perceptível com apenas dois ou três objetos, editar dados compartilhados é mais rápido e menos propenso a erros do que editar cada um manualmente.
Por exemplo, se você precisar modificar a velocidade da pá, ajustá-la em um único local atualizará ambas as pás em todas as cenas. Se você os armazenasse como campos exclusivos no MonoBehaviours, um clique incorreto poderia facilmente fazer com que dois valores ficassem fora de sincronia.
Descarregar dados em ScriptableObjects também pode ajudar no controle de versão e evitar conflitos de mesclagem quando colegas de equipe trabalham na mesma cena ou Prefab.

O GameDataSO mostra como usar um ScriptableObject como um contêiner de dados. No PaddleBallSO, isso inclui várias configurações para configurar a jogabilidade:
Com essas configurações e dados em um local central, o GameDataSO permite que qualquer objeto acesse esses dados compartilhados. Isso simplifica a maneira como você gerencia esses objetos e promove maior consistência em todo o seu projeto. Mudando a física da raquete? Faça uma alteração aqui em vez de ajustar vários scripts.

Às vezes, você pode ter o bolo e comê-lo também. Com a serialização dupla, você pode armazenar dados em um ScriptableObject e, ao mesmo tempo, mantê-los em outro formato.
O script LevelLayoutSO demonstra esse conceito. Além de manter as posições iniciais das raquetes e da bola, ele armazena dados de transformação das paredes e gols em uma estrutura personalizada.
Esses valores podem ser gravados no disco por meio do método ExportToJson . Os arquivos JSON são textos legíveis por humanos, permitindo modificações diretas fora do Unity. Isso permite que você trabalhe com ScriptableObjects no Editor e então armazene seus dados em outro local, como um arquivo JSON ou XML.
Formatos de arquivo como JSON e XML podem ser desafiadores para trabalhar no Editor, mas são fáceis de modificar fora do Unity em um editor de texto. Isso abre a possibilidade de níveis personalizados ou modificados pelo usuário.
O script GameSetup pode então usar um LevelLayout ScriptableObject ou um arquivo JSON externo para gerar o nível do jogo.
Para carregar um nível modificado personalizado, o script de configuração gera um ScriptableObject em tempo de execução com CreateInstance. Em seguida, ele lê o texto do arquivo JSON para preencher o ScriptableObject.
Seus dados personalizados substituem o conteúdo do ScriptableObject e permitem que você use este nível modificado externamente como qualquer outro. O restante do aplicativo funciona normalmente, sem perceber a mudança.

Embora nosso minijogo de paddle ball não possa demonstrar todos os casos de uso para contêineres de dados ScriptableObject, considere o seguinte para seus próprios aplicativos:
À medida que você se aprofunda nos ScriptableObjects e os adapta aos seus próprios projetos, você descobrirá ainda mais aplicações para eles. Eles são especialmente úteis para gerenciar dados e facilitam a manutenção da consistência entre vários elementos do jogo.

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