Câmera Dinâmica e Salto de Sprite
O objetivo deste tutorial é revisar os principais conceitos necessários para programar jogos. Criaremos um ambiente virtual onde você controla um cão que anda em um cenário onde a câmera o acompanha dinamicamente. A imagem abaixo exemplifica a implementação que buscamos.
Você pode fazer o download dos arquivos de imagem usado neste tutorial clicando no link abaixo.
Vamos organizar nosso código dividindo-o em 4 seções.
- Condições Iniciais (onde poderemos calibrar a dinâmica do jogo)
- Funções (para reaproveitamento de código ou organização)
- Início do Gameloop com Atualização de Variáveis
- Final do Gameloop com update de sprites, desenho de imagens e update da janela
Obs: O paradigma de orientação a objetos será evitado para garantir que o código seja entendido por iniciantes, mas alguns comentários acerca deste paradigma serão realizados esporadicamente.
Como iremos desenhar a animação do cão quando ele está parado (fazendo pequenos movimentos para dar vida ao personagem), precisamos verificar as imagens usadas nessa animação. Chamamos esta imagem de sprite, e ela é um conjunto de frames colocados lado a lado como podemos observar na figura.
Avaliando a imagem do cão parado verificamos que este sprite possui 6 frames. Então já podemos iniciar nosso código.
from PPlay.window import *
from PPlay.gameimage import *
from PPlay.keyboard import *
from PPlay.sprite import *
# DEFINIÇÕES INICIAIS
# Variáveis
posxDog = 100
posyDog = 320
# Objetos
janela = Window(700,500)
cenario = GameImage("cenario.webp")
teclado = Keyboard()
dogStoppedRight = Sprite("dogStoppedRight.webp", 6)
dogStoppedRight.set_sequence_time(0, 6, 400, True)
dogStoppedRight.x = posxDog
dogStoppedRight.y = posyDog
while True:
# ATUALIZAÇÃO DE VARIÁVEIS
# DESENHO E ATUALIZAÇÃO
cenario.draw()
dogStoppedRight.update()
dogStoppedRight.draw()
janela.update()
As linhas em destaque acima são as responsáveis por instanciarmos o objeto que representa o cão parado e virado para a direita. Vamos adicionar um novo sprite que representa o cão andando para a direita e vamos fazer o controle desse movimento através do teclado. Para isso vamos verificar a imagem do sprite do cão andando para a direita.
Novamente verificamos a presença de 6 frames neste sprite.
from PPlay.window import *
from PPlay.gameimage import *
from PPlay.keyboard import *
from PPlay.sprite import *
# DEFINIÇÕES INICIAIS
# Variáveis
posxDog = 100
posyDog = 320
velxDog = 250
# Objetos
janela = Window(700,500)
cenario = GameImage("cenario.webp")
teclado = Keyboard()
dogStoppedRight = Sprite("dogStoppedRight.webp", 6)
dogStoppedRight.set_sequence_time(0, 6, 400, True)
dogStoppedRight.x = posxDog
dogStoppedRight.y = posyDog
dogWalkRight = Sprite("dogWalkRight.webp", 6)
dogWalkRight.set_sequence_time(0, 6, 100, True)
dogWalkRight.x = posxDog
dogWalkRight.y = posyDog
while True:
# ATUALIZAÇÃO DE VARIÁVEIS + DESENHO E ATUALIZAÇÃO
cenario.draw()
if teclado.key_pressed('RIGHT'):
dogWalkRight.x += velxDog * janela.delta_time()
dogStoppedRight.x += velxDog * janela.delta_time()
dogWalkRight.update()
dogWalkRight.draw()
else:
dogStoppedRight.update()
dogStoppedRight.draw()
janela.update()
Neste momento verificamos que há uma necessidade de fazer um controle sobre qual Sprite será desenhado de acordo com o que estiver sendo pressionado no teclado. Além disso, o cenário deve ser desenhado antes de todos os outros sprites para que não haja sobreposição. Se continuarmos implementando com essa estrutura o nosso código irá ficar um pouco bagunçado. Então vamos instanciar uma nova variável que será usada para armazenar o estado do cachorro e a partir desse estado iremos desenhar o cão no final do código. Além disso, em vez de atualizarmos o x e o y de cada sprite dentro dos condicionais, vamos atualizar as variáveis posx e posy e usar elas para alterar a posição de todos os sprites.
from PPlay.window import *
from PPlay.gameimage import *
from PPlay.keyboard import *
from PPlay.sprite import *
# DEFINIÇÕES INICIAIS
# Variáveis
posxDog = 100
posyDog = 320
velxDog = 250
dogState = 'parado-direita'
# Objetos
janela = Window(700,500)
cenario = GameImage("cenario.webp")
teclado = Keyboard()
dogStoppedRight = Sprite("dogStoppedRight.webp", 6)
dogStoppedRight.set_sequence_time(0, 6, 400, True)
dogStoppedRight.x = posxDog
dogStoppedRight.y = posyDog
dogWalkRight = Sprite("dogWalkRight.webp", 6)
dogWalkRight.set_sequence_time(0, 6, 100, True)
dogWalkRight.x = posxDog
dogWalkRight.y = posyDog
while True:
# ATUALIZAÇÃO DE VARIÁVEIS
if teclado.key_pressed('RIGHT'):
dogState = 'andando-direita'
posxDog += velxDog * janela.delta_time()
else:
dogState = 'parado-direita'
# Movimento
dogStoppedRight.x = posxDog
dogWalkRight.x = posxDog
# DESENHO E ATUALIZAÇÃO
cenario.draw()
if dogState == 'parado-direita':
dogStoppedRight.update()
dogStoppedRight.draw()
elif dogState == 'andando-direita':
dogWalkRight.update()
dogWalkRight.draw()
janela.update()
Agora temos nosso código mais bem organizado. Podemos criar duas funções para os blocos que estão marcados de amarelo para tornar nosso código principal mais enxuto, e cada vez que adicionarmos uma sprite nova fazemos sua movimentação e seu desenho dentro dessas duas funções. Assim, vamos usar a estrutura switch / case do python para tornar o código mais legível.
from PPlay.window import *
from PPlay.gameimage import *
from PPlay.keyboard import *
from PPlay.sprite import *
# DEFINIÇÕES INICIAIS
# Variáveis
posxDog = 100
posyDog = 320
velxDog = 250
dogState = 'parado-direita'
# Objetos
janela = Window(700,500)
cenario = GameImage("cenario.webp")
teclado = Keyboard()
dogStoppedRight = Sprite("dogStoppedRight.webp", 6)
dogStoppedRight.set_sequence_time(0, 6, 400, True)
dogStoppedRight.x = posxDog
dogStoppedRight.y = posyDog
dogWalkRight = Sprite("dogWalkRight.webp", 6)
dogWalkRight.set_sequence_time(0, 6, 100, True)
dogWalkRight.x = posxDog
dogWalkRight.y = posyDog
def movimentoDog():
dogStoppedRight.x = posxDog
dogWalkRight.x = posxDog
def desenhoDog():
match dogState:
case 'parado-direita':
dogStoppedRight.update()
dogStoppedRight.draw()
case 'andando-direita':
dogWalkRight.update()
dogWalkRight.draw()
while True:
# ATUALIZAÇÃO DE VARIÁVEIS
if teclado.key_pressed('RIGHT'):
dogState = 'andando-direita'
posxDog += velxDog * janela.delta_time()
else:
dogState = 'parado-direita'
movimentoDog()
# DESENHO E ATUALIZAÇÃO
cenario.draw()
desenhoDog()
janela.update()
O Gameloop do código está bem mais enxuto e organizado. Isso é importante para continuarmos seguindo nossa programação sem nos perdermos no código. Além disso é possível verificar que como a posição do cão é atualizada antes de desenhar, não precisamos definir ela no início do código. Basta adicionar a definição da posição y dentro da função de movimento. Apagaremos então as linhas em vermelho e configuraremos os próximos sprites com apenas 2 linhas de código para cada sprite. Vamos fazer o cão de mover também para a esquerda instanciando mais 2 sprites. Além disso, vamos definir que se o cão está virado para a direita (andando ou parado) ele inicia o GameLoop parado para a direita, e se ele estiver virado para a esquerda (andando ou parado) ele inicia o GameLoop parado para a esquerda, e atualizamos este estado caso alguma tecla seja pressionada. Atualizamos também as funções com esses novos sprites.
from PPlay.window import *
from PPlay.gameimage import *
from PPlay.keyboard import *
from PPlay.sprite import *
# DEFINIÇÕES INICIAIS
# Variáveis
posxDog = 100
posyDog = 320
velxDog = 250
dogState = 'parado-direita'
# Objetos
janela = Window(700,500)
cenario = GameImage("cenario.webp")
teclado = Keyboard()
dogStoppedRight = Sprite("dogStoppedRight.webp", 6)
dogStoppedRight.set_sequence_time(0, 6, 400, True)
dogWalkRight = Sprite("dogWalkRight.webp", 6)
dogWalkRight.set_sequence_time(0, 6, 100, True)
dogStoppedLeft = Sprite("dogStoppedLeft.webp", 6)
dogStoppedLeft.set_sequence_time(0, 6, 400, True)
dogWalkLeft = Sprite("dogWalkLeft.webp", 6)
dogWalkLeft.set_sequence_time(0, 6, 100, True)
def movimentoDog():
dogStoppedRight.x = posxDog
dogStoppedRight.y = posyDog
dogWalkRight.x = posxDog
dogWalkRight.y = posyDog
dogStoppedLeft.x = posxDog
dogStoppedLeft.y = posyDog
dogWalkLeft.x = posxDog
dogWalkLeft.y = posyDog
def desenhoDog():
match dogState:
case 'parado-direita':
dogStoppedRight.update()
dogStoppedRight.draw()
case 'andando-direita':
dogWalkRight.update()
dogWalkRight.draw()
case 'parado-esquerda':
dogStoppedLeft.update()
dogStoppedLeft.draw()
case 'andando-esquerda':
dogWalkLeft.update()
dogWalkLeft.draw()
while True:
# ATUALIZAÇÃO DE VARIÁVEIS
if dogState == 'andando-direita' or dogState == 'parado-direita':
dogState = 'parado-direita'
if dogState == 'andando-esquerda' or dogState == 'parado-esquerda':
dogState = 'parado-esquerda'
if teclado.key_pressed('RIGHT'):
dogState = 'andando-direita'
posxDog += velxDog * janela.delta_time()
elif teclado.key_pressed('LEFT'):
dogState = 'andando-esquerda'
posxDog -= velxDog * janela.delta_time()
movimentoDog()
# DESENHO E ATUALIZAÇÃO
cenario.draw()
desenhoDog()
janela.update()
Neste ponto é interessante fazer 2 comentários. Um diz respeito do uso do “elif” no código. Ele indica que há uma preferência do cão andar para a direita caso as teclas de direita e esquerda sejam pressionadas juntas. Assim, o cão irá se movimentar de fato para a direita. Caso tivéssemos usado apenas o “if” o estado do cão se atualizaria para “andando-esqueda” ao final desse conjunto de condicionais, mas a sua posição se manteria a mesma, dando a impressão que o cão está andando em uma esteira, sem sair do lugar.
O outro comentário diz respeito ao udo do delta_time(). Como usaremos essa função de maneira mais frequente no código, é interessante chamá-la uma única vez no início do loop, armazenando o valor do tempo em uma variável e usando esta variável quando necessário. Isso reduz o processamento durante a execução do jogo, melhorando a eficiência do código.
Salto
Para implementar o salto do cão precisamos fazer várias alterações no código, além de implementar equações da física de um corpo em queda livre (para fazermos o movimento parabólico da queda do cão.
from PPlay.window import *
from PPlay.gameimage import *
from PPlay.keyboard import *
from PPlay.sprite import *
# DEFINIÇÕES INICIAIS
# Variáveis
posyInicial = 320
posxDog = 100
posyDog = posyInicial
velxDog = 250
dogState = 'parado-direita'
velInicialSalto = -400
velyDog = 0
grav = 700
# Objetos
janela = Window(700,500)
cenario = GameImage("cenario.webp")
teclado = Keyboard()
dogStoppedRight = Sprite("dogStoppedRight.webp", 6)
dogStoppedRight.set_sequence_time(0, 6, 400, True)
dogStoppedLeft = Sprite("dogStoppedLeft.webp", 6)
dogStoppedLeft.set_sequence_time(0, 6, 400, True)
dogWalkRight = Sprite("dogWalkRight.webp", 6)
dogWalkRight.set_sequence_time(0, 6, 100, True)
dogWalkLeft = Sprite("dogWalkLeft.webp", 6)
dogWalkLeft.set_sequence_time(0, 6, 100, True)
dogJumpRight = Sprite("dogJumpRight.webp", 5)
dogJumpRight.set_sequence_time(0, 4, 100, False)
dogJumpLeft = Sprite("dogJumpLeft.webp", 5)
dogJumpLeft.set_sequence_time(0, 4, 100, False)
def movimentoDog():
dogStoppedRight.x = posxDog
dogStoppedRight.y = posyDog
dogStoppedLeft.x = posxDog
dogStoppedLeft.y = posyDog
dogWalkRight.x = posxDog
dogWalkRight.y = posyDog
dogWalkLeft.x = posxDog
dogWalkLeft.y = posyDog
dogJumpRight.x = posxDog
dogJumpRight.y = posyDog
dogJumpLeft.x = posxDog
dogJumpLeft.y = posyDog
def desenhoDog():
match dogState:
case 'parado-direita':
dogStoppedRight.update()
dogStoppedRight.draw()
case 'andando-direita':
dogWalkRight.update()
dogWalkRight.draw()
case 'parado-esquerda':
dogStoppedLeft.update()
dogStoppedLeft.draw()
case 'andando-esquerda':
dogWalkLeft.update()
dogWalkLeft.draw()
case 'saltando-direita':
dogJumpRight.update()
dogJumpRight.draw()
case 'saltando-esquerda':
dogJumpLeft.update()
dogJumpLeft.draw()
while True:
deltatempo = janela.delta_time()
# ATUALIZAÇÃO DE VARIÁVEIS
if dogState == 'andando-direita' or dogState == 'parado-direita':
dogState = 'parado-direita'
if dogState == 'andando-esquerda' or dogState == 'parado-esquerda':
dogState = 'parado-esquerda'
if teclado.key_pressed('RIGHT'):
posxDog += velxDog * deltatempo
if not (dogState == 'saltando-direita' or dogState == 'saltando-esquerda'):
dogState = 'andando-direita'
elif teclado.key_pressed('LEFT'):
posxDog -= velxDog * deltatempo
if not (dogState == 'saltando-direita' or dogState == 'saltando-esquerda'):
dogState = 'andando-esquerda'
if teclado.key_pressed('SPACE'):
if not dogState == 'saltando-direita' and not dogState == 'saltando-esquerda':
velyDog = velInicialSalto # Apenas na primeira entrada do condicional que avalia a tecla "SPACE" pressionada
if (dogState == 'parado-direita' or dogState == 'andando-direita'):
dogState = 'saltando-direita'
elif(dogState == 'parado-esquerda' or dogState == 'andando-esquerda'):
dogState = 'saltando-esquerda'
if dogState == 'saltando-direita' or dogState == 'saltando-esquerda':
# Queda Livre
posyDog += velyDog * deltatempo + (grav * (deltatempo ** 2) ) / 2
velyDog += grav * deltatempo
if posyDog > posyInicial:
posyDog = posyInicial
velyDog = 0
if dogState == 'saltando-direita':
dogState = 'parado-direita'
elif dogState == 'saltando-esquerda':
dogState = 'parado-esquerda'
movimentoDog()
# DESENHO E ATUALIZAÇÃO
cenario.draw()
desenhoDog()
janela.update()
As primeiras linhas em amarelo dizem respeito a física do movimento do cão. Temos que definir uma posição y inicial que representa a altura que a imagem do cão fica quando ele está apoiado no chão, e definimos também uma velocidade com a qual ele inicia o o salto ( será um valor fixo e negativo pois os valores de y diminuem no sentido para cima). Também definimos uma velocidade vertical para o cachorro (que inicia com o valor que definimos para o início do salto e irá atualizar ao longo de sua queda livre), e um valor pra gravidade que pode ser calibrada posteriormente.
Instanciamos e configuramos os sprites de salto antes do GameLoop e definimos seu movimento e implementamos seu desenho dentro das funções específicas para estes fins que serão chamadas no GameLoop.
Agora precisamos fazer algumas alterações para entrarmos corretamente no condicional que altera o estado do cachorro quado as teclas “RIGHT” ou “LEFT” são pressionadas. Se o cão está pulando, continuamdo querendo que a posição horizontal do cão se altere, mas não queremos que a imagem desenhada da tela seja de um cachorro andando, já que ele está pulando.
Quando a tecla “SPACE” é pressionada, então queremos alterar a velocidade vertical do cão apenas na primeira entrada deste condicional (caso contrário o cão irá subir enquanto a tecla space estiver sendo pressionada). Assim, alteramos a velocidade apenas se o seu estado não for pulando. Alteramos então o estado do cão para pulando e executamos a queda livre sempre que ele estiver nesse estado.
O cão sai da queda livre quando ele atinge o chão, ou seja, quando atinge o y inicial. É importante reposicionar o cão corretamente pois ele pode atingir um y maior que o inicial, e resetamos também sua velocidade e estado.
Aqui temos que fazer três comentários. O primeiro diz respeito ao sprite do salto. Ele tem 5 frames, mas paramos no quarto frame. Isso foi feito porque esse é o frame em que o cão está voando no ar, e o último frame deve ser usado apenas para o pouso. Além disso, apenas o primeiro salto mostra o cão pegando impulso, e o restante apenas usa o quarto frame. O terceiro comentário é que o cão não está virando no ar quando mudamos a direção durante o salto. Vamos focar em resolver estes três problemas agora.
from PPlay.window import *
from PPlay.gameimage import *
from PPlay.keyboard import *
from PPlay.sprite import *
# DEFINIÇÕES INICIAIS
# Variáveis
posyInicial = 320
posxDog = 100
posyDog = posyInicial
velxDog = 250
dogState = 'parado-direita'
velInicialSalto = -400
velyDog = 0
grav = 700
disparaTemporizadorPouso = False
tempoPouso = 0
# Objetos
janela = Window(700,500)
cenario = GameImage("cenario.webp")
teclado = Keyboard()
dogStoppedRight = Sprite("dogStoppedRight.webp", 6)
dogStoppedRight.set_sequence_time(0, 6, 400, True)
dogStoppedLeft = Sprite("dogStoppedLeft.webp", 6)
dogStoppedLeft.set_sequence_time(0, 6, 400, True)
dogWalkRight = Sprite("dogWalkRight.webp", 6)
dogWalkRight.set_sequence_time(0, 6, 100, True)
dogWalkLeft = Sprite("dogWalkLeft.webp", 6)
dogWalkLeft.set_sequence_time(0, 6, 100, True)
dogJumpRight = Sprite("dogJumpRight.webp", 5)
dogJumpRight.set_sequence_time(0, 4, 100, False)
dogJumpLeft = Sprite("dogJumpLeft.webp", 5)
dogJumpLeft.set_sequence_time(0, 4, 100, False)
def movimentoDog():
dogStoppedRight.x = posxDog
dogStoppedRight.y = posyDog
dogStoppedLeft.x = posxDog
dogStoppedLeft.y = posyDog
dogWalkRight.x = posxDog
dogWalkRight.y = posyDog
dogWalkLeft.x = posxDog
dogWalkLeft.y = posyDog
dogJumpRight.x = posxDog
dogJumpRight.y = posyDog
dogJumpLeft.x = posxDog
dogJumpLeft.y = posyDog
def desenhoDog():
match dogState:
case 'parado-direita':
dogStoppedRight.update()
dogStoppedRight.draw()
case 'andando-direita':
dogWalkRight.update()
dogWalkRight.draw()
case 'parado-esquerda':
dogStoppedLeft.update()
dogStoppedLeft.draw()
case 'andando-esquerda':
dogWalkLeft.update()
dogWalkLeft.draw()
case 'saltando-direita':
dogJumpRight.update()
dogJumpRight.draw()
case 'saltando-esquerda':
dogJumpLeft.update()
dogJumpLeft.draw()
while True:
# ATUALIZAÇÃO DE VARIÁVEIS
deltatempo = janela.delta_time()
if dogState == 'andando-direita' or dogState == 'parado-direita':
dogState = 'parado-direita'
if dogState == 'andando-esquerda' or dogState == 'parado-esquerda':
dogState = 'parado-esquerda'
if teclado.key_pressed('RIGHT'):
posxDog += velxDog * deltatempo
if not (dogState == 'saltando-direita' or dogState == 'saltando-esquerda'):
dogState = 'andando-direita'
if dogState == 'saltando-esquerda':
dogState = 'saltando-direita'
dogJumpLeft.stop()
dogJumpRight.set_sequence_time(3, 4, 200, False)
elif teclado.key_pressed('LEFT'):
posxDog -= velxDog * deltatempo
if not (dogState == 'saltando-direita' or dogState == 'saltando-esquerda'):
dogState = 'andando-esquerda'
if dogState == 'saltando-direita':
dogState = 'saltando-esquerda'
dogJumpRight.stop()
dogJumpLeft.set_sequence_time(3, 4, 200, False)
if teclado.key_pressed('SPACE'):
if not dogState == 'saltando-direita' and not dogState == 'saltando-esquerda':
velyDog = velInicialSalto
if (dogState == 'parado-direita' or dogState == 'andando-direita'):
dogState = 'saltando-direita'
elif(dogState == 'parado-esquerda' or dogState == 'andando-esquerda'):
dogState = 'saltando-esquerda'
if dogState == 'saltando-direita' or dogState == 'saltando-esquerda':
# Queda Livre
posyDog += velyDog * deltatempo + (grav * (deltatempo ** 2) ) / 2
velyDog += grav * deltatempo
dogJumpRight.play()
dogJumpLeft.play()
if posyDog > posyInicial:
posyDog = posyInicial
velyDog = 0
dogJumpLeft.set_sequence_time(4, 5, 100, False)
dogJumpRight.set_sequence_time(4, 5, 100, False)
disparaTemporizadorPouso = True
if disparaTemporizadorPouso:
tempoPouso += deltatempo
if tempoPouso > 0.1:
tempoPouso = 0
disparaTemporizadorPouso = False
dogJumpLeft.set_sequence_time(0, 4, 100, False)
dogJumpRight.set_sequence_time(0, 4, 100, False)
if dogState == 'saltando-direita':
dogState = 'parado-direita'
if dogState == 'saltando-esquerda':
dogState = 'parado-esquerda'
movimentoDog()
# DESENHO E ATUALIZAÇÃO
cenario.draw()
desenhoDog()
janela.update()
O temporizador declarado no início do código irá disparar quando o cão voltar a tocar o solo após o salto. Neste momento o sprite de pouso será executado em um intervalo de tempo definido no final do código (0.1 s). Após esse tempo resetamos o sprite, o estado do cão, as variáveis do temporizador e o sprite de salto.
Dentro dos condicionais que avaliam a direção do movimento do cão adicionamos um condicional que permite que o sprite mude de direção durante o salto e evitamos que toda sequencia de pegar impulso seja executada novamente configurando o sprite no frame do vôo. Além disso, configuramos o sprite para realizar a animação do pulo novamente através da função “play()”. Isso é importante após reconfigurar os frames que precisam ser exibidos.
Câmera Dinâmica
Vamos impedir que o cão se mova e fazer o cenário se mover no sentido oposto para dar a impressão que a câmera está acompanhando o cachorro. Quando o cão chegar no final da janela, o cenário para de se mover e o cão se move pela janela.
from PPlay.window import *
from PPlay.gameimage import *
from PPlay.keyboard import *
from PPlay.sprite import *
# DEFINIÇÕES INICIAIS
# Variáveis
dogState = 'parado-direita'
posxDog = 100
posyInicial = 320
posyDog = posyInicial
velyDog = 0
disparaTemporizadorPouso = False
tempoPouso = 0
posxCamera = 0
# Calibração
velxDog = 250
velInicialSalto = -400
grav = 700
# Objetos
janela = Window(700,500)
cenario = GameImage("cenario.webp")
teclado = Keyboard()
dogStoppedRight = Sprite("dogStoppedRight.webp", 6)
dogStoppedRight.set_sequence_time(0, 6, 400, True)
dogStoppedLeft = Sprite("dogStoppedLeft.webp", 6)
dogStoppedLeft.set_sequence_time(0, 6, 400, True)
dogWalkRight = Sprite("dogWalkRight.webp", 6)
dogWalkRight.set_sequence_time(0, 6, 100, True)
dogWalkLeft = Sprite("dogWalkLeft.webp", 6)
dogWalkLeft.set_sequence_time(0, 6, 100, True)
dogJumpRight = Sprite("dogJumpRight.webp", 5)
dogJumpRight.set_sequence_time(0, 4, 100, False)
dogJumpLeft = Sprite("dogJumpLeft.webp", 5)
dogJumpLeft.set_sequence_time(0, 4, 100, False)
def movimentoCamera():
cenario.x = posxCamera
def movimentoDog():
dogStoppedRight.x = posxDog
dogStoppedRight.y = posyDog
dogStoppedLeft.x = posxDog
dogStoppedLeft.y = posyDog
dogWalkRight.x = posxDog
dogWalkRight.y = posyDog
dogWalkLeft.x = posxDog
dogWalkLeft.y = posyDog
dogJumpRight.x = posxDog
dogJumpRight.y = posyDog
dogJumpLeft.x = posxDog
dogJumpLeft.y = posyDog
def desenhoDog():
match dogState:
case 'parado-direita':
dogStoppedRight.update()
dogStoppedRight.draw()
case 'andando-direita':
dogWalkRight.update()
dogWalkRight.draw()
case 'parado-esquerda':
dogStoppedLeft.update()
dogStoppedLeft.draw()
case 'andando-esquerda':
dogWalkLeft.update()
dogWalkLeft.draw()
case 'saltando-direita':
dogJumpRight.update()
dogJumpRight.draw()
case 'saltando-esquerda':
dogJumpLeft.update()
dogJumpLeft.draw()
while True:
# ATUALIZAÇÃO DE VARIÁVEIS
deltatempo = janela.delta_time()
if dogState == 'andando-direita' or dogState == 'parado-direita':
dogState = 'parado-direita'
if dogState == 'andando-esquerda' or dogState == 'parado-esquerda':
dogState = 'parado-esquerda'
if teclado.key_pressed('RIGHT'):
if posxCamera > janela.width - cenario.width:
posxCamera -= velxDog * deltatempo
else:
posxDog += velxDog * deltatempo
if not (dogState == 'saltando-direita' or dogState == 'saltando-esquerda'):
dogState = 'andando-direita'
if dogState == 'saltando-esquerda':
dogState = 'saltando-direita'
dogJumpLeft.stop()
dogJumpRight.set_sequence_time(3, 4, 200, False)
elif teclado.key_pressed('LEFT'):
if posxCamera < 0:
posxCamera += velxDog * deltatempo
else:
posxDog -= velxDog * deltatempo
if not (dogState == 'saltando-direita' or dogState == 'saltando-esquerda'):
dogState = 'andando-esquerda'
if dogState == 'saltando-direita':
dogState = 'saltando-esquerda'
dogJumpRight.stop()
dogJumpLeft.set_sequence_time(3, 4, 200, False)
# Salto
if teclado.key_pressed('SPACE'):
if not dogState == 'saltando-direita' and not dogState == 'saltando-esquerda':
velyDog = velInicialSalto
if (dogState == 'parado-direita' or dogState == 'andando-direita'):
dogState = 'saltando-direita'
elif(dogState == 'parado-esquerda' or dogState == 'andando-esquerda'):
dogState = 'saltando-esquerda'
if dogState == 'saltando-direita' or dogState == 'saltando-esquerda':
# Queda Livre
posyDog += velyDog * deltatempo + (grav * (deltatempo ** 2) ) / 2
velyDog += grav * deltatempo
dogJumpRight.play()
dogJumpLeft.play()
if posyDog > posyInicial:
posyDog = posyInicial
velyDog = 0
dogJumpLeft.set_sequence_time(4, 5, 100, False)
dogJumpRight.set_sequence_time(4, 5, 100, False)
disparaTemporizadorPouso = True
if disparaTemporizadorPouso:
tempoPouso += deltatempo
if tempoPouso > 0.1:
tempoPouso = 0
disparaTemporizadorPouso = False
dogJumpLeft.set_sequence_time(0, 4, 100, False)
dogJumpRight.set_sequence_time(0, 4, 100, False)
if dogState == 'saltando-direita':
dogState = 'parado-direita'
if dogState == 'saltando-esquerda':
dogState = 'parado-esquerda'
movimentoDog()
movimentoCamera()
# DESENHO E ATUALIZAÇÃO
cenario.draw()
desenhoDog()
janela.update()
Agora temos um problema. Quando o cão anda até sair do cenário e pressionamos a tecla para ir para o lado oposto, o cão não retoma a sua posição inicial antes do cenário começar a andar novamente. Além disso temos que implementar melhorias no posicionamento da câmera para que seja enfatizado o que está na frente do cão.
from PPlay.window import *
from PPlay.gameimage import *
from PPlay.keyboard import *
from PPlay.sprite import *
# DEFINIÇÕES INICIAIS
# Variáveis
dogState = 'parado-direita'
posxDog = 100
posyInicial = 320
posyDog = posyInicial
velyDog = 0
disparaTemporizadorPouso = False
tempoPouso = 0
posxCamera = 0
AtivarPosCamDir = False
AtivarPosCamEsq = False
# Calibração
velxDog = 250
velInicialSalto = -400
grav = 700
velxPosCam = 1000
# Objetos
janela = Window(700,500)
cenario = GameImage("cenario.webp")
teclado = Keyboard()
dogStoppedRight = Sprite("dogStoppedRight.webp", 6)
dogStoppedRight.set_sequence_time(0, 6, 400, True)
dogStoppedLeft = Sprite("dogStoppedLeft.webp", 6)
dogStoppedLeft.set_sequence_time(0, 6, 400, True)
dogWalkRight = Sprite("dogWalkRight.webp", 6)
dogWalkRight.set_sequence_time(0, 6, 100, True)
dogWalkLeft = Sprite("dogWalkLeft.webp", 6)
dogWalkLeft.set_sequence_time(0, 6, 100, True)
dogJumpRight = Sprite("dogJumpRight.webp", 5)
dogJumpRight.set_sequence_time(0, 4, 100, False)
dogJumpLeft = Sprite("dogJumpLeft.webp", 5)
dogJumpLeft.set_sequence_time(0, 4, 100, False)
def movimentoCamera():
cenario.x = posxCamera
def movimentoDog():
dogStoppedRight.x = posxDog
dogStoppedRight.y = posyDog
dogStoppedLeft.x = posxDog
dogStoppedLeft.y = posyDog
dogWalkRight.x = posxDog
dogWalkRight.y = posyDog
dogWalkLeft.x = posxDog
dogWalkLeft.y = posyDog
dogJumpRight.x = posxDog
dogJumpRight.y = posyDog
dogJumpLeft.x = posxDog
dogJumpLeft.y = posyDog
def desenhoDog():
match dogState:
case 'parado-direita':
dogStoppedRight.update()
dogStoppedRight.draw()
case 'andando-direita':
dogWalkRight.update()
dogWalkRight.draw()
case 'parado-esquerda':
dogStoppedLeft.update()
dogStoppedLeft.draw()
case 'andando-esquerda':
dogWalkLeft.update()
dogWalkLeft.draw()
case 'saltando-direita':
dogJumpRight.update()
dogJumpRight.draw()
case 'saltando-esquerda':
dogJumpLeft.update()
dogJumpLeft.draw()
while True:
# ATUALIZAÇÃO DE VARIÁVEIS
deltatempo = janela.delta_time()
if dogState == 'andando-direita' or dogState == 'parado-direita':
dogState = 'parado-direita'
if dogState == 'andando-esquerda' or dogState == 'parado-esquerda':
dogState = 'parado-esquerda'
if teclado.key_pressed('RIGHT'):
# Se o cão ta antes da posição 100px, coloca o cão na posição 100px
if posxDog < 100:
posxDog += velxDog * deltatempo
# Se a câmera não chegou no fim, rola a câmera
if posxDog >= 100 and posxCamera > janela.width - cenario.width:
posxCamera -= velxDog * deltatempo
# Se chegou no fim do cenário e o cão não saiu do cenário, o cão pode andar
if posxCamera <= janela.width - cenario.width and posxDog < 700 - dogWalkRight.width:
posxDog += velxDog * deltatempo
if not (dogState == 'saltando-direita' or dogState == 'saltando-esquerda'):
dogState = 'andando-direita'
if dogState == 'saltando-esquerda':
dogState = 'saltando-direita'
dogJumpLeft.stop()
dogJumpRight.set_sequence_time(3, 4, 200, False)
elif teclado.key_pressed('LEFT'):
# Se o cão ta depois de 500px, posiciona ele em 500px
if posxDog > 500:
posxDog -= velxDog * deltatempo
# Se a câmera não cheogu no início do cenário, rola a câmera
if posxDog <= 500 and posxCamera < 0:
posxCamera += velxDog * deltatempo
# Se chegou no fim do cenário e o cão não saiu do cenário, o cão pode andar
if posxCamera >= 0 and posxDog > 0:
posxDog -= velxDog * deltatempo
if not (dogState == 'saltando-direita' or dogState == 'saltando-esquerda'):
dogState = 'andando-esquerda'
if dogState == 'saltando-direita':
dogState = 'saltando-esquerda'
dogJumpRight.stop()
dogJumpLeft.set_sequence_time(3, 4, 200, False)
# Salto
if teclado.key_pressed('SPACE'):
if not dogState == 'saltando-direita' and not dogState == 'saltando-esquerda':
velyDog = velInicialSalto
if (dogState == 'parado-direita' or dogState == 'andando-direita'):
dogState = 'saltando-direita'
elif(dogState == 'parado-esquerda' or dogState == 'andando-esquerda'):
dogState = 'saltando-esquerda'
if dogState == 'saltando-direita' or dogState == 'saltando-esquerda':
# Queda Livre
posyDog += velyDog * deltatempo + (grav * (deltatempo ** 2) ) / 2
velyDog += grav * deltatempo
dogJumpRight.play()
dogJumpLeft.play()
if posyDog > posyInicial:
posyDog = posyInicial
velyDog = 0
dogJumpLeft.set_sequence_time(4, 5, 100, False)
dogJumpRight.set_sequence_time(4, 5, 100, False)
disparaTemporizadorPouso = True
if disparaTemporizadorPouso:
tempoPouso += deltatempo
if tempoPouso > 0.1:
tempoPouso = 0
disparaTemporizadorPouso = False
dogJumpLeft.set_sequence_time(0, 4, 100, False)
dogJumpRight.set_sequence_time(0, 4, 100, False)
if dogState == 'saltando-direita':
dogState = 'parado-direita'
if dogState == 'saltando-esquerda':
dogState = 'parado-esquerda'
# Movimentação da Câmera
if (dogState == 'andando-direita' or dogState == 'parado-direita' or dogState == 'saltando-direita') and posxDog > 100 and posxCamera > janela.width - cenario.width:
AtivarPosCamDir = True
if AtivarPosCamDir:
posxDog -= velxPosCam * deltatempo
posxCamera -= velxPosCam * deltatempo
if posxDog < 100 or posxCamera <= janela.width - cenario.width:
AtivarPosCamDir = False
if (dogState == 'andando-esquerda' or dogState == 'parado-esquerda' or dogState == 'saltando-esquerda') and posxDog < 500 and posxCamera < 0:
AtivarPosCamEsq = True
if AtivarPosCamEsq:
posxDog += velxPosCam * deltatempo
posxCamera += velxPosCam * deltatempo
if posxDog > 500 or posxCamera >= 0:
AtivarPosCamEsq = False
movimentoDog()
movimentoCamera()
# DESENHO E ATUALIZAÇÃO
cenario.draw()
desenhoDog()
janela.update()
Neste código adicionamos o movimento dinâmico da câmera de acordo com como o cão está na tela. Se ele estiver virado para direita, mas posicionado muito à esquerda da tela (mais que 100px) e se ainda tem cenário para mostrar no lado direito, então a câmera se movimentará para mostrar esse cenário. O movimento da câmera cessa quando o cão atinge os 100px ou quando o cenário acaba. Caso a condição de parada seja de acordo com a posição do cachorro, reposicionamos ele na tela para evitar escorregamento.
Os condicionais que definem como a câmera e o cão devem se movimentar foi redefinida e destacada em amarelo. Os comentários explicam o que está sendo testado em cada um dos 3 condicionais.
A essa altura você deve estar se perguntando por que temos uma função separada com uma única linha para fazer o movimento do cenário? O objetivo de manter esta função é que podemos adicionar elementos ao cenário que estão em uma camada acima. Percebemos por exemplo que quando movemos o cão para a esquerda ele fica na frente da árvore, dando um efeito visual ruim. Vamos corrigir isso.
from PPlay.window import *
from PPlay.gameimage import *
from PPlay.keyboard import *
from PPlay.sprite import *
# DEFINIÇÕES INICIAIS
# Variáveis
dogState = 'parado-direita'
posxDog = 100
posyInicial = 320
posyDog = posyInicial
velyDog = 0
disparaTemporizadorPouso = False
tempoPouso = 0
posxCamera = 0
AtivarPosCamDir = False
AtivarPosCamEsq = False
# Calibração
velxDog = 250
velInicialSalto = -400
grav = 700
velxPosCam = 1000
# Objetos
janela = Window(700,500)
cenario = GameImage("cenario.webp")
arvore = GameImage("arvore.webp")
cobra = Sprite("snake-sprite.png", 4)
cobra.set_sequence_time(0, 4, 400, True)
cobra.y = 360
teclado = Keyboard()
dogStoppedRight = Sprite("dogStoppedRight.webp", 6)
dogStoppedRight.set_sequence_time(0, 6, 400, True)
dogStoppedLeft = Sprite("dogStoppedLeft.webp", 6)
dogStoppedLeft.set_sequence_time(0, 6, 400, True)
dogWalkRight = Sprite("dogWalkRight.webp", 6)
dogWalkRight.set_sequence_time(0, 6, 100, True)
dogWalkLeft = Sprite("dogWalkLeft.webp", 6)
dogWalkLeft.set_sequence_time(0, 6, 100, True)
dogJumpRight = Sprite("dogJumpRight.webp", 5)
dogJumpRight.set_sequence_time(0, 4, 100, False)
dogJumpLeft = Sprite("dogJumpLeft.webp", 5)
dogJumpLeft.set_sequence_time(0, 4, 100, False)
dogDieRight = Sprite("dogDieRight.webp", 7)
dogDieRight.set_sequence_time(0, 7, 100, False)
dogDieLeft = Sprite("dogDieLeft.webp", 7)
dogDieLeft.set_sequence_time(0, 7, 100, False)
def movimentoCamera():
cenario.x = posxCamera
arvore.x = posxCamera
cobra.x = posxCamera + 800
def movimentoDog():
dogStoppedRight.x = posxDog
dogStoppedRight.y = posyDog
dogStoppedLeft.x = posxDog
dogStoppedLeft.y = posyDog
dogWalkRight.x = posxDog
dogWalkRight.y = posyDog
dogWalkLeft.x = posxDog
dogWalkLeft.y = posyDog
dogJumpRight.x = posxDog
dogJumpRight.y = posyDog
dogJumpLeft.x = posxDog
dogJumpLeft.y = posyDog
dogDieRight.x = posxDog
dogDieRight.y = posyDog
dogDieLeft.x = posxDog
dogDieLeft.y = posyDog
def desenhoDog():
match dogState:
case 'parado-direita':
dogStoppedRight.update()
dogStoppedRight.draw()
case 'andando-direita':
dogWalkRight.update()
dogWalkRight.draw()
case 'parado-esquerda':
dogStoppedLeft.update()
dogStoppedLeft.draw()
case 'andando-esquerda':
dogWalkLeft.update()
dogWalkLeft.draw()
case 'saltando-direita':
dogJumpRight.update()
dogJumpRight.draw()
case 'saltando-esquerda':
dogJumpLeft.update()
dogJumpLeft.draw()
case 'morrendo-direita':
dogDieRight.update()
dogDieRight.draw()
case 'morrendo-esquerda':
dogDieLeft.update()
dogDieLeft.draw()
while True:
# ATUALIZAÇÃO DE VARIÁVEIS
deltatempo = janela.delta_time()
if dogState == 'andando-direita' or dogState == 'parado-direita':
dogState = 'parado-direita'
if dogState == 'andando-esquerda' or dogState == 'parado-esquerda':
dogState = 'parado-esquerda'
if teclado.key_pressed('RIGHT') and not (dogState == 'morrendo-direita' or dogState == 'morrendo-esquerda'):
if posxDog < 100:
posxDog += velxDog * deltatempo
if posxDog >= 100 and posxCamera > janela.width - cenario.width:
posxCamera -= velxDog * deltatempo
if posxCamera <= janela.width - cenario.width and posxDog < 700 - dogWalkRight.width:
posxDog += velxDog * deltatempo
if not (dogState == 'saltando-direita' or dogState == 'saltando-esquerda'):
dogState = 'andando-direita'
if dogState == 'saltando-esquerda':
dogState = 'saltando-direita'
dogJumpLeft.stop()
dogJumpRight.set_sequence_time(3, 4, 200, False)
elif teclado.key_pressed('LEFT') and not (dogState == 'morrendo-direita' or dogState == 'morrendo-esquerda'):
if posxDog > 500:
posxDog -= velxDog * deltatempo
if posxDog <= 500 and posxCamera < 0:
posxCamera += velxDog * deltatempo
if posxCamera >= 0 and posxDog > 0:
posxDog -= velxDog * deltatempo
if not (dogState == 'saltando-direita' or dogState == 'saltando-esquerda'):
dogState = 'andando-esquerda'
if dogState == 'saltando-direita':
dogState = 'saltando-esquerda'
dogJumpRight.stop()
dogJumpLeft.set_sequence_time(3, 4, 200, False)
# Salto
if teclado.key_pressed('SPACE'):
if not dogState == 'saltando-direita' and not dogState == 'saltando-esquerda':
velyDog = velInicialSalto
if (dogState == 'parado-direita' or dogState == 'andando-direita'):
dogState = 'saltando-direita'
elif(dogState == 'parado-esquerda' or dogState == 'andando-esquerda'):
dogState = 'saltando-esquerda'
if dogState == 'saltando-direita' or dogState == 'saltando-esquerda' or dogState == 'morrendo-direita' or dogState == 'morrendo-esquerda':
# Queda Livre
posyDog += velyDog * deltatempo + (grav * (deltatempo ** 2) ) / 2
velyDog += grav * deltatempo
dogJumpRight.play()
dogJumpLeft.play()
if posyDog > posyInicial:
posyDog = posyInicial
velyDog = 0
dogJumpLeft.set_sequence_time(4, 5, 100, False)
dogJumpRight.set_sequence_time(4, 5, 100, False)
disparaTemporizadorPouso = True
if disparaTemporizadorPouso:
tempoPouso += deltatempo
if tempoPouso > 0.1:
tempoPouso = 0
disparaTemporizadorPouso = False
dogJumpLeft.set_sequence_time(0, 4, 100, False)
dogJumpRight.set_sequence_time(0, 4, 100, False)
if dogState == 'saltando-direita':
dogState = 'parado-direita'
if dogState == 'saltando-esquerda':
dogState = 'parado-esquerda'
# Movimentação da Câmera
if (dogState == 'andando-direita' or dogState == 'parado-direita' or dogState == 'saltando-direita') and posxDog > 100 and posxCamera > janela.width - cenario.width:
AtivarPosCamDir = True
if AtivarPosCamDir:
posxDog -= velxPosCam * deltatempo
posxCamera -= velxPosCam * deltatempo
if posxDog < 100 or posxCamera <= janela.width - cenario.width:
AtivarPosCamDir = False
if posxDog < 100: # Evitar Escorregamento
posxDog = 100
if (dogState == 'andando-esquerda' or dogState == 'parado-esquerda' or dogState == 'saltando-esquerda') and posxDog < 500 and posxCamera < 0:
AtivarPosCamEsq = True
if AtivarPosCamEsq:
posxDog += velxPosCam * deltatempo
posxCamera += velxPosCam * deltatempo
if posxDog > 500 or posxCamera >= 0:
AtivarPosCamEsq = False
if posxDog > 500: # Evitar Escorregamento
posxDog = 500
if posxDog + dogStoppedRight.width - 30 > cobra.x and posxDog + 30 < cobra.x + cobra.width and posyDog + dogStoppedRight.height - 30 > cobra.y:
if (dogState == 'andando-esquerda' or dogState == 'parado-esquerda' or dogState == 'saltando-esquerda'):
dogState = 'morrendo-esquerda'
if (dogState == 'andando-direita' or dogState == 'parado-direita' or dogState == 'saltando-direita'):
dogState = 'morrendo-direita'
movimentoDog()
movimentoCamera()
# DESENHO E ATUALIZAÇÃO
cenario.draw()
desenhoDog()
cobra.update()
cobra.draw()
arvore.draw()
janela.update()
Além dessa vantagem, agora também podemos adicionar inimigos e fazer a movimentação deles junto com o cenário nesta função. Repare que nada impede que alem de se mover com o cenário, o inimigo também tenha um movimento próprio, bastando adicionar valores aos seus atributos x e y quando implementar sua movimentação.
No código adicionamos o sprite que faz o cão morrer e adicionamos uma cobra. Se o cão encostar na cobra ele morre, então definimos um retângulo em torno do cão que não pode sobrepor ao retângulo em torno da cobra. Colocamos ainda 30 px de tolerância pois os sprites possuem pixels transparentes, e utilizamos o sprite do cão parado como referência para o teste.
Ao tocar na cobra o sprite que mostra o cão morrendo é disparado porque mudamos o estado do cachorro. Agora fica mais fácil adicionar inimigos e elementos na tela que se movimentam junto com o cenário. Os próximos passos seria adicionar inimigos, colocá-los em um vetor para agilizar o processo de testar de o cão morreu, fazer uma tela de game-over, implementar um menu, etc. No entanto, o escopo deste tutorial já foi contemplado.
Cŕedito: Sergio Herman