Advertisement
  1. Code
  2. Coding Fundamentals
  3. Game Development

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

Scroll to top
Read Time: 10 min
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 2

() translation by (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Neste tutorial, vamos construir um jogo simples na Unity, onde o jogador pode rebobinar o progresso (que também pode ser adaptado para funcionar em outros sistemas). Nesta primeira parte veremos o básico do sistema, e a próxima parte vai esclarecer tudo e tornar o sistema muito mais versátil.

Em primeiro lugar, vamos dar uma olhada nos jogos que usam isto. Então, veremos outros usos para esta técnica, antes de, finalmente, criar um jogo pequeno que pode rebobinar, que deve dar-lhe uma base para o seu próprio jogo.

A demonstation of the basic functionalityA demonstation of the basic functionalityA demonstation of the basic functionality
Uma demonstração da funcionalidade básica

Você precisará da versão mais recente da Unity para isso e deve ter alguma experiência. O código-fonte também está disponível para download, se você quiser comparar o seu progresso com ele.

Pronto? Vamos lá!

Como isso foi usado antes?

Prince Of Persia: The Sands of Time é um dos primeiros jogos a realmente integrar uma mecânica de voltar no tempo em sua jogabilidade. Quando você morre não precisa recarregar, mas sim voltar um pouco no passado durante alguns segundos para onde o personagem estava vivo e imediatamente, tentar novamente.

Prince of Persia The Forgotten Sands The Sands Of Time Trilogy integrates time-rewinding beautifully into its gameplay and avoids immersion-breaking quick-reloadingPrince of Persia The Forgotten Sands The Sands Of Time Trilogy integrates time-rewinding beautifully into its gameplay and avoids immersion-breaking quick-reloadingPrince of Persia The Forgotten Sands The Sands Of Time Trilogy integrates time-rewinding beautifully into its gameplay and avoids immersion-breaking quick-reloading
Prince of Persia: The Forgotten Sands. A trilogia The Sands Of Time integra o mecanismo de voltar no tempo maravilhosamente em sua jogabilidade e evita a quebra de imersão em um recarregamento da fase.

Essa mecânica não é apenas integrada à jogabilidade, mas a narrativa e o universo também, e isso é mencionado ao longo da história.

Outros jogos que utilizam esses sistemas são Braid, por exemplo, que também funciona com voltas no tempo. A heroína Tracer em Overwatch tem um poder que muda a sua posição para uma posição de alguns segundos atrás, isso é essencialmente voltar no tempo, mesmo em um jogo multiplayer. A série de jogos de corrida GRID também tem um mecanismo de retorno, onde você tem uma pequeno número de retrocessos durante uma corrida, que pode ser usado quando você comete uma falha crítica. Isso inibe a frustração causada por deslizes perto do final da corrida, o que pode ser muito irritante.

When you have a fatal crash in GRID you get the chance to rewind the game to a point before the crashWhen you have a fatal crash in GRID you get the chance to rewind the game to a point before the crashWhen you have a fatal crash in GRID you get the chance to rewind the game to a point before the crash
Quando você bate o carro em GRID você tem a chance de rebobinar o jogo até um ponto antes do acidente.

Outros usos

Mas este sistema não só pode ser usado para substituir salvamentos automáticos. Outra maneira de se aplicar isso é o "carro fantasma" em jogos de corrida e multiplayer assíncrono.

Replays

Replays são outra forma divertida de outra de empregar esse recurso. Isto pode ser visto em jogos como SUPERHOT, a série Worms e praticamente a maioria dos jogos esportivos.

Replays de esportes funcionam da mesma maneira que eles são apresentados na TV, onde uma ação é mostrada mais uma vez, possivelmente de um ângulo diferente. Para isso não é gravado um vídeo, mas sim as ações do usuário, permitindo que a repetição aconteça de ângulos e câmera diferentes. Jogos Worms usam isto de uma forma humorística, onde uma morte muito engraçada ou eficaz é mostrada em um Replay instantâneo.

SUPERHOT também registra seus movimentos. Quando você termina e fase seu progresso inteiro é então repetido, mostrando tudo o que aconteceu.

Super Meat Boy usa isso de uma maneira divertida. Quando você termina uma fase você vê um replay de todas as suas tentativas anteriores uma seguida da outra, terminando com a última performance.

The end-of-level replay in Super Meat Boy Every one of your previous attempts is recorded and then played back at the same timeThe end-of-level replay in Super Meat Boy Every one of your previous attempts is recorded and then played back at the same timeThe end-of-level replay in Super Meat Boy Every one of your previous attempts is recorded and then played back at the same time
A repetição de fim-de-fase no Super Meat Boy. Cada uma de suas tentativas anteriores é gravada e reproduzida ao mesmo tempo.

Fantasmas em modos contra-relógio

Fantasmas de corrida é uma técnica onde você corre pelo melhor tempo em uma pista vazia. Mas ao mesmo tempo, você disputa contra um fantasma, que é um carro transparente que dirige exatamente como você dirigiu no melhor tempo. Você não pode colidir com ele, o que significa que você ainda pode se concentrar em obter o melhor tempo.

Em vez de dirigir sozinho você pode competir contra si mesmo, o que torna tudo mais divertido. Esse recurso aparece na maioria dos jogos de corrida, desde a série Need for Speed até Diddy Kong Racing.

Racing a ghost in Trackmania Nations This one has the silver difficulty meaning I will get the silver medal if I beat them Note the overlap in car-models showing the ghost isnt corporeal and can be driven throughRacing a ghost in Trackmania Nations This one has the silver difficulty meaning I will get the silver medal if I beat them Note the overlap in car-models showing the ghost isnt corporeal and can be driven throughRacing a ghost in Trackmania Nations This one has the silver difficulty meaning I will get the silver medal if I beat them Note the overlap in car-models showing the ghost isnt corporeal and can be driven through
Fantasma de corrida em Trackmania Nations. Este tem a dificuldade prata, o que significa que o jogador receberá a medalha de prata se vencê-lo. Observe a sobreposição nos modelos dos carros, mostrando o fantasma não é físico e pode ser atravessado.

Fantasmas Multiplayer

Fantasmas multiplayer assíncronos são outra maneira de usar essa funcionalidade. Neste recurso raramente utilizado, partidas multiplayer são realizadas para gravar os dados de um jogador, que então envia sua partida para outro jogador, que posteriormente pode batalhar contra o primeiro jogador. Os dados são aplicados da mesma forma que seriam em um fantasma em modos contra-relógio, só que você está correndo contra outro jogador.

Isso é usado nos jogos de Trackmania, onde é possível jogar contra certas dificuldades. Estes pilotos gravados lhe darão um adversário para vencer por uma certa recompensa.

Edição de filme

Alguns jogos oferecem isso desde o início, mas se for bem usado pode ser uma ferramenta divertida. Team Fortress 2 oferece um editor de repetição embutido, com o qual você pode criar seus próprios clipes.

The replay-editor from Team Fortress 2 Once recorded a match can be seen from any perspective not just the playersThe replay-editor from Team Fortress 2 Once recorded a match can be seen from any perspective not just the playersThe replay-editor from Team Fortress 2 Once recorded a match can be seen from any perspective not just the players
O editor de repetição em Team Fortress 2. Uma vez gravado um jogo, ele pode ser visto de qualquer perspectiva, não apenas a do jogador.

Uma vez que o recurso é ativado, você pode gravar e assistir jogos anteriores. O elemento vital é que tudo é gravado, não só o que você vê. Isto significa que você pode se mover no mundo do jogo gravado, ver onde todo mundo está e ter controle sobre o tempo.

Como construí-lo

Para testar este sistema, precisamos de um jogo simples onde podemos testá-lo. Vamos criar um!

O jogador

Crie um cubo em sua cena, este será nosso personagem. Em seguida, crie um novo script C# chamado Player.cs e escreva a função Update() desta forma:

1
void Update()
2
{
3
    transform.Translate (Vector3.forward * 3.0f * Time.deltaTime * Input.GetAxis ("Vertical"));
4
    transform.Rotate (Vector3.up * 200.0f * Time.deltaTime * Input.GetAxis ("Horizontal"));
5
}

Isto vai ler os movimentos nas setas do teclado. Anexe este script no cubo. Agora se você apertar Play será capaz de se movimentar.

Ajuste o ângulo da câmera para que possa ver o cubo de cima. Por fim, crie um plano para servir como chão e atribua alguns materiais diferentes para cada objeto, para que não caiamos no vazio. Deve ficar desta forma:

Angle the camera so it views the cube from aboveAngle the camera so it views the cube from aboveAngle the camera so it views the cube from above

Experimente. Você deve ser capaz de mover o cubo usando WSAD e as setas.

O TimeController

Agora crie um novo script C# chamado TimeController.cs e adicione-o em um novo GameObject vazio. Isto irá lidar com a gravação real e a função rebobinar do jogo.

Para fazer isso, registraremos o movimento do personagem. Quando apertarmos o botão de voltar no tempo, nós vamos adaptar as coordenadas do jogador. Para isso comece criando uma variável para armazenar o jogador, como esta:

1
public GameObject player;

Atribuia o objeto jogador para o slot criado no TimeController, para que ele possa acessar o jogador e seus dados.

Assign the player-object to the resulting slot on the TimeControllerAssign the player-object to the resulting slot on the TimeControllerAssign the player-object to the resulting slot on the TimeController

Agora, precisamos criar uma matriz para armazenar os dados do jogador:

1
public ArrayList playerPositions;
2
3
void Start()
4
{
5
    playerPositions = new ArrayList();
6
}

O que faremos é gravar continuamente a posição do jogador. Teremos a posição de onde o jogador estava no último quadro, a posição onde o jogador estava 6 frames atrás e a posição onde o jogador estava há 8 segundos (ou o quanto você deseja gravar). Mais tarde quando apertarmos o botão, vamos buscar nossa matriz de posições e atribuí-la quadro a quadro, causando a impressão de voltar no tempo.

Primeiro, vamos salvar os dados:

1
void FixedUpdate()
2
{
3
    playerPositions.Add (player.transform.position);
4
}

Na função FixedUpdate() gravamos os dados. FixedUpdate() rodará 50 ciclos por segundo (ou o que achar melhor), o que permite um intervalo fixo para gravar e definir os dados. A função Update() roda dependendo de quantos frames o CPU suporta, o que faz as coisas mais difíceis.

Este código irá armazenar a posição do jogador em cada frame na matriz. Agora precisamos aplicar!

Vamos adicionar uma verificação para ver se o botão foi pressionado. Para isso, precisamos de uma variável booleana:

1
public bool isReversing = false;

e também de um controle na função Update() para marcá-la quando quisermos rebobinar os passos:

1
void Update()
2
{
3
    if(Input.GetKey(KeyCode.Space))
4
    {
5
        isReversing = true;
6
    }
7
    else
8
    {
9
        isReversing = false;
10
    }
11
}

Para fazer o jogo funcionar de trás pra frente, vamos aplicar os dados em vez de gravá-los. O novo código para registrar e aplicar a posição do jogador deve ficar assim:

1
void FixedUpdate()
2
{
3
    if(!isReversing)
4
    {
5
        playerPositions.Add (player.transform.position);
6
    }
7
    else
8
    {
9
        player.transform.position = (Vector3) playerPositions[playerPositions.Count - 1];
10
        playerPositions.RemoveAt(playerPositions.Count - 1);
11
    }
12
}

E todo o script TimeController ficará assim:

1
using UnityEngine;
2
using System.Collections;
3
4
public class TimeController: MonoBehaviour
5
{
6
    public GameObject player;
7
    public ArrayList playerPositions;
8
    public bool isReversing = false;
9
10
    void Start()
11
    {
12
        playerPositions = new ArrayList();
13
    }
14
15
    void Update()
16
    {
17
        if(Input.GetKey(KeyCode.Space))
18
        {
19
            isReversing = true;
20
        }
21
        else
22
        {
23
            isReversing = false;
24
        }
25
    }
26
  
27
    void FixedUpdate()
28
    {
29
        if(!isReversing)
30
        {
31
            playerPositions.Add (player.transform.position);
32
        }
33
        else
34
        {
35
            player.transform.position = (Vector3) playerPositions[playerPositions.Count - 1];
36
            playerPositions.RemoveAt(playerPositions.Count - 1);
37
        }
38
    }
39
}

Também, não se esqueça de adicionar uma checagem na classe Player para ver se o TimeController atualmente está rebobinando ou não, e só mover-se quando ele não está voltando. Caso contrário, ele pode criar um comportamento inesperado:

1
using UnityEngine;
2
using System.Collections;
3
4
public class Player: MonoBehaviour
5
{
6
    private TimeController timeController;
7
8
    void Start()
9
    {
10
        timeController = FindObjectOfType(typeof(TimeController)) as TimeController;
11
    }
12
    
13
    void Update()
14
    {
15
        if(!timeController.isReversing)
16
        {
17
    	    transform.Translate (Vector3.forward * 3.0f * Time.deltaTime * Input.GetAxis ("Vertical"));
18
    	    transform.Rotate (Vector3.up * 200.0f * Time.deltaTime * Input.GetAxis ("Horizontal"));
19
        }
20
    }
21
}

Essas novas linhas automaticamente localizarão o objeto TimeController na inicialização e verificarão durante tempo de execução para ver se estamos atualmente jogando ou rebobinando. Só podemos controlar o personagem quando nós não estamos voltando no tempo.

Agora você deve ser capaz de se mover pelo mundo e rebobinar seu movimento pressionando espaço. Se você baixar o pacote de compilação anexado a este artigo e abrir o TimeRewindingFunctionality01 você pode experimentar!

Mas espere, por que é que nosso cubo continua olhando para a última posição que nós o deixamos? Porque não gravamos também sua rotação!

Para isso você precisa de outra matriz para manter seus valores de rotação, instanciá-lo no início, salvar e aplicar os dados da mesma maneira que lidamos com dados de posição.

1
using UnityEngine;
2
using System.Collections;
3
4
public class TimeController: MonoBehaviour
5
{
6
    public GameObject player;
7
    public ArrayList playerPositions;
8
    public ArrayList playerRotations;
9
    public bool isReversing = false;
10
    
11
    void Start()
12
    {
13
        playerPositions = new ArrayList();
14
        playerRotations = new ArrayList();
15
    }
16
    
17
    void Update()
18
    {
19
        if(Input.GetKey(KeyCode.Space))
20
        {
21
            isReversing = true;
22
        }
23
        else
24
        {
25
            isReversing = false;
26
        }
27
    }
28
    
29
    void FixedUpdate()
30
    {
31
        if(!isReversing)
32
        {
33
            playerPositions.Add (player.transform.position);
34
            playerRotations.Add (player.transform.localEulerAngles);
35
        }
36
        else
37
        {
38
            player.transform.position = (Vector3) playerPositions[playerPositions.Count - 1];
39
            playerPositions.RemoveAt(playerPositions.Count - 1);
40
    
41
            player.transform.localEulerAngles = (Vector3) playerRotations[playerRotations.Count - 1];
42
            playerRotations.RemoveAt(playerRotations.Count - 1);
43
        }
44
    }
45
}

Experimente! TimeRewindingFunctionality02 é a versão melhorada. Agora nosso cubo pode voltar no tempo e ele vai parecer do mesmo jeito que estava naquele momento.

Conclusão

Nós construímos um protótipo simples com um sistema de voltar no tempo já utilizável, mas ele ainda está longe de pronto. Na próxima parte desta série vamos torná-lo muito mais estável e versátil e adicionar alguns efeitos.

Aqui está o que ainda precisamos fazer:

  • gravar apenas a cada ~12 frames e interpolar entre o que foi gravado para salvar uma carga enorme de dados;
  • apenas gravar as últimas posições ~75 posições e rotações do jogador para certificar-se de que a matriz fique muito grande para que o jogo não tenha problemas.

Nós também daremos uma olhada em como estender este sistema para o jogador:

  • gravar mais do que apenas o jogador;
  • adicionar um efeito para indicar que o rebobinamento está acontecendo (como um efeito de blur);
  • usar uma classe personalizada para manter a posição do jogador e a rotação em vez de matrizes.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
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.