Truques avançados de script do Editor para economizar seu tempo, parte 1

JORDI CABALLOL / UNITYSenior Software Engineer
Oct 18, 2022|15 Min
Truques avançados de script do Editor para economizar seu tempo, parte 1
Esta página da Web foi automaticamente traduzida para sua conveniência. Não podemos garantir a precisão ou a confiabilidade do conteúdo traduzido. Se tiver dúvidas sobre a precisão do conteúdo traduzido, consulte a versão oficial em inglês da página da Web.

Na maioria dos projetos que vi, há muitas tarefas que os desenvolvedores realizam que são repetitivas e propensas a erros, especialmente quando se trata de integrar novos recursos de arte. Por exemplo, configurar um personagem geralmente envolve arrastar e soltar muitas referências de ativos, marcar caixas de seleção e clicar em botões: Defina o equipamento do modelo como Humanoide, desabilite o sRGB da textura SDF, defina os mapas normais como mapas normais e as texturas da IU como sprites. Em outras palavras, tempo valioso é gasto e etapas cruciais ainda podem ser perdidas.

Neste artigo de duas partes, mostrarei dicas que podem ajudar a melhorar esse fluxo de trabalho para que seu próximo projeto seja mais tranquilo que o anterior. Para ilustrar melhor isso, criei um protótipo simples – semelhante a um RTS – onde as unidades de uma equipe atacam automaticamente edifícios inimigos e outras unidades. Com cada hack de script, melhorarei um aspecto desse processo, sejam as texturas ou os modelos.

Veja como é o protótipo:

Dica 1: Organize e automatize seus ativos

O principal motivo pelo qual os desenvolvedores precisam configurar tantos pequenos detalhes ao importar ativos é simples: O Unity não sabe como você vai usar um ativo, então ele não pode saber quais são as melhores configurações para ele. Se você quiser automatizar algumas dessas tarefas, esse é o primeiro problema que precisa ser resolvido.

A maneira mais simples de descobrir para que serve um ativo e como ele se relaciona com outros é seguir uma convenção de nomenclatura e estrutura de pastas específicas, como:

  • Convenção de nomenclatura: Podemos acrescentar coisas ao nome do próprio ativo, portanto Shield_BC.png é a cor base, enquanto Shield_N.png é o mapa normal.
  • Estrutura da pasta: Knight/Animations/Walk.fbx é claramente uma animação, enquanto Knight/Models/Knight.fbx é um modelo, embora ambos compartilhem o mesmo formato (.fbx).

O problema é que isso só funciona bem em uma direção. Então, embora você já saiba para que serve um ativo quando recebe seu caminho, não é possível deduzi-lo se receber apenas informações sobre o que o ativo faz. Ser capaz de encontrar um recurso – por exemplo, o material para um personagem – é útil ao tentar automatizar a configuração de alguns aspectos dos recursos. Embora isso possa ser resolvido usando uma convenção de nomenclatura rígida para garantir que o caminho seja fácil de deduzir, ele ainda é suscetível a erros. Mesmo que você se lembre da convenção, erros de digitação são comuns.

Uma abordagem interessante para resolver isso é usar rótulos. Você pode usar um script do Editor que analisa os caminhos dos ativos e atribui rótulos a eles adequadamente. Como os rótulos são automatizados, é possível descobrir o rótulo exato que um ativo terá. Você pode até mesmo procurar ativos pelo rótulo usando AssetDatabase.FindAssets.

Se você quiser automatizar essa sequência, há uma classe que pode ser muito útil chamada AssetPostprocessor. O AssetPostprocessor recebe várias mensagens quando o Unity importa ativos. Um deles é OnPostprocessAllAssets, um método que é chamado sempre que o Unity termina de importar ativos. Ele fornecerá todos os caminhos para os ativos importados, proporcionando uma oportunidade de processar esses caminhos. Você pode escrever um método simples, como o seguinte, para processá-los:

No caso do protótipo, vamos nos concentrar na lista de ativos importados – tanto para tentar capturar novos ativos quanto os ativos movidos. Afinal, conforme o caminho muda, talvez queiramos atualizar os rótulos.

Para criar os rótulos, analise o caminho e procure por pastas relevantes, prefixos e sufixos do nome, bem como as extensões. Depois de gerar os rótulos, combine-os em uma única sequência e defina-os para o ativo.

Para atribuir os rótulos, carregue o ativo usando AssetDatabase.LoadAssetAtPathe, em seguida, atribua seus rótulos com AssetDatabase.SetLabels.

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Lembre-se, é importante definir rótulos somente se eles realmente tiverem mudado. Definir rótulos acionará uma reimportação do ativo, então você não quer que isso aconteça, a menos que seja estritamente necessário.

Se você marcar isso, a reimportação não será um problema: Os rótulos são definidos na primeira vez que você importa um ativo e salvos no arquivo .meta, o que significa que também são salvos no seu controle de versão. Uma reimportação só será acionada se você renomear ou mover seus ativos.

Com as etapas acima concluídas, todos os ativos são rotulados automaticamente, como no exemplo mostrado abaixo.

Captura de tela do pós-processamento de rotulagem automática no Editor Unity.
Dica 2: Determinar configurações e tamanhos de textura

Importar texturas para um projeto geralmente envolve ajustar as configurações de cada textura. É uma textura regular? Um mapa normal? Um sprite? É linear ou sRGB? Se quiser alterar as configurações de um importador de ativos, você pode usar o AssetPostprocessor mais uma vez.

Nesse caso, você vai querer usar a mensagem OnPreprocessTexture , que é chamada logo antes de importar uma textura. Isso permite que você altere as configurações do importador.

Quando se trata de selecionar as configurações corretas para cada textura, você precisa verificar com que tipo de textura está trabalhando – e é exatamente por isso que os rótulos são essenciais no primeiro passo.

Com essas informações, você pode escrever um TexturePreprocessorsimples:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

É importante garantir que você execute isso apenas para texturas que tenham o rótulo de arte (nossas próprias texturas). Você receberá uma referência ao importador para poder configurar tudo, começando pelo tamanho da textura.

O AssetPostprocessor tem uma propriedade de contexto a partir da qual você pode determinar a plataforma de destino. Dessa forma, você pode concluir alterações específicas da plataforma, como definir as texturas para uma resolução mais baixa para dispositivos móveis:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Em seguida, verifique o rótulo para ver se a textura é uma textura de IU e defina-a adequadamente:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Para o restante das texturas, defina os valores como padrão. Vale ressaltar que Albedo é a única textura que terá sRGB habilitado:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Graças ao script acima, quando você arrastar e soltar as novas texturas no Editor, elas terão automaticamente as configurações corretas.

Dica 3: Assuma o empacotamento de canais de textura

“Empacotamento de canais” refere-se à combinação de diversas texturas em uma usando diferentes canais. É comum e oferece muitas vantagens. Por exemplo, o valor do canal Vermelho é metálico e o valor do canal Verde é sua suavidade.

No entanto, combinar todas as texturas em uma só exige um trabalho extra da equipe de arte. Se a embalagem precisar mudar por algum motivo (por exemplo, uma mudança no shader), a equipe de arte terá que refazer todas as texturas usadas com aquele shader.

Como você pode ver, há espaço para melhorias aqui. A abordagem que gosto de usar para empacotamento de canais é criar um tipo de ativo especial onde você define as texturas "brutas" e gera uma textura empacotada em canais para usar em seus materiais.

Primeiro, crio um arquivo fictício com uma extensão específica e, em seguida, uso um importador com script que faz todo o trabalho pesado ao importar esse ativo. É assim que funciona:

  • Os importadores podem ter parâmetros, como as texturas que você precisa combinar.
  • No importador, você pode definir as texturas como uma dependência, o que permite que o ativo fictício seja reimportado sempre que uma das texturas de origem for alterada. Isso permite que você reconstrua as texturas geradas adequadamente.
  • O importador tem uma versão. Se precisar alterar a maneira como as texturas são compactadas, você pode modificar o importador e aumentar a versão. Isso forçará uma regeneração de todas as texturas compactadas no seu projeto e tudo será compactado da nova maneira, imediatamente.
  • Um bom efeito colateral de gerar coisas em um importador é que os ativos gerados ficam apenas na pasta Biblioteca, então eles não preenchem seu controle de versão.

Para implementar isso, crie um ScriptableObject que conterá as texturas criadas e servirá como resultado do importador. No exemplo, chamei essa classe de TexturePack.

Com isso criado, você pode começar declarando a classe do importador e adicionando o ScriptedImporterAttribute para definir a versão e a extensão associadas ao importador:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

No importador, declare os campos que você deseja usar. Eles aparecerão no Inspetor, assim como MonoBehaviours e ScriptableObjects:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

O inspetor do importador.

Com os parâmetros prontos, crie novas texturas a partir daquelas que você definiu como parâmetros. Observe, entretanto, que no Pré-processador (da seção anterior), definimos isReadable como True para fazer isso.

Neste protótipo, você notará duas texturas: a Albedo, que tem o Albedo no RGB e uma máscara para aplicar a cor do jogador no Alpha, e a textura Máscara, que inclui o metálico no canal Vermelho e a suavidade no canal Verde.

Embora isso talvez esteja fora do escopo deste artigo, vamos ver como combinar o Albedo e a máscara do jogador como exemplo. Primeiro, verifique se as texturas estão definidas e, se estiverem, obtenha seus dados de cor. Em seguida, defina as texturas como dependências usando AssetImportContext.DependsOnArtifact. Como mencionado acima, isso forçará o objeto a ser recalculado se alguma das texturas mudar.

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Você também precisa criar uma nova textura. Para fazer isso, obtenha o tamanho do TexturePreprocessor que você criou na seção anterior para que ele siga as restrições predefinidas:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Em seguida, preencha todos os dados para a nova textura. Isso poderia ser otimizado massivamente usando Jobs e Burst (mas isso exigiria um artigo inteiro só para isso). Aqui usaremos um loop simples:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Defina esses dados na textura:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Agora, você pode criar o método para gerar outra textura de uma maneira muito semelhante. Quando isso estiver pronto, crie o corpo principal do importador. Neste caso, criaremos apenas o ScriptableObject que contém os resultados, cria as texturas e define o resultado do importador por meio do AssetImportContext.

Ao escrever um importador, todos os ativos gerados devem ser registrados usando AssetImportContext.AddObjectToAsset para que apareçam na janela do projeto. Selecione um ativo principal usando AssetImportContext.SetMainObject. É assim que se parece:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

A única coisa que resta a fazer é criar os ativos fictícios. Como são personalizados, você não pode usar o atributo CreateAssetMenu. Em vez disso, você deve fazê-los manualmente.

Usando o atributo MenuItem , especifique o caminho completo para criar o menu de ativos, Assets/Create. Para criar o ativo, use ProjectWindowUtil.CreateAssetWithContent, que gera um arquivo com o conteúdo que você especificou e permite que o usuário insira um nome para ele. Parece algo assim:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Por fim, crie as texturas preenchidas com canais.

Dica 4: Use o shader personalizado para materiais

A maioria dos projetos usa shaders personalizados. Às vezes, eles são usados para adicionar efeitos extras, como um efeito de dissolução para esmaecer inimigos derrotados, e outras vezes, os shaders implementam um estilo de arte personalizado, como shaders de desenho animado. Seja qual for o caso de uso, o Unity criará novos materiais com o shader padrão, e você precisará alterá-lo para usar o shader personalizado.

Neste exemplo, o shader usado para unidades tem dois recursos adicionais: o efeito de dissolução e a cor do player (vermelho e azul no protótipo de vídeo). Ao implementar isso em seu projeto, você deve garantir que todos os edifícios e unidades usem o shader apropriado.

Para validar se um ativo atende a certos requisitos – neste caso, se ele usa o shader correto – há outra classe útil: o AssetModificationProcessor. Com AssetModificationProcessor.OnWillSaveAssets, em particular, você será notificado quando o Unity estiver prestes a gravar um ativo no disco. Isso lhe dará a oportunidade de verificar se o ativo está correto e corrigi-lo antes de salvá-lo.

Além disso, você pode “dizer” ao Unity para não salvar o ativo, o que é eficaz quando o problema detectado não pode ser corrigido automaticamente. Para fazer isso, crie o método OnWillSaveAssets :

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Para processar os ativos, verifique se eles são materiais e se possuem os rótulos corretos. Se eles corresponderem ao código abaixo, então você tem o shader correto:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

O que é conveniente aqui é que esse código também é chamado quando o ativo é criado, o que significa que o novo material terá o shader correto.

Como um novo recurso no Unity 2022, também temos Variantes de Material. Variantes de materiais são incrivelmente úteis ao criar materiais para unidades. Na verdade, você pode criar um material base e derivar os materiais para cada unidade a partir dele – substituindo os campos relevantes (como as texturas) e herdando o restante das propriedades. Isso permite padrões sólidos para nossos materiais, que podem ser atualizados conforme necessário.

Dica 5: Gerencie suas animações

Importar animações é semelhante a importar texturas. Há várias configurações que precisam ser estabelecidas, e algumas delas podem ser automatizadas.

O Unity importa os materiais de todos os arquivos FBX (.fbx) por padrão. Para animações, os materiais que você deseja usar estarão no projeto ou no FBX da malha. Os materiais extras da animação FBX aparecem toda vez que você procura materiais no projeto, adicionando bastante ruído, então vale a pena desabilitá-los.

Para configurar o equipamento – ou seja, escolher entre Humanoide e Genéricoe, nos casos em que estamos usando um avatar cuidadosamente configurado, atribuí-lo – aplique a mesma abordagem que foi aplicada às texturas. Mas para animações, a mensagem que você usará é AssetPostprocessor.OnPreprocessModel. Isso será chamado para todos os arquivos FBX, então você precisa distinguir os arquivos FBX de animação dos arquivos FBX de modelo.

Graças aos rótulos que você configurou anteriormente, isso não deve ser muito complicado. O método começa muito parecido com o das texturas:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Em seguida, você vai querer usar o equipamento do FBX de malha, então você precisa encontrar esse ativo. Para localizar o ativo, use os rótulos mais uma vez. No caso deste protótipo, as animações têm rótulos que terminam com “animação”, enquanto as malhas têm rótulos que terminam com “modelo”. Você pode concluir uma substituição simples para obter a etiqueta do seu modelo. Depois de ter o rótulo, encontre seu ativo usando AssetDatabase.FindAssets com “l:label-name”.

Ao acessar outros ativos, há algo mais a considerar: É possível que, no meio do processo de importação, o avatar ainda não tenha sido importado quando este método é chamado. Se isso ocorrer, o LoadAssetAtPath retornará nulo e você não poderá definir o avatar. Para contornar esse problema, defina uma dependência para o caminho do avatar. A animação será importada novamente assim que o avatar for importado, e você poderá defini-la lá.

Colocar tudo isso em código ficaria mais ou menos assim:

Tipo de bloco desconhecido "codeBlock", especifique um serializador para ele na propriedade `serializers.types`

Agora você pode arrastar as animações para a pasta correta e, se sua malha estiver pronta, cada uma será configurada automaticamente. Mas se não houver um avatar disponível quando você importar as animações, o projeto não poderá identificá-lo depois de criado. Em vez disso, você precisará reimportar a animação manualmente depois de criá-la. Isso pode ser feito clicando com o botão direito do mouse na pasta com as animações e selecionando Reimportar.

Você pode ver tudo isso no vídeo de exemplo abaixo.

Dica 6: Configurar a malha usando importadores FBX

Usando exatamente as mesmas ideias das seções anteriores, você vai querer configurar os modelos que vai usar. Nesse caso, utilize AssetPostrocessor.OnPreprocessModel para definir as configurações do importador para este modelo.

Para o protótipo, configurei o importador para não gerar materiais (usarei os que criei no projeto) e verifiquei se o modelo é uma unidade ou um edifício (verificando o rótulo, como sempre). As unidades são configuradas para gerar um avatar, mas a criação de avatar para os edifícios é desabilitada, pois os edifícios não são animados.

Para seu projeto, talvez você queira definir os materiais e animadores (e qualquer outra coisa que queira adicionar) ao importar o modelo. Dessa forma, o Prefab gerado pelo importador está pronto para uso imediato.

Para fazer isso, use o método AssetPostprocessor.OnPostprocessModel . Este método é chamado após a conclusão da importação de um modelo. Ele recebe o Prefab que foi gerado como parâmetro, o que nos permite modificar o Prefab como quisermos.

Para o protótipo, encontrei o material e o Controlador de Animação combinando o rótulo, assim como localizei o avatar para as animações. Com o Renderizador e o Animador no Prefab, configurei o material e o controle como no jogo normal.

Você pode então soltar o modelo no seu projeto e ele estará pronto para ser inserido em qualquer cena. Só que não definimos nenhum componente relacionado à jogabilidade, o que abordarei na segunda parte deste blog.

Até a próxima…

Com essas dicas avançadas de script, você estará quase pronto para o jogo. Fique ligado na próxima parte deste artigo de duas partes do Tech from the Trenches , que abordará dicas para balancear dados de jogos e muito mais.

Se você quiser discutir o artigo ou compartilhar suas ideias depois de lê-lo, acesse nosso fórum de script. Você também pode se conectar comigo no Twitter em @CaballolD.