• Jogos
  • 05. Física

Simulação e Física

Movimento Retilíneo Uniforme

Vamos supor, em um primeiro momento, que o jogo não possui aceleração da gravidade. Assim, o personagem vai cair em velocidade constante até chegar no chão.

Um primeiro problema é que, sem aceleração da gravidade o personagem não vai cair, pois sua velocidade vertical é zero. Vamos então supor que o personagem já começa caindo, com velocidade constante.

Exercício 1

Vamos retomar nossa pergunta original. Supondo que a distância da plataforma até o chão é de 100 pixels, quantos pixels o personagem deve se mover a cada frame para chegar no chão em 0,5 segundos?

Resposta

Nós sabemos a distância e o tempo. Usando a fórmula do Movimento Retilíneo Uniforme, nós poderíamos calcular a velocidade em pixels por segundo:

\[ v = \frac{\Delta S}{\Delta t} = \frac{100 pixels}{0,5 segundos} = 200 pixels/segundo \]

Entretanto, como não temos nenhuma maneira de relacionar o tempo em segundos com a quantidade de frames. Ainda nos falta informação!

Relacionando frames e tempo#

O problema é que nós não sabemos quanto tempo se passa de um frame para o outro. Em um computador antigo e lento, pode ser que o tempo entre os frames seja 200 ms. Em um computador mais rápido, pode ser que o tempo entre os mesmos frames seja 2 ms. Pior ainda, o tempo pode ser diferente de um frame para o outro!

Isso pode ocorrer devido a fatores internos ou externos. Um exemplo de fator interno é se em um determinado frame você precisar realizar um cálculo muito pesado. Isso pode fazer com que o tempo entre os frames aumente temporariamente. Um exemplo de fator externo é se o seu navegador começar a usar todo o processamento do computador. Nesse caso, o jogo pode acabar ficando mais lento por precisar esperar a liberação de recursos computacionais.

Então a resposta para a pergunta "quanto tempo se passa de um frame para outro" é depende! Não temos como saber de antemão. Precisamos medir isso de alguma forma. Felizmente, o Pygame já possui uma função que faz isso: pygame.time.get_ticks()

Exercício 2

Abra a documentação da função pygame.time.get_ticks(). Escreva abaixo, em uma frase, o que essa função faz.

Resposta

A função devolve quanto tempo se passou, em milissegundos, desde que a função pygame.init() foi chamada.

Exercício 3

Escreva abaixo, em uma frase, como você faria para calcular o tempo decorrido entre 2 frames consecutivos utilizando a função pygame.time.get_ticks().

Resposta

Como a função pygame.time.get_ticks() devolve o tempo em milissegundos desde a chamada ao pygame.init(), precisamos guardar o resultado de pygame.time.get_ticks() em um frame e subtraí-lo do resultado dessa mesma função no frame seguinte. O resultado seria mais ou menos assim:

import pygame

pygame.init()
t0 = -1
while True:
    t1 = pygame.time.get_ticks()
    if t0 >= 0:  # Ignora a primeira vez
        print(f'Tempo entre frames: {t1 - t0} milissegundos')
    t0 = t1

No exercício acima, calculamos quanto tempo se passou entre dois frames. Uma outra forma de interpretar essa informação é o seu inverso, ou seja, quantos frames foram mostrados em 1 segundo. Para gamers, essa é uma medida bastante conhecida: o FPS (frames por segundo).

Exercício 4

Suponha que você calculou o tempo entre dois frames \(t = 3\) milissegundos. Como você calcularia o FPS?

Resposta

FPS é Frames Por Segundo, mas o nosso tempo está em milissegundos. Para transformar milissegundos em segundos, dividimos o valor por 1000. Assim:

\[ FPS = \frac{frame}{\frac{t}{1000} segundos} = \frac{1000\text{ }frame}{t\text{ }segundos} \]

Exercício 5

Faça o exercício Calcula FPS e depois volte para este handout.

Voltando ao MRU#

Agora sim temos todos os dados que precisamos para saber quantos pixels o personagem deve se mover para o personagem chegar no chão em 0,5 segundos.

Exercício 6

Escreva abaixo qual seria a linha de código que calcula quantos pixels devem ser adicionados à posição do personagem em um frame para obter o resultado desejado. Já calculamos no Exercício 1 que a velocidade deve ser 200 pixels/segundo. Armazene o resultado em uma variável chamada dy.

Suponha que a variável dt armazena o tempo entre frames, em milissegundos, e a variável vy armazena o valor 200 (referente à velocidade no eixo vertical).

Resposta

dy = vy * dt / 1000

Implementando o MRU#

Agora vamos desenvolver nosso primeiro programa usando física. Abra o exercício Bola Bate Rebate no VS Code e volte para este handout para seguir as instruções.

Acessar exercício

Vamos começar entendendo o código que recebemos.

Exercício 7

No código jogo.py temos a definição da variável que guarda o estado do jogo. Olhando para esta variável, em que coordenadas a bola começa no jogo?

Resposta

O dicionário state contém a chave ball_pos, que representa a posição inicial da bola como uma lista em que o primeiro elemento é a coordenada x e o segundo a coordenada y.

Exercício 8

Para deixar nossos nomes mais consistentes, vamos renomear a função recebe_eventos. Como agora ela atualiza o estado do jogo, incluindo tanto receber eventos quanto fazer a simulação física, agora ela deverá se chamar atualiza_estado. Essa função deverá receber o estado atual do jogo como argumento.

Sua função deve passar no teste test_renomeou_funcao.

Agora vamos implementar o Movimento Retilíneo Uniforme em uma simulação simples. Seu jogo deverá ter o comportamento abaixo no fim deste guia.

Um ponto importante desse nosso primeiro jogo com física é que decompomos o movimento da bola em dois MRUs: um na horizontal e um na vertical. Assim usamos sempre o caso mais simples 1D mesmo com movimentos em 2D.

Exercício 9

Precisamos guardar a velocidade da bola em pixels por segundo no estado inicial do jogo. Faça isso criando uma chave "ball_vel" no estado do jogo e guardando a velocidade horizontal igual a 400 pixels por segundo e a vertical 300 pixels por segundo. Guarde-os em uma lista, assim como foi feito com a posição.

Seu código deve passar no teste test_inicializa_estado_jogo_com_velocidade.

Agora precisamos calcular quanto tempo se passou entre frames no nosso programa.

Exercício 10

O cálculo do nosso \(\Delta t\) será feito com a função pygame.time.get_ticks. Isso é feito em três partes:

  1. Guardamos o tempo da última atualização na chave "last_updated" do estado do jogo. Esta chave começa com o valor 0. Essa chave vai cumprir o mesmo papel da chave "t0" do exercício do FPS.
  2. No início de cada execução de atualiza_estado, chamamos pygame.time.get_ticks e guardamos esse valor em uma variável (poderia ser t1).
  3. Calculamos \(\Delta t\) como a diferença entre o valor acima e o valor de "last_updated". Sugestão: armazene o valor de \(\Delta t\) em segundos.
  4. No fim de cada execução de atualiza_estado sobrescrevemos o valor de "last_updated" com o valor obtido no item 2.

Implemente a lógica acima no seu jogo. Seu código deve passar nos testes

  • test_inicializa_estado_jogo_com_last_updated
  • test_atualiza_estado_chama_get_ticks_e_atualiza_last_updated

Exercício 11

Finalmente, como temos \(\Delta t\) podemos atualizar a posição da bola. Faça isto agora. Você deve atualizar tanto a posição x quando a posição y dentro de atualiza_estado seguindo a fórmula que deduzimos do MRU:

  • prox_posicao = posicao_atual + velocidade * delta_t

Seu código deverá passar nos seguintes testes:

  • test_atualiza_estado_delta_t_1000_movimento_vertical
  • test_atualiza_estado_delta_t_500_movimento_vertical
  • test_atualiza_estado_delta_t_1000_movimento_horizontal
  • test_atualiza_estado_delta_t_500_movimento_horizontal
  • test_atualiza_estado_delta_t_200_movimento_composto

Estamos quase acabando nossa simulação. Execute o programa e veja o que está faltando para sua simulação ter os resultados esperados.

A bola não rebate nas bordas!

Exercício 12

Para que isto aconteça, quais partes do estado do jogo você precisaria modificar?

Resposta

Para que a bola rebata nas bordas precisamos alterar sua velocidade!

A colisão com a borda será feita da mesma forma que o movimento: 1 dimensão por vez. Ou seja, primeiro vemos se a bola está saindo da tela na vertical e tratamos esse caso. Depois fazemos o mesmo para a horizontal.

Exercício 13

Sabendo que a nossa tela do jogo tem largura 640 e altura 480, qual seria a condição a ser checada para ver se a bola, que tem raio 10, saiu da tela na coordenada horizontal? Considere nas alternativas abaixo que

x = state['ball_pos'][0]
y = state['ball_pos'][1]

Resposta

A bola sai da tela se sua extremidade esquerda tiver coordenada x menor que 0 ou se sua extremidade direita tiver coordenada x maior que a largura da tela (640). A posição que guardamos é a do centro da bola, logo, obtemos as extremidades adicionando/subtraindo o valor do raio do centro.

Sabendo disso, implemente a bola rebatendo em seu jogo. Leve em conta as seguintes dicas:

  1. Se você detectar que a bola está fora da tela em uma componente, deve inverter a velocidade da bola nesta componente
  2. A bola nunca pode sair da tela, se ela sair coloque-a de volta na tela no ponto da colisão

Agora você é capaz de implementar o Movimento Retilíneo Uniforme em seus jogos! O próximo passo é adicionar a possibilidade de aceleração.

CHECK 4

Agora você já pode fazer o check 4. Depois de concluir, faça um commit (não se esqueça de sincronizar/dar push) com a mensagem "Check 4".

Não se esqueça de mostrar para algum professor para ganhar o check!

CHECK 5

Agora você também já pode fazer o check 5. Depois de concluir, faça um commit (não se esqueça de sincronizar/dar push) com a mensagem "Check 5".

Não se esqueça de mostrar para algum professor para ganhar o check!