Da ideia à implementação: a mecânica de margarina do Pãozito

João Borks
luizalabs
Published in
9 min readApr 27, 2023
Imagem promocional do Pãozito com a Doriana. Mostra a visão de dentro de uma geladeira, evidenciando um pote de Doriana sendo observado pelo Pãozito ao fundo e uma mulher abrindo a geladeira.

A mecânica de jogo é um dos elementos mais importantes para criar uma experiência divertida e desafiadora para os jogadores. E com o avanço da tecnologia, os desenvolvedores têm mais ferramentas à sua disposição para criar mecânicas inovadoras e interessantes.

Neste artigo, vamos falar sobre uma nova mecânica de jogo desenvolvida em parceria com a marca Doriana, que faz parte do novo capítulo do jogo. Essa mecânica consiste em uma curva (de margarina) que pode ser usada tanto como um auxílio para o deslocamento do personagem quanto como um obstáculo a ser superado.

Versão finalizada da mecânica.

Ao colidir com a curva, o personagem é acelerado para seguir o trajeto dela até o fim. Além disso, há buracos na curva que possibilitam que o personagem atravesse para chegar ao outro lado. Essa mecânica oferece uma nova camada de estratégia e desafio ao jogo, tornando a jogabilidade ainda mais envolvente.

Ao longo deste artigo, vamos detalhar como essa mecânica foi desenvolvida e implementada usando a Unity. Se você é um desenvolvedor de jogos, entusiasta ou simplesmente está interessado em conhecer os bastidores do desenvolvimento de jogos, continue lendo!

Design da Mecânica

Antes de começar o desenvolvimento da mecânica, o Game Designer da equipe, Anderson Verissimo, desenhou a ideia inicial com a intenção de criar uma experiência única em que o jogador precisasse formular estratégias diferentes para interagir com a mecânica. Seria necessário pensar em como utilizar o fluxo de margarina da melhor forma possível para obter todos os coletáveis, enquanto se atentar também para os buracos que surgem ocasionalmente. Para isso, ele imaginou uma curva que pudesse ser facilmente manipulada para criar diferentes fases, com múltiplos fluxos de margarina e controle dos parâmetros de força da aceleração, tempo de surgimento dos buracos e velocidade de movimento dos buracos.

Imagem conceito da mecânica com o personagem deslizando
Imagem conceito da mecânica: personagem deslizando pela curva.
Imagem conceito da mecânica com buracos no fluxo de margarina
Imagem conceito da mecânica: buraco na curva.

Com essas condições em mente, a equipe começou a planejar a implementação da mecânica. Foi decidido que a curva seria uma Bézier, uma curva matemática que permite criar formas suaves e flexíveis. Além disso, seria possível controlar a força da aceleração, o tempo de surgimento dos buracos e a velocidade de movimento dos buracos por meio de scripts na Unity.

Além disso, nesse estágio de desenvolvimento, a parte visual da mecânica ainda seria estudada e definida pela artista do time, Fernanda Rigler. A equipe ainda não sabia como iria fazer a movimentação visual do fluxo de manteiga. Seria via animação de texturas, shaders ou vértices? Essa seria uma questão a ser resolvida posteriormente, mas a equipe estava confiante de que encontraria uma solução que atendesse às expectativas visuais do jogo.

Com o planejamento definido, a equipe partiu para a implementação da mecânica, que será detalhada nas próximas seções.

Prototipagem

A fase de prototipagem é essencial em qualquer desenvolvimento de jogo. Ela tem como principais objetivos concluir se a mecânica é tecnicamente viável, se é divertida e, também, falhar rápido. É o momento de testar hipóteses e validar as ideias iniciais, a fim de evitar problemas no desenvolvimento futuro e garantir a qualidade do jogo final.

Durante essa fase, buscamos implementar a mecânica utilizando uma implementação de Spline obtida em um tutorial do Catlike Coding. Embora a Unity possua um pacote para isso, ele não era suportado na versão do projeto que estávamos utilizando (2021.3).

Para a renderização visual da curva, utilizamos um componente LineRenderer, que permite a renderização de uma linha por meio de pontos (que extrairíamos da curva). Ele é responsável por criar uma faixa visível que representa a curva ao longo do caminho definido pela Spline. O componente é bastante versátil, permitindo a customização de diversos parâmetros, como largura da linha, cor e material utilizado para renderização. Nossa intenção era utilizar essa ferramenta para dar forma visual à mecânica de curva que estávamos desenvolvendo.

Primeira implementação do Line Renderer, uma linha com ângulos fechados.
Primeira implementação pura do Line Renderer (posições fixas).

Uma das primeiras coisas que testamos foi se era possível criar uma máscara visual na curva, para que pudéssemos criar buracos por onde o personagem pudesse atravessar. Essa máscara é responsável por criar um buraco na linha do LineRenderer, permitindo posteriormente que o personagem possa atravessar a curva. Para isso, o primeiro teste foi feito com um shader criado através do Shader Graph da Unity, utilizando a função SphereMask. Felizmente, tivemos sucesso no teste e foi possível criar a máscara com sucesso.

Linha do LineRenderer com um buraco esférico no meio da linha.
Implementação de máscara no material do LineRenderer.
Gráfico do shader criado para implementação da máscara, em detalhe a função SphereMask.
Detalhe do shader utilizado para renderizar a máscara.
Linha do LineRenderer seguindo os pontos da curva.
Implementação do LineRenderer seguindo os pontos da curva.

Com o sucesso do primeiro teste da máscara, partimos para a renderização da linha conforme os pontos da curva. Para isso, atualizamos os pontos do LineRenderer com os pontos da curva. Em paralelo, testamos criar um colisor (PolygonCollider2D) baseado nos pontos da curva, mas falhamos na implementação.

Curva da margarina com detalhe do colisor construído pelo PolygonCollider2D, mas com algumas posições erradas.
Implementação do PolygonCollider2D com alguns pontos errados.

Então, decidimos usar um EdgeCollider2D e tivemos sucesso. O EdgeCollider2D é uma opção mais simples do que o PolygonCollider2D, pois só requer a definição dos mesmos pontos que foram usados no LineRenderer. Isso permitiu uma implementação mais eficiente e eficaz da mecânica.

Curva da margarina com implementação do EdgeCollider2D, se alinhando exatamente na posição esperada.
Implementação do EdgeCollider2D se alinhando perfeitamente com a curva.
Detalhe do colisor do EdgeCollider2D.

Para fazer o personagem ser acelerado na curva, foi usado um SurfaceEffector2D, um componente nativo da Unity que permite definir uma força em objetos que entram em contato com superfícies. Dessa forma, a curva foi definida como uma superfície com um SurfaceEffector2D aplicado, e o personagem passou a ser acelerado ao entrar em contato com ela.

Gif ilustrando o deslizamento do personagem ao longo da curva.
Implementação do SurfaceEffector2D no colisor da curva.

Além disso, realizamos testes adicionais de animação de textura na renderização da curva, movimentação da máscara e máscara de colisão. A animação da textura permitiria uma movimentação da textura junto com o fluxo de margarina, trazendo uma naturalidade para a experiência. Como definido no design, a máscara deve ser móvel e percorrer o trajeto todo definido pela curva, e deve ser possível que o personagem atravesse o buraco da máscara. A função SphereMask permite que movimentemos a máscara e também, adicionamos um colisor na máscara para que o personagem possa atravessar o colisor da curva ao passar por ela.

Gif ilustrando a animação horizontal da textura.
Implementação da animação horizontal da textura.
Curva com textura animada e máscara (buraco) se movendo ao longo do seu comprimento.
Máscara se movendo ao longo da curva.
Implementação da máscara de colisão.

Dessa forma, a prototipagem permitiu concluir que a mecânica da curva era tecnicamente viável e divertida, além de possibilitar a identificação de falhas rapidamente, garantindo um processo mais eficiente e econômico. Em seguida, só faltava validar a implementação de fluxos múltiplos.

O Desafio

Para implementar os fluxos múltiplos, decidimos criar uma curva, um LineRenderer e um EdgeCollider2D para cada fluxo. Isso nos permitiu ter uma curva independente para cada fluxo, possibilitando diferentes caminhos e velocidades. Para a renderização visual, utilizamos um shader que implementava 4 máscaras, uma para cada fluxo. Essa solução nos trouxe um limite de fluxos na fase, e isso rapidamente se tornou um problema.

Implementação de múltiplas máscaras na curva.
Gráfico de shader mostrando implementação de uma máscara baseada em no vetor providenciado.
Trecho do shader com implementação de uma máscara individual.
Trecho do shader juntando o resultado de 4 máscaras.
Gráfico do resultado final do shader, com a implementação de 4 máscaras.
Shader final, com implementação das 4 máscaras.
Implementação de múltiplos fluxos de margarina.
Fluxos múltiplos com uma máscara móvel por fluxo.

Nesse ponto, a mecânica já estava sendo estressada pelo Game Designer e seu primeiro feedback foi que ele precisaria de mais máscaras para cumprir o propósito esperado. Isso significa que teríamos que fazer o mascaramento de outra forma, preferencialmente que não tivesse o limite de 4 máscaras. Para isso, optamos por mudar a forma de mascaramento do shader para Stencil. No entanto, descobrimos que não havia suporte para Stencil no Shader Graph, o que significava que teríamos que escrever um shader manualmente.

Após algumas pesquisas, encontramos uma implementação de shader Stencil que poderíamos adaptar às nossas necessidades. O shader permitia que criássemos máscaras ilimitadas e de formatos diversos (o anterior era somente círculos). Com essa implementação, pudemos criar novos fluxos e, ao mesmo tempo, manter o desempenho do jogo, já que todos os fluxos utilizavam o mesmo material.

Trecho da curva com uma máscara do Pãozito.
Máscara com sprite do Pãozito.
Visualização do fluxo de margarina com várias máscaras de formatos e rotações diversas.
Máscaras de diversos formatos e rotações aplicadas na curva.

Isso nos permitiu criar níveis mais complexos e desafiadores para o jogador. A mudança também nos deu mais liberdade para experimentar novas ideias de fases e explorar as possibilidades da mecânica do jogo.

Visualização dos fluxos de margarina com uma máscara personalizada, no formato de uma engrenagem, que se rotaciona conforme sua movimentação pela curva.
Máscaras móveis com sprite personalizado e rotação durante a trajetória.

Polimento

Com as principais funcionalidades da mecânica concluídas, era hora de melhorar o visual. Ainda com a ideia visual incerta, decidimos testar algumas alternativas de animação da curva. A primeira delas foi tentar animar as posições dos pontos da curva para que “balançassem”. Para que isso funcionasse sem comprometer o desempenho, testamos usar o Job System da Unity, e acabou funcionando perfeitamente.

Fluxos de margarina se movimentando de forma estranha por falta de parametrização.
Fluxos de margarina pós parametrização dos valores da animação.
Visão dos fluxos de margarina e suas máscaras com mais detalhes, mostrando colisores, a curva principal e as extremidades do LineRenderer.
Detalhe das máscaras (antes da aplicação do Stencil), colisores e vértices dos fluxos.
Gráfico mostrando que o código de animação está sendo executado em múltiplos threads paralelos.
Profiler (no Editor) mostrando as barras verdes que indicam a animação rodando em vários threads em paralelo.

Porém, esse tipo de animação poderia também ser feito diretamente no shader sem termos que alterar nada no LineRenderer, ou seja, poderíamos ter uma melhoria de desempenho. Fizemos então um teste de animação seguindo a mesma lógica do experimento anterior, mas aplicando a movimentação nos vértices no shader. Também tivemos sucesso nesse teste.

Animação dos vértices do LineRenderer via shader.
Gráfico do shader mostrando as funções para obter animação de vértices.
Shader (antes da aplicação do Stencil) com animação dos vértices da curva.

Assim que a artista do time trouxe a solução visual, decidimos mudar mais uma vez a animação da curva, dessa vez para animação de textura. Esse tipo de animação deu mais liberdade à artista para que desenhasse o fluxo da forma que fizesse mais sentido dentro da solução proposta.

Visualização da animação de texturas (frame-to-frame), com máscaras em Stencil.

Com esses pontos concluídos, ainda melhoramos as texturas das máscaras e fizemos alguns ajustes pequenos de parametrização para chegarmos no estado que a mecânica está hoje no jogo.

Versão finalizada da mecânica.

Conclusão

A nossa experiência de desenvolvimento com a mecânica de fluxo de margarina nos mostrou que a prototipagem é essencial para validar ideias e testar a viabilidade técnica e diversão da mecânica. Além disso, tivemos que lidar com desafios inesperados, como o limite de máscaras e a necessidade de implementar um shader manualmente. No final, conseguimos superar esses obstáculos e chegar em um resultado que atendesse aos objetivos do jogo. Esperamos que nossa experiência possa inspirar outros desenvolvedores e incentivá-los a serem criativos e persistentes na busca por soluções.

E vocês, o que acharam de todo esse processo? No segundo capítulo do Pãozito ainda tivemos a introdução de outras mecânicas que também trouxeram experiências novas bem divertidas e interessantes. Querem saber mais? Comentem suas dúvidas e impressões e fiquem ligados nas nossas redes para mais novidades!

--

--