Como otimizamos nosso Hospital Temático para diferentes plataformas

imagem

O Project Hospital é um jogo sobre o gerenciamento de um edifício hospitalar com todos os aspectos padrão do gênero: cenas dinâmicas criadas pelo jogador, muitos personagens e objetos ativos, implementados pelo sistema da interface do usuário. Para fazer o jogo funcionar em equipamentos diferentes, tivemos que fazer muitos esforços, e esse foi um ótimo exemplo da infame “morte por mil cortes” - muitos pequenos passos que resolvem vários problemas muito específicos e muito tempo gasto em criação de perfis.

Nível de desempenho: o que queríamos alcançar


Em um estágio inicial de desenvolvimento, decidimos os principais parâmetros: o tamanho máximo das cenas, o nível de desempenho e os requisitos do sistema.

Nós nos propusemos a tarefa de fornecer suporte para pelo menos cem caracteres ativos e totalmente animados em uma tela, trezentos caracteres ativos no total, mapas de blocos medindo aproximadamente 100x100 e até quatro andares no edifício.

Estávamos firmemente convencidos de que o jogo deveria funcionar em 1080p com uma taxa de quadros decente mesmo em placas gráficas integradas, e por si só esse objetivo não era tão difícil de alcançar: o principal fator limitante é a CPU, especialmente com um aumento no volume do hospital. As placas gráficas integradas modernas começam a ter problemas apenas em resoluções de aproximadamente 2560 x 1440.

Para simplificar o suporte a mods, a maioria dos dados foi aberta, ou seja, tivemos que sacrificar o desempenho alcançado ao compactar os arquivos, mas isso não teve um impacto particularmente forte, exceto por um tempo de download um pouco mais longo.

Gráficos


O Project Hospital é um jogo 2D isométrico “clássico”, para que você possa entender que tudo é retrocedido para a frente - no Unity, isso é feito definindo os valores apropriados ao longo do eixo Z (ou distância da câmera) para objetos gráficos individuais. Se possível, objetos que não interagem entre si são organizados em camadas, por exemplo, os pisos são independentes de objetos e caracteres.


Toda geometria em uma cena renderizada isometricamente é criada dinamicamente em C #, portanto, um dos dois aspectos mais importantes para o desempenho gráfico é a frequência da reconstrução da geometria. O segundo aspecto é o número de chamadas de empate.

Desenhar chamadas


O número de objetos individuais desenhados em um quadro, independentemente de sua simplicidade, é a principal limitação, especialmente em equipamentos ruins (além disso, o próprio mecanismo do Unity adiciona um consumo excessivo de recursos). A solução óbvia é agrupar (lote) o maior número possível de objetos gráficos em uma chamada de desenho. Assim, você pode obter alguns resultados bastante interessantes, por exemplo, agrupar objetos que estão à mesma distância da câmera para que o restante dos gráficos seja renderizado corretamente atrás ou na frente deles.


Aqui estão alguns números: em um bloco de 96 x 96, você pode, teoricamente, colocar 9216 objetos, o que exigiria 9216 chamadas de desenho. Após o lote, esse número cai para 192.

No entanto, na vida real, tudo é um pouco mais complicado, porque você só pode agrupar objetos com a mesma textura, o que torna os resultados um pouco menos ideais, mas o sistema ainda funciona muito bem.


A maioria dos lotes é feita manualmente para ter controle sobre os resultados. Além disso, como último recurso, também usamos lotes dinâmicos do Unity, mas essa é uma faca de dois gumes - na verdade, ajuda a reduzir o número de chamadas de empate, mas gera recursos desnecessários em cada quadro e, em alguns casos, pode ser imprevisível. Por exemplo, dois sprites sobrepostos à mesma distância da câmera em quadros diferentes podem ser renderizados em uma ordem diferente, o que causa oscilações que não aparecem quando o lote é manualmente.

Vários andares


Os jogadores podem construir edifícios com vários andares, e isso aumenta a complexidade, mas, surpreendentemente, ajuda no desempenho. Somente personagens no andar ativo e na rua precisam ser renderizados e animados, e tudo nos outros andares do hospital pode ser oculto.

Shaders


O Project Hospital usa shaders auto-escritos relativamente simples, com pequenos truques, como a troca de cores. Suponha que um sombreador de caracteres possa substituir até cinco cores (dependendo das condições no código do sombreador) e, portanto, bastante caro, mas isso não parece causar problemas, porque os caracteres raramente ocupam muito espaço na tela. O shader justificou o esforço, porque a capacidade de usar um número infinito de cores de roupas pode aumentar bastante a variabilidade dos personagens e do ambiente.

Além disso, aprendemos com rapidez suficiente para evitar a especificação de parâmetros do shader e, em vez disso, usamos cores de vértice sempre que possível.

Qualidade da textura


Um fato interessante - no Project Hospital, não usamos compressão de textura: os gráficos são feitos em estilo vetorial e, em algumas texturas, a compressão parece muito ruim.

Para economizar memória da CPU em sistemas com menos de 1 GB, reduzimos automaticamente o tamanho das texturas no jogo para meia resolução (exceto as texturas da interface do usuário) - isso pode ser entendido ao se ver o parâmetro "qualidade da textura: baixo" nas opções. As texturas da interface do usuário mantêm sua resolução original.

Otimize o desempenho da CPU - Multithreading


Embora a lógica de script do Unity seja essencialmente de thread único, sempre temos a capacidade de executar vários threads diretamente em C #. Talvez essa abordagem não seja adequada para a lógica do jogo, mas geralmente existem tarefas com tempo crítico que podem ser executadas em threads separados, organizando um sistema de tarefas. No nosso caso, os threads foram usados ​​para duas funções:

1. A tarefa de encontrar um caminho, especialmente em mapas grandes com um arranjo confuso, pode levar até centenas de milissegundos, portanto esse foi o principal candidato à transferência do fluxo principal. Tarefas paralelas levam em consideração o número de threads de hardware de uma máquina.

2. Os cartões de iluminação também podem ser atualizados em um fluxo separado, mas apenas um andar de cada vez - esse não é um sistema crítico, e as lâmpadas automáticas nas salas se apagam a uma velocidade que uma atualização rara é suficiente.

Animações


Quase no início do desenvolvimento, decidimos usar um sistema de animação esquelética bidimensional. Tendo estudado vários programas modernos de animação, finalmente decidimos modificar um sistema simples que eu criei há vários anos (essencialmente como um projeto de hobby), adaptando-o às necessidades do Project Hospital - ele se assemelha a uma coluna simplificada com suporte direto para criar variações de caracteres. Como o Spine, ele usa o tempo de execução C #, que é obviamente mais caro que o código nativo, portanto, durante o processo de desenvolvimento, realizamos alguns ciclos de otimização. Felizmente, nossas plataformas são bastante simples, apenas cerca de 20 ossos por personagem.

Um fato curioso: a melhoria mais útil na otimização do acesso à transformação de ossos individuais acabou sendo a transição da pesquisa de mapa para a simples indexação de matrizes.


Além do fato de os personagens não serem animados fora da câmera, há outro truque: os personagens escondidos atrás das janelas da interface principal também não precisam ser animados. Infelizmente, na versão final do jogo, mudamos para uma interface translúcida, por isso não pudemos usá-la.

Armazenamento em cache


Se possível, tentamos realizar os cálculos mais caros apenas com alterações que afetam seus valores. O melhor exemplo disso são salas e elevadores: quando um jogador coloca um elevador ou constrói paredes, executamos um algoritmo de preenchimento que marca os ladrilhos a partir dos quais elevadores e salas estão disponíveis. Isso acelera a busca subseqüente por caminhos e pode ser usado para mostrar ao jogador quais salas ainda não estão disponíveis.

Atualizações dispersas e adiadas


Em alguns casos, é lógico executar determinadas atualizações apenas parcialmente. Aqui estão alguns exemplos:

Algumas atualizações podem ser executadas em cada quadro apenas para parte dos personagens, por exemplo, os scripts de comportamento de metade dos pacientes são atualizados apenas em quadros ímpares e para a segunda metade - em quadros pares (embora animações e movimentos sejam realizados sem problemas).

Em certas condições, especialmente quando os caracteres estão no modo de espera, mas chamam partes caras do código (por exemplo, funcionários verificando o que precisa ser preenchido e procurando equipamentos desocupados), as operações são executadas apenas em determinados intervalos, por exemplo, uma vez por segundo.

Um dos desafios mais caros e ao mesmo tempo comuns é verificar quais testes estão disponíveis para cada paciente. Ao mesmo tempo, muitos fatores precisam ser avaliados - por exemplo, qual dos funcionários do departamento está atualmente ocupado e qual equipamento está reservado. Além disso, essas informações não são comuns a todos os pacientes porque são afetadas, por exemplo, pelo médico designado a eles e por sua capacidade de falar. É necessário verificar dezenas de tipos de análises disponíveis; portanto, em um quadro, a atualização é realizada apenas para alguns e continua no seguinte.


Conclusão


A otimização de um gerenciador de jogos com muitas partes interagentes provou ser um processo demorado. Eu regularmente tinha que trabalhar com o criador de perfil do Unity e corrigir os problemas mais óbvios, isso se tornou parte integrante do processo de desenvolvimento.

Obviamente, sempre há espaço para melhorias, mas estamos bastante satisfeitos com os resultados. O jogo lida com nossas tarefas, e os jogadores constantemente criam mods para ele, excedendo significativamente o limite original do número de personagens.

Também vale mencionar que, mesmo em comparação com alguns jogos AAA em que trabalhei, no Project Hospital encontrei a lógica de jogo mais complexa em minha prática, portanto muitos dos problemas eram específicos para esse projeto. No entanto, ainda recomendo deixar tempo suficiente em qualquer projeto para otimização de acordo com a complexidade do jogo.

Source: https://habr.com/ru/post/pt459256/


All Articles