O que você está procurando?
Hero background image
Programação avançada e arquitetura de código
Explore sua arquitetura de código para otimizar ainda mais a renderização de seus gráficos. Este é o quarto de uma série de artigos que apresenta dicas de otimização para seus projetos Unity. Use-os como um guia para executar em taxas de quadros mais altas com menos recursos. Depois de experimentar essas práticas recomendadas, verifique as outras páginas da série: Configurando seu projeto Unity para um desempenho mais forte Otimização de desempenho para gráficos de alta qualidade Gerenciando o uso de GPU para jogos de PC e console Desempenho de física aprimorado para uma jogabilidade suave
Entenda o Unity PlayerLoop

O Unity PlayerLoop contém funções para interagir com o núcleo do mecanismo de jogo. Essa estrutura inclui vários sistemas que lidam com inicialização e atualizações por quadro. Todos os seus scripts dependerão deste PlayerLoop para criar a jogabilidade. Ao criar o perfil, você verá o código de usuário do seu projeto em PlayerLoop – com componentes Editor em EditorLoop.

É importante entender a ordem de execução do FrameLoopdo Unity. Cada script do Unity executa várias funções de evento em uma ordem predeterminada. Aprenda a diferença entre Awake, Start, Updatee outras funções que criam o ciclo de vida de um script para fortalecer o desempenho.

Alguns exemplos incluem o uso de FixedUpdate em vez de Update ao lidar com um Rigidbody ou o uso de Awake em vez de Start para inicializar variáveis ​​ou estado do jogo antes do início do jogo. Use-os para minimizar o código executado em cada quadro. Awake é chamado apenas uma vez durante o tempo de vida da instância de script e sempre antes das funções Start. Isso significa que você deve usar Start para lidar com objetos que você sabe que podem se comunicar com outros objetos ou consultá-los à medida que foram inicializados.

Consulte o fluxograma do ciclo de vida do script para obter a ordem específica de execução das funções de evento.

Diagrama do gerente de atualizações personalizado
Crie um gerenciador de atualizações personalizado

Se o seu projeto tiver requisitos de desempenho exigentes (por exemplo, um jogo de mundo aberto), considere criar um Update Manager personalizado usando Update, LateUpdateou FixedUpdate.

Um padrão de uso comum para Update ou LateUpdate é executar a lógica somente quando alguma condição for atendida. Isso pode levar a vários retornos de chamada por quadro que efetivamente não executam nenhum código, exceto para verificar essa condição.

Sempre que o Unity chama um método de mensagem como Update ou LateUpdate, ele faz uma chamada de interoperabilidade – ou seja, uma chamada do lado C/C++ para o lado C# gerenciado. Para um pequeno número de objetos, isso não é um problema. Quando você tem milhares de objetos, essa sobrecarga começa a se tornar significativa.

Assine objetos ativos neste Update Manager quando eles precisarem de retornos de chamada e cancele a assinatura quando não precisarem. Esse padrão pode reduzir muitas das chamadas de interoperabilidade para seus objetos Monobehaviour .

Consulte as técnicas de otimização específicas do mecanismo de jogo para obter exemplos de implementação.

Minimize o código que executa cada quadro

Considere se o código deve executar cada quadro. Você pode remover lógica desnecessária de Update, LateUpdate e FixedUpdate. Essas funções de evento do Unity são locais convenientes para colocar o código que deve atualizar cada quadro, mas você pode extrair qualquer lógica que não precise ser atualizada com essa frequência.

Execute a lógica apenas quando as coisas mudarem. Lembre-se de aproveitar técnicas como o padrão observador na forma de eventos para acionar uma assinatura de função específica.

Se precisar usar o Update, você pode executar o código a cada n frames. Essa é uma maneira de aplicar o Time Slicing, uma técnica comum de distribuição de uma carga de trabalho pesada em vários quadros.

Neste exemplo, executamos ExampleExpensiveFunction uma vez a cada três quadros.

O truque é intercalar isso com outro trabalho executado nos outros quadros. Neste exemplo, você poderia “agendar” outras funções caras quando Time.frameCount % interval == 1 ou Time.frameCount % interval == 2.

Como alternativa, use uma classe personalizada do Update Manager para atualizar os objetos inscritos a cada n quadros.

Armazene em cache os resultados de funções caras

Nas versões do Unity anteriores a 2020.2, GameObject.Find, GameObject.GetComponente Camera.main podem ser caros, portanto, é melhor evitar chamá-los nos métodos Update.

Além disso, tente evitar colocar métodos caros em OnEnable e OnDisable se eles forem chamados com frequência. Chamar esses métodos com frequência pode contribuir para picos de CPU.

Sempre que possível, execute funções caras, como MonoBehaviour.Awake e MonoBehaviour.Startdurante a fase de inicialização. Armazene em cache as referências necessárias e reutilize-as mais tarde. Confira nossa seção anterior no Unity PlayerLoop para a execução da ordem do script com mais detalhes.

Aqui está um exemplo que demonstra o uso ineficiente de uma chamada repetida de GetComponent :

atualização nula()
{
Renderer myRenderer = GetComponent<Renderer>();
ExampleFunction(myRenderer);
}

Em vez disso, invoque GetComponent apenas uma vez, pois o resultado da função é armazenado em cache. O resultado armazenado em cache pode ser reutilizado em Update sem quaisquer chamadas adicionais para GetComponent.

Leia mais sobre a ordem de execução das funções de evento.

Evite eventos vazios do Unity e instruções de log de depuração

As instruções de log (especialmente em Update, LateUpdate ou FixedUpdate) podem prejudicar o desempenho, portanto, desative suas instruções de log antes de fazer uma compilação. Para fazer isso rapidamente, considere criar um Atributo Condicional junto com uma diretiva de pré-processamento.

Por exemplo, você pode querer criar uma classe personalizada conforme mostrado abaixo.

Gere sua mensagem de log com sua classe personalizada. Se você desabilitar o pré-processador ENABLE_LOG em Player Settings > Scripting Define Symbols, todas as suas declarações de log desaparecerão de uma só vez.

O tratamento de strings e texto é uma fonte comum de problemas de desempenho em projetos Unity. É por isso que a remoção de instruções de log e sua dispendiosa formatação de strings pode potencialmente ser um grande ganho de desempenho.

Da mesma forma, MonoBehaviours vazios requerem recursos, portanto você deve remover os métodos Update ou LateUpdate em branco. Use diretivas de pré-processador se estiver empregando estes métodos para teste:

#if UNITY_EDITOR
atualização nula()
{
}
#endif

Aqui, você pode usar o Update in-Editor para testar sem sobrecarga desnecessária em sua construção.

Esta postagem do blog sobre 10.000 chamadas Update explica como o Unity executa o Monobehaviour.Update.

Desabilitar o registro do Stack Trace

Use as opções de rastreamento de pilha nas configurações do player para controlar que tipo de mensagens de log aparecem. Se seu aplicativo estiver registrando erros ou mensagens de aviso em sua versão (por exemplo, para gerar relatórios de falhas em tempo real), desative Stack Traces para melhorar o desempenho.

Saiba mais sobre o registro em log do Stack Trace.

Use valores hash em vez de parâmetros de string

O Unity não usa nomes de string para endereçar propriedades Animator, Materialou Shader internamente. Para maior velocidade, todos os nomes de propriedades são criptografados em Property IDse esses IDs são usados ​​para endereçar as propriedades.

Ao usar um método Set ou Get em um Animator, Material ou Shader, aproveite o método com valor inteiro em vez dos métodos com valor de string. Os métodos com valor de string executam hash de string e, em seguida, encaminham o ID com hash para os métodos com valor inteiro.

Use Animator.StringToHash para nomes de propriedades do Animator e Shader.PropertyToID para nomes de propriedades de Material e Shader.

Relacionada está a escolha da estrutura de dados, que afeta o desempenho à medida que você itera milhares de vezes por quadro. Siga o guia MSDN para estruturas de dados em C# como um guia geral para escolher a estrutura correta.

Interface de script do pool de objetos
Agrupe seus objetos

Instanciar e Destruir podem gerar picos de coleta de lixo (GC). Geralmente, esse é um processo lento, portanto, em vez de instanciar e destruir GameObjects regularmente (por exemplo, disparar balas de uma arma), use conjuntos de objetos pré-alocados que podem ser reutilizados e reciclados.

Crie as instâncias reutilizáveis ​​em um ponto do jogo, como durante uma tela de menu ou tela de carregamento, quando um pico de CPU é menos perceptível. Acompanhe esse “conjunto” de objetos com uma coleção. Durante o jogo, basta ativar a próxima instância disponível quando necessário e desativar objetos em vez de destruí-los, antes de devolvê-los ao pool. Isso reduz o número de alocações gerenciadas no seu projeto e pode evitar problemas de GC.

Da mesma forma, evite adicionar componentes em tempo de execução; Invocar AddComponent tem algum custo. O Unity deve verificar se há duplicatas ou outros componentes necessários sempre que adicionar componentes em tempo de execução. Instanciar um Prefab com os componentes desejados já configurados tem melhor desempenho, então use isso em combinação com seu Pool de Objetos.

Relacionado, ao mover Transforms, use Transform.SetPositionAndRotation para atualizar a posição e a rotação de uma só vez. Isso evita a sobrecarga de modificar um Transform duas vezes.

Se você precisar instanciar um GameObject em tempo de execução, crie-o e reposicione-o para otimização, veja abaixo.

Para obter mais informações sobre Object.Instantiate, consulte API de script.

Aprenda como criar um sistema simples de pool de objetos no Unity aqui.

Pool de ScriptableObjects
Aproveite o poder dos ScriptableObjects

Armazene valores ou configurações imutáveis ​​em um ScriptableObject em vez de um MonoBehaviour. O ScriptableObject é um ativo que reside dentro do projeto. Ele só precisa ser configurado uma vez e não pode ser anexado diretamente a um GameObject.

Crie campos no ScriptableObject para armazenar seus valores ou configurações e, em seguida, faça referência ao ScriptableObject em seu MonoBehaviours. O uso de campos do ScriptableObject pode evitar a duplicação desnecessária de dados sempre que você instanciar um objeto com esse MonoBehaviour.

Assista a este tutorial de Introdução ao ScriptableObjects e encontre a documentação relevante aqui.

arte chave da unidade 21 11
Obtenha o e-book gratuito

Um de nossos guias mais completos já coleta mais de 80 dicas práticas sobre como otimizar seus jogos para PC e console. Criadas por nossos engenheiros especialistas em Success and Accelerate Solutions , essas dicas detalhadas ajudarão você a aproveitar ao máximo o Unity e aumentar o desempenho do seu jogo.

Você gostou deste conteúdo?