Olá Habr! Recentemente, lançamos
nosso jogo , que preparamos por um longo tempo e no processo em que um número considerável de tópicos interessantes se acumulou e que valem a pena ser compartilhados com a comunidade. O tópico será de interesse não apenas do iOS e de outros desenvolvedores de dispositivos móveis, mas também de todos os interessados em saber como todo tipo de coisa gráfica funciona, bem como de todos os fãs de estratégias 2D, o que eu mesmo já fiz na terceira década.
Hoje falaremos sobre as nuances de um tópico tão importante como os índices z em uma superfície isométrica (sim, nem tudo é tão simples quanto parece para alguns wiseacres). No mundo 3d, estranhamente, temos três coordenadas - x, y, z - que determinam completamente a posição do objeto no espaço. A tarefa de determinar a proximidade dos objetos com a câmera também está lá, mas fica inteiramente sobre os ombros do OpenGL. O desenvolvedor opera apenas com parâmetros de alto nível, como a profundidade do buffer z, que afetam o desempenho, mas você pode confiar no OpenGL como uma caixa preta - ele possui informações suficientes.
Uma situação completamente diferente é observada em nosso mundo “pseudo-3D” - cada objeto possui apenas (x, y) - coordenadas e tamanho do sprite. A primeira tarefa que um programador enfrenta ao escrever um mecanismo é a tarefa de determinar quais objetos devem se sobrepor na frente de nossa "câmera" virtual.
Sinopse
Coordenadas do SpriteKit (onde (0; 0) é o centro do "mundo" e Y sobe). Nesse caso, não estamos interessados, porque eles não significam nada em nosso “mundo” isométrico com você, então vamos fazer uma reserva - temos um campo em forma de diamante como o Age of Empires.

Um bloco com coordenadas (0; 0) está localizado no canto esquerdo do losango, a abcissa X aumenta "para baixo" e "para a direita", ou seja, cresce mais perto do observador, a ordenada Y aumenta "para cima" e "para a direita", ou seja, diminui à medida que você se aproxima do observador.
Além disso, os trilhos devem estar "embaixo" do trem, a fumaça da chaminé deve estar "acima" do trem. Mas não vamos nos preocupar agora com as "camadas do ser" - obviamente, nada nos impede de fazer quantas fatias isométricas quiser, trabalhando de acordo com as mesmas regras. Assumimos que em um bloco há sempre um objeto - para maior clareza, mais não é necessário.

Considere os dois trens acima. Obviamente, do ponto de vista do observador, os carros devem estar localizados "abaixo" do trem, ou seja, o índice z deve ser menor. Ao mesmo tempo, o trem “superior” deve “se sobrepor” ao vizinho, estar “mais longe”. Podemos, tendo apenas as coordenadas (x; y), construir um mapa de índices z para cada bloco?
Obviamente, sim, usando a seguinte fórmula (pseudocódigo à la swift):
zIndex = pos.x * field.size.width - pos.y
Assim, garantimos que, à medida que a ordenada cresce, os objetos se afastam (-pos.y), bem como com o aumento da abcissa, os objetos se aproximam (pos.x) e, principalmente, qualquer objeto que tenha uma abcissa, digamos 44, será deliberadamente "mais próximo" ”Do que qualquer objeto com uma abcissa 43. Para adicionar" camadas "aqui (lembre-se, os trilhos sob o trem fumam acima da chaminé), basta adicionar alguma" altura "constante da camada:
zIndex = layerZIndex + pos.x * field.size.width - pos.y
É isso aí, você pode terminar o artigo e elogiar-se pelos conceitos básicos de estereometria aprendidos na 10ª série e iniciar a lógica do jogo. Não? Se ao menos! Eu escreveria sobre as coisas óbvias! (Bem, como óbvio, alguns dias são esmagados e isso)
Estamos apenas começando a parte divertida, seguindo em frente.
Luta pelo desempenho
Todos, pelo menos uma vez, executaram um projeto de teste para o SpriteKit (ou um coco, ou qualquer outro mecanismo), viram números mágicos - fps e nós.

Obviamente, fps é o número de quadros por segundo, nós é o número de nós, principalmente sprites. Mas, na prática, a maioria de todos os fps é definida não pelo número de nós, mas por outro parâmetro, que por padrão não é exibido, mas que também pode ser exibido com uma linha - o número de empates redesenhados.

Na mesma cena, como você vê agora, o número de nós é de cerca de 6000 e o número de renderizações é de cerca de 120. Esse é o zoom mínimo (a câmera está o mais próximo possível da superfície), 1: 1.
Agora vamos mover a câmera para a distância máxima (no nosso jogo são 2,5: 1)

Alteramos a escala em apenas 2,5 vezes (no exemplo, longe de todos os objetos são desenhados), e o número de empates aumentou de 5 a 6 vezes com a contagem de nós inalterada!
Obviamente, o número de renderizações afeta fps incomensuravelmente mais do que o número abstrato de nós. O SpriteKit simplesmente não desenha nós que não caem na janela de exibição (na câmera). A única exceção que encontrei até agora são os emissores de partículas, sempre desenhados, independentemente de serem visíveis ou não.
Agora vamos falar sobre o significado desse desenho "desenho". A placa de vídeo possui todos os nós "camadas", guiados por seus índices z. E ele repassa o quadro todo repetidas vezes, do mais baixo ao mais alto. O número de tais ciclos de renderização é empatado.
Agora você entende que, se você desenhar cada objeto minúsculo (e tivermos um mapa grande, aproximadamente 6000 x 3000) com seu próprio z-index, isso arruinará o desempenho de qualquer telefone.
Os problemas são mais visíveis nos antigos 5 e 5s, mas a presença do iPhone 10 não garante nada - com a abordagem errada, você pode abandonar qualquer hardware poderoso. No nosso jogo, uma das pedras angulares foi a extrema clareza. No zoom mais próximo, os sprites um a um correspondem a retin pixels. Devo dizer que, na maioria dos jogos para dispositivos móveis, a resolução é de ordens de magnitude mais baixa, por isso os requisitos não são tão grandes, mas fizemos qualitativamente, quanto a nós mesmos ...
Então você tem que ir para os truques.
- Todos os objetos com os quais o jogador não interage e que estão no mesmo nível na coordenada X podem geralmente ser mesclados em um sprite. Para uma placa de vídeo, é muito mais fácil desenhar um sprite grande do que 10 pequenos. Portanto, as pistas florestais entre as estradas são sprites integrais que consistem em várias árvores. E árvores que não se sobrepõem a caminhos e outras árvores geralmente são costuradas no mapa. Em alpha, a propósito, houve alguns bugs quando o carvalho secular cresceu sob os trilhos de um trem ou sob o semáforo de um trem, teste com cuidado seu jogo para não divertir os usuários.
- Os objetos que possuem um índice z são desenhados na ordem em que aparecem na placa de vídeo. I.e. adicionando objetos “distantes” antes dos objetos “fechados”, eles cairão corretamente, mas não aumentarão o número de renderizações da placa gráfica.
Tudo isso permite reduzir o número de empates às vezes, fixando fps mesmo no iPhone antigo. Eu tive que limitar severamente alguns efeitos a eles, mas a Apple não lançou atualizações para eles há um ano - um pecado vai reclamar!
Elevação
Bem, tudo, o motor está pronto, você já pode começar algo interessante? Alguém pode, mas é muito cedo para nós. Afinal, o trem deve sair lindamente do túnel, e aqui nem tudo é tão simples quanto parece.

O trem deve estar localizado "mais alto" do que a parede "distante" do túnel e "mais baixo" que o teto do túnel e as montanhas que o seguem. Afinal, é lindo quando o mapa é tão multinível, com mudanças de altitude - novamente, não estamos fazendo bobagens sem alma, mas sim o que gostamos!
Mas voltando aos detalhes - para isso, o mapa foi “cortado” da seguinte forma.

A parede interna do túnel e tudo o mais à esquerda é mais baixa e

o topo do túnel, juntamente com as montanhas para as quais ele flui. Aqui, nenhuma geração processual de índices z ajudará, apenas um código rígido bielorrusso grave.
O habrayuzer atento notou na captura de tela do jogo que perto dos túneis as árvores foram "cortadas" cuidadosamente, expondo a areia da praia. Essa falha aparentemente vem da impossibilidade fundamental de realizar essas plantações de árvores em 2D. O trem, saindo do túnel, deve estar deliberadamente "acima" das árvores sobrepostas, cobrindo-as consigo mesmo. Mas essas mesmas árvores devem se sobrepor ao teto do túnel, sob o qual o trem deve entrar! E o teto deve ser mais alto que o trem e, em círculo, temos uma contradição lógica ...
Por uma razão semelhante, devido à imperfeição do mecanismo gráfico, em jogos antigos como Duke Nukem e Doom2, não existem grandes diferenças nas alturas e pisos dos edifícios.
É por isso que as árvores não crescem perto dos túneis.
Espero que tenha sido interessante, o
brinquedo ao vivo aqui (livre para jogar) , o próximo artigo da série será sobre água 2D realista bonita, não perca!
PS A propósito, um vídeo para atrair atenção pode ser
visto no youtube em qualidade normal.
PPS Até agora, o jogo está disponível apenas na CEI, Canadá e Irlanda. Se alguém quiser procurar em outros países, envie um e-mail pessoal com appleId - vou adicioná-lo ao TestFlight