Obtenha um melhor fluxo de trabalho de cena com ScriptableObjects

Gerenciar várias cenas no Unity pode ser um desafio, e melhorar esse fluxo de trabalho é crucial para o desempenho do seu jogo e a produtividade da sua equipe. Aqui, compartilhamos algumas dicas para configurar seus fluxos de trabalho de cena de forma que sejam escaláveis para projetos maiores.
A maioria dos jogos envolve vários níveis, e os níveis geralmente contêm mais de uma cena. Em jogos onde as cenas são relativamente pequenas, você pode dividi-las em seções diferentes usando Prefabs. Entretanto, para habilitá-los ou instanciá-los durante o jogo, você precisa referenciar todos esses Prefabs. Isso significa que, à medida que seu jogo fica maior e essas referências ocupam mais espaço na memória, fica mais eficiente usar Cenas.
Você pode dividir seus níveis em uma ou várias Cenas de Unidade. Encontrar a maneira ideal de gerenciar todos eles se torna essencial. Você pode abrir várias Cenas no Editor e em tempo de execução usando a edição Multi-Cena. Dividir níveis em várias cenas também tem a vantagem de facilitar o trabalho em equipe, pois evita conflitos de mesclagem em ferramentas de colaboração como Git, SVN, Unity Collaborate e similares.
No vídeo abaixo, mostramos como carregar um nível de forma mais eficiente dividindo a lógica do jogo e as diferentes partes do nível em várias Cenas Unity distintas. Então, usando o modo de carregamento de cena aditivo ao carregar essas cenas, carregamos e descarregamos as partes necessárias junto com a lógica do jogo, que é persistente. Usamos Prefabs para atuar como “âncoras” para as Cenas, o que também oferece muita flexibilidade ao trabalhar em equipe, pois cada Cena representa uma parte do nível e pode ser editada separadamente.
Você ainda pode carregar essas Cenas no Modo de Edição e pressionar Reproduzir a qualquer momento, para poder visualizá-las todas juntas ao criar o design do nível.
Mostramos dois métodos diferentes para carregar essas cenas. O primeiro é baseado na distância, o que é mais adequado para níveis não internos, como um mundo aberto. Essa técnica também é útil para alguns efeitos visuais (como neblina, por exemplo) para esconder o processo de carga e descarga.
A segunda técnica usa um gatilho para verificar quais cenas carregar, o que é mais eficiente ao trabalhar com interiores.
Agora que tudo é gerenciado dentro do nível, você pode adicionar uma camada sobre ele para gerenciar melhor os níveis.
Queremos monitorar as diferentes cenas de cada nível, bem como todos os níveis durante toda a duração do jogo. Uma maneira possível de fazer isso é usar variáveis estáticas e o padrão singleton em seus scripts MonoBehaviour, mas há alguns problemas com essa solução. Usar o padrão singleton permite conexões rígidas entre seus sistemas, portanto, não é estritamente modular. Os sistemas não podem existir separadamente e sempre dependerão uns dos outros.
Outro problema envolve o uso de variáveis estáticas. Como você não pode vê-los no Inspetor, você precisa alterar o código para defini-los, dificultando que artistas ou designers de níveis testem o jogo facilmente. Quando você precisa que dados sejam compartilhados entre diferentes Cenas, use variáveis estáticas combinadas com DontDestroyOnLoad, mas este último deve ser evitado sempre que possível.
Para armazenar informações sobre as diferentes cenas, você pode usar ScriptableObject, que é uma classe serializável usada principalmente para armazenar dados. Ao contrário dos scripts MonoBehaviour, que são usados como componentes anexados aos GameObjects, os ScriptableObjects não são anexados a nenhum GameObject e, portanto, podem ser compartilhados entre as diferentes cenas de todo o projeto.
Você quer poder usar essa estrutura para níveis, mas também para cenas de menu no seu jogo. Para fazer isso, crie uma classe GameScene que contenha as diferentes propriedades comuns entre níveis e menus.
Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`
Observe que a classe herda de ScriptableObject e não de MonoBehaviour. Você pode adicionar quantas propriedades precisar para seu jogo. Após esta etapa, você pode criar classes Level e Menu que herdam da classe GameScene que acabou de ser criada – portanto, elas também são ScriptableObjects.
Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`
Adicionar o atributo CreateAssetMenu na parte superior permite que você crie um novo nível no menu Ativos no Unity. Você pode fazer o mesmo para a classe Menu. Você também pode incluir uma enumeração para poder escolher o tipo de menu no Inspetor.
Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`
Agora que você pode criar níveis e menus, vamos adicionar um banco de dados que liste os níveis e menus para fácil referência. Você também pode adicionar um índice para rastrear o nível atual do jogador. Depois, você pode adicionar métodos para carregar um novo jogo (nesse caso, o primeiro nível será carregado), para repetir o nível atual e para ir para o próximo nível. Observe que apenas o índice muda entre esses três métodos, então você pode criar um método que carregue o nível com um índice para usá-lo várias vezes.
Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`
Também há métodos para os menus, e você pode usar o tipo enum que criou antes para carregar o menu específico que deseja – apenas certifique-se de que a ordem no enum e a ordem na lista de menus seja a mesma.
Agora você pode finalmente criar um ScriptableObject de nível, menu ou banco de dados no menu Ativos clicando com o botão direito do mouse na janela Projeto.

A partir daí, basta continuar adicionando os níveis e menus necessários, ajustando as configurações e, em seguida, adicionando-os ao banco de dados de Cenas. O exemplo abaixo mostra a aparência dos dados Level1, MainMenu e Scenes.

É hora de chamar esses métodos. Neste exemplo, o botão Próximo Nível na interface do usuário (IU) que aparece quando um jogador chega ao final do nível chama o método NextLevel. Para anexar o método ao botão, clique no botão de adição do evento On Click do componente Button para adicionar um novo evento, depois arraste e solte o Scenes Data ScriptableObject no campo do objeto e escolha o método NextLevel em ScenesData, conforme mostrado abaixo.

Agora você pode passar pelo mesmo processo para os outros botões – para repetir o nível ou ir para o menu principal, e assim por diante. Você também pode referenciar o ScriptableObject de qualquer outro script para acessar as diferentes propriedades, como o AudioClip para a música de fundo ou o perfil de pós-processamento, e usá-los no nível.
- Minimizar o carregamento/descarregamento
No script ScenePartLoader mostrado no vídeo, você pode ver que um jogador pode entrar e sair do colisor várias vezes, acionando o carregamento e descarregamento repetidos de uma cena. Para evitar isso, você pode adicionar uma corrotina antes de chamar os métodos de carregamento e descarregamento da Cena no script e interromper a corrotina se o jogador sair do gatilho.
- Convenções de nomenclatura
Outra dica geral é usar convenções de nomenclatura sólidas no projeto. A equipe deve concordar previamente sobre como nomear os diferentes tipos de ativos – de scripts e cenas a materiais e outras coisas no projeto. Isso tornará mais fácil não só para você, mas também para seus colegas de equipe, trabalhar no projeto e mantê-lo. Esta é sempre uma boa ideia, mas é crucial para o gerenciamento de cena com ScriptableObjects neste caso específico. Nosso exemplo usou uma abordagem direta baseada no nome da cena, mas há muitas soluções diferentes que dependem menos do nome da cena. Você deve evitar a abordagem baseada em strings porque se você renomear uma Cena do Unity em um determinado contexto, em outra parte do jogo essa Cena não será carregada.
- Ferramentas personalizadas
Uma maneira de evitar a dependência de nomes em todo o jogo é configurar seu script para referenciar Cenas como tipo de Objeto . Isso permite que você arraste e solte um ativo de cena em um Inspetor e, então, obtenha seu nome com segurança em um script. No entanto, como é uma classe Editor, você não tem acesso à classe AssetDatabase em tempo de execução, então você precisa combinar ambos os dados para uma solução que funcione no Editor, evite erros humanos e ainda funcione em tempo de execução. Você pode consultar a interface ISerializationCallbackReceiver para obter um exemplo de como implementar um objeto que, após a serialização, pode extrair o caminho da string do ativo Scene e armazená-lo para ser usado em tempo de execução.
Além disso, você também pode criar um Inspetor personalizado para facilitar a adição rápida de Cenas às Configurações de Construção usando botões, em vez de ter que adicioná-las manualmente por meio desse menu e mantê-las sincronizadas.
Como exemplo desse tipo de ferramenta, confira esta ótima implementação de código aberto do desenvolvedor JohannesMP (este não é um recurso oficial do Unity).
Esta postagem mostra apenas uma maneira como o ScriptableObjects pode aprimorar seu fluxo de trabalho ao trabalhar com várias cenas combinadas com Prefabs. Diferentes jogos têm maneiras muito diferentes de gerenciar cenas – nenhuma solução única funciona para todas as estruturas de jogo. Faz muito sentido implementar suas próprias ferramentas personalizadas para se adequar à organização do seu projeto.
Esperamos que essas informações possam ajudar você em seu projeto ou talvez inspirá-lo a criar suas próprias ferramentas de gerenciamento de cena.
Deixe-nos saber nos comentários se você tiver alguma dúvida. Gostaríamos de saber quais métodos você usa para gerenciar as Cenas no seu jogo. E fique à vontade para sugerir outros casos de uso que você gostaria que abordássemos em futuras postagens do blog.