Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Unity 3D

Como construir um sistema de volta no tempo como em Prince Of Persia, parte 2

by
Length:MediumLanguages:
This post is part of a series called How to Build a Prince-Of-Persia-Style Time-Rewind System.
How to Build a Prince-of-Persia-Style Time-Rewind System, Part 1

Portuguese (Português) translation by Jonathan Ramos (you can also view the original English article)

Final product image
What You'll Be Creating

Da última vez nós criamos um jogo simples onde nós podíamos voltar no tempo para um ponto anterior. Agora vamos solidificar esse recurso e torná-lo muito mais divertido de usar.

Tudo o que faremos aqui será em cima da parte anterior, então dê uma olhada! Como antes, você precisa do Unity e de um entendimento básico da ferramenta.

Pronto? Vamos lá!

Gravar menos dados e interpolar

Nós gravamos as posições e rotações do jogador 50 vezes por segundo. Essa quantidade de dados rapidamente se tornará insustentável, e isto se tornará especialmente perceptível com configurações de jogos mais complexas e dispositivos móveis com menor poder de processamento.

O que podemos fazer, em vez disso, é gravar apenas 4 vezes por segundo e interpolar entre os quadros-chave. Assim podemos economizar 92% de processamento e obter resultados que são indistinguíveis das gravações de 50 quadros, já que eles são executados em frações de segundo.

Vamos começar gravando apenas um quadro-chave a cada x quadros. Para fazer isso, primeiro precisamos destas novas variáveis:

A variável keyframe é o quadro no método FixedUpdate no qual vamos gravar os dados do jogador. Por enquanto, definimos como 5, o que significa que na quinta vez do ciclo do método FixedUpdate os dados serão registrados. Como FixedUpdate é executado 50 vezes por segundo, isso significa que serão gravados 10 quadros por segundo, em comparação com os 50 de antes. A variável frameCounter será usada para contar os quadros até o próximo quadro-chave.

Agora adapte o bloco de gravação na função FixedUpdate para ficar assim:

Se você experimentá-lo agora, você verá que a volta no tempo está mais curta do que antes. Isso é porque nós gravamos menos dados, mas reproduzimos na velocidade normal. Agora precisamos mudar isso.

Primeiro, precisamos de outra variável chamada frameCounter não para gravar dados, mas para executá-los.

Adapte o código que restaura a posição do jogador para utilizar isto da mesma maneira que nós gravamos os dados. A função FixedUpdate deve ficar assim:

Agora, quando você volta no tempo, o jogador irá saltar de volta para suas posições anteriores, em tempo real!

Não é bem o que queremos, na verdade. Precisamos interpolar entre os quadros-chave, o que será um pouco mais complicado. Primeiro, precisamos dessas quatro variáveis:

Isso salvará os dados atuais do jogador e do quadro-chave gravado antes para que possamos interpolar entre os dois.

Então, temos esta função:

Isto irá atribuir as informações correspondentes às variáveis de posição e a rotação para podermos interpolar. Precisamos disto em uma função separada, já que ela será chamada em dois pontos diferentes.

Nosso bloco de restauração de dados deve parecer como este:

Chamamos a função para obter o último e penúltimo set de informação dos nossos vetores sempre que o contador chega no intervalo de frames que digitamos (no caso 5), mas também precisamos chamar isso no primeiro ciclo quando o restauro está acontecendo. É por isso que temos este bloco:

Para que isso funcione, você precisa também da variável firstRun:

E redefini-la quando o botão de espaço é solto:

Eis como funciona a interpolação:

Em vez de usar apenas o último quadro-chave que salvamos, este sistema obtém o último e o penúltimo e interpola entre eles. A quantidade de interpolação baseia-se em quão distantes os quadros estão.

Tudo isto acontece através da função Lerp, onde acrescentamos a posição atual (ou rotação) e a anterior. Então a fração da interpolação é calculada, o que pode ir de 0 a 1. O jogador é colocado no lugar equivalente entre esses dois pontos salvos, por exemplo, 40% na rota para o último quadro-chave.

Quando você diminui a velocidade e executa quadro a quadro, você pode realmente ver o movimento do personagem do jogador entre os quadros-chave, mas no jogo, não é perceptível.

E, assim, reduzimos extremamente a complexidade da implementação e tornamos tudo mais estável.

Gravando apenas um número fixo de quadros-chave

Agora que reduzimos consideravelmente o número de quadros que realmente salvamos, podemos nos certificar que não salvamos dados demais.

Agora temos apenas uma pilha com os dados gravados na matriz, o que não é bom a longo prazo. Conforme a matriz cresce, ela se tornará mais difícil de manejar, acessar levará mais tempo e a instalação inteira vai se tornar mais instável.

Para corrigir isto, nós podemos criar um código que verifica se a matriz cresce até um determinado tamanho. Se sabemos quantos quadros por segundo vamos salvar, podemos determinar quantos segundos de tempo "retornável" devemos guardar, ajustando-se a complexidade do jogo. O sistema do Prince of Persia permite talvez 15 segundos de tempo retorno do tempo, enquanto a configuração simples de Braid permite um retorno ilimitado.

O que acontece é que, quando a matriz cresce ao longo de um determinado tamanho, nós removemos a primeira entrada da mesma. Assim, ele só fica o tempo que queremos que o jogador volte, e não há perigo de isso se tornar muito grande para usar eficientemente. Coloque isso na função FixedUpdate após a gravação e a repetição de código.

Use uma classe personalizada para armazenar dados do jogador

Agora, nós gravamos as posições dos jogadores e rotações em duas matrizes separadas. Apesar de funcionar, é preciso lembrar de sempre gravar e acessar os dados em dois lugares ao mesmo tempo, o que pode gerar problemas futuros.

O que podemos fazer, no entanto, é criar uma classe separada para guardar ambas essas coisas e ainda mais (caso isso seja necessário no seu projeto).

O código para uma classe personalizada atuar como um contêiner para os dados é o seguinte:

Você pode adicioná-lo ao arquivo TimeController.cs, logo antes do início da declaração da classe. O que o script faz é fornecer um recipiente para salvar a posição e a rotação do jogador. O método construtor permite que sejam criados diretamente com as informações necessárias.

O resto do algoritmo precisará ser adaptado para trabalhar com o novo sistema. No método Start, a matriz precisa ser inicializada:

Em vez de dizer:

Podemos guardar diretamente em um objeto Keyframe:

O que fazemos aqui é adicionar a posição e a rotação do jogador para o mesmo objeto, que em seguida é adicionado em uma única matriz, o que reduz a complexidade desta configuração.

Adicionando um efeito de desfoque para sinalizar que a volta no tempo está acontecendo

Precisamos de algum tipo de sinal nos dizendo que o jogo está sendo rebobinado. Nós desenvolvedores sabemos disso, mas um jogador pode ficar confuso. Em tais situações, é bom ter várias coisas dizendo ao jogador que a volta no tempo está acontecendo, como efeitos visuais(borrando a tela um pouco) e áudio (deixando a música mais baixa ou ao contrário).

Vamos fazer algo semelhante ao que é feito em Prince of Persia, com algum efeito de blur.

A screenshot of the time-rewinding from Prince of Persia The Forgotten Sands
Voltando no tempo em Prince of Persia: The Forgotten Sands

Unity permite que você adicione vários efeitos de câmera juntos, e com algumas experiências, você pode fazer esse efeito encaixar-se perfeitamente no seu projeto.

Antes de usarmos os efeitos básicos, precisamos importá-los. Para fazer isso, vá em Assets > Import Package > Effects e importe tudo o que é oferecido.

View of the effects-menu in Unity 3D

Efeitos visuais podem ser adicionados diretamente na câmara principal. Vá em Components > Image Effects e adicione os efeitos Blur e Bloom. A combinação desses dois deve resultar no efeito que queremos.

View of the effects-inspector in Unity 3D
Estas são as configurações básicas. Você pode ajustá-las para o seu projeto.

Experimente agora, o jogo terá este efeito o tempo todo.

A screenshot of the effect in use

Agora precisamos ativá-lo e desativá-lo respectivamente. Por isso, o TimeController precisa importar os efeitos de imagem. Adicione esta linha no início:

Para acessar a câmera do TimeController, adicione essa variável:

E atribua-o na função Start:

Adicione este código para ativar os efeitos enquanto volta no tempo, e desativa-lo no final:

Quando você pressiona o botão de espaço, você não apenas começa a voltar no tempo, mas também ativa os efeitos na câmera, dizendo ao jogador que algo está acontecendo.

Todo o código do TimeController deve ficar assim:

Baixe o pacote compilado anexado e experimente!

Conclusão

Nosso jogo agora está muito melhor que antes. O algoritmo é visivelmente melhor e usa 90% menos poder de processamento, é muito mais estável, e temos um feedback para o usuário quando eles estão voltando no tempo.

Agora faça um jogo usando isso!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.