Program Arcade Games
With Python And Pygame

Chapter 8: Introdução a Animação

8.1 O Retângulo Saltitante

Video: Retangulo Saltitante

Para iniciar nossa primeira animação, vamos começar com o programa pygame base com uma tela em branco, do capítulo 5. O fonte para ele paygame_base_template.py pode ser baixado de:
ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py

Vamos montar um programa que cria um retângulo branco que percorre a largura de uma tela com um fundo preto. Sinta-se livre para escolher suas próprias cores, para isso, basta usar uma cor de fundo diferente da cor do retângulo!

fig.bouncing_rectangle

Primeiro passo, inciar com o template base e “ flipar” o fundo com a cor preta, o código para isso está na linha 46.

screen.fill(BLACK)

Seguindo adiante, desenhamos o retângulo que planejamos animar. Um simples retângulo é suficiente. Este código pode ir depois do código que limpa a tela e antes de “flipar” ela.

pygame.draw.rect(screen, WHITE, [50, 50, 50, 50])

A cada interção do laço (loop) o retângulo será desenhado com sua licalização (x,y) exatamente em (50,50). Isto é controlado pelas dois primeiros 50s dentro da tupla. O quadrado só irá se mover quando esses numeros mudarem.

O retângulo irá terá 50 pixels de largura por 50 pixels de altura. As dimenções são controladas pelos dois últimos números da tupla. Nós podemos também chamar esse retângulo de quadrado, uma vez que ele tem a largura e a altura do mesmo tamanho. Eu insisto em chamar ele de retângulo porque quadrados também são retângulos e dependendo da resolução do monitor usado eles continuam a ser quadrados. De uma olhada em Pixel Aspect Ratio se você estiver mesmo interessado neste assunto.

Como podemos ficar mudando o local, em vez de tê-lo preso em (50, 50)? Usando uma variável, é claro! O código abaixo é um primeiro passo nesse sentido:

rect_x = 50
pygame.draw.rect(screen, WHITE, [rect_x, 50, 50, 50])

Para mover o retângulo para a direita, x deve ser incrementado a cada interação do frame. Este código é efetivo, mas não é bemisso que precisamos:

rect_x = 50
pygame.draw.rect(screen, WHITE, [rect_x, 50, 50, 50])
rect_x += 1

O problema com o código acima é que rect_x é resetado para 50 em todas as vezes que entra no laço (loop). Para resolver isso, movemos a inicialização de rect_x para antes de declarar o início do laço (loop). A próxima sessão de código irá efetivamente mover o retângulo para a direita.

# Iniciando a posição x do retangulo
# Note como tem de ficar fora do laço while.
rect_x = 50

# -------- Main Program Loop -----------
while not done:
	for event in pygame.event.get(): # Aguarda a acao do usuario
		if event.type == pygame.QUIT: # Se o usuario clicou em sair
			done = True # Passa verdadeiro para a Flag "done"

	# Define o fundo da tela
	screen.fill(BLACK)

	pygame.draw.rect(screen, WHITE, [rect_x, 50, 50, 50])
	rect_x += 1

Para mover a caixa mais rápido, basta incrementar rect_x em 5 em vez de 1:

rect_x += 5

Podemos expandir este código e incrementar x e y, com isso, o quadrado irá se mover para a direita e para a esquerda respectivamente:

# Posicao inicial do retangulo
rect_x = 50
rect_y = 50

# -------- Laço principal do programa -----------
while not done:
	for event in pygame.event.get():
		if event.type == pygame.QUIT:
			done = True

	# Define um fundo preto para a tela
	screen.fill(BLACK)

	# Desenha o retangulo
	pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])

	# Move o retangulo para o ponto inicial
	rect_x += 5
	rect_y += 5

A direção e a velocidade para o movimento das caixas podem ser armazenadas em um vetor. Isto torna fácil madar direção e a velocidade de cada movimento do objeto. O próximo bit de código mostra como usar variáveis para armazenar os valores para x e y no lugar de (5, 5).

# Starting position of the rectangle
rect_x = 50
rect_y = 50

# Speed and direction of rectangle
rect_change_x = 5
rect_change_y = 5

# -------- Main Program Loop -----------
while done == False:
	for event in pygame.event.get(): # User did something
		if event.type == pygame.QUIT: # If user clicked close
			done = True # Flag that we are done so we exit this loop

	# Set the screen background
	screen.fill(BLACK)

	# Draw the rectangle
	pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])

	# Move the rectangle starting point
	rect_x += rect_change_x
	rect_y += rect_change_y

Uma vez que a caixa atinge a borda da tela ela irá continuar. Nada faz com que o retângulo salte de volta para dentro da borda da tela. Para reverter a direção que retângulo siga para a direita, o rect_change_y precisa ser mudado de 5 para -5 até que o retângulo atinja a borda inferior da tela. retângulo obtém ao fundo. O retângulo stá na parte inferior Quando o rect_y for maior que a altura total da tela. O código a seguir checa e reverte a direção:

# Faz o retengulo saltar se for necessario
if rect_y > 450:
	rect_change_y = rect_change_y * -1
fig.rect_loc
Figure 8.1: Rectangle location based on y coordinate

Porque checar se rect_y está na faixa de 450? Se a tela tem 500 pixels de altura, checar para 500 seria o óbvio. Mas lembre-se, o quadrado é desenhado partindo dos cantos topo esquerda do retângulo. Se o retângulo for desenhado partindo em 500, ele será desenhado de 500 para 550, ou seja, totalmente fora da tela antes de saltar. Veja a figura 8.1.

Tenha em conta que esse retângulo tem 50 pixels de altura, assim a correta licalização do salto, é:
$500-50=450$.

O código abaixo irá fazer o retângulo saltar em todos os lados da tela:

# Faz o retângulo saltar quando for necessario
if rect_y > 450 or rect_y < 0:
	rect_change_y = rect_change_y * -1
if rect_x > 650 or rect_x < 0:
	rect_change_x = rect_change_x * -1

Interessado em uma figura mais complexas que um retângulo? Muitos comandos podem ser usados para desenhar baseando-se no uso que foi feito aqui das variáveis rect_x e rect_y. O código abaixo desenha um retângulo vermelho dentro de um branco. O retângulo vermelho é desenhado 10 pixels a mais em relação da direção do canto superior esquerdo do retângulo branco. Ele também é 20 pixels menor em ambas as dimenções, resultando em 10 pixels de branco sobressaindo do retângulo vermelho. Veja a figura 8.2.

fig.two_part_rectangle
Figure 8.2: White rectangle with a red square in the middle
# Desenha um retângulo vermelho dentro de um branco
pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])
pygame.draw.rect(screen, RED, [rect_x + 10, rect_y + 10 ,30, 30])

8.2 Efeito de neve animado

Animar somente um item não é suficiente? Necessidade de animar mais? Que tal ser capaz de animar centenas de objetos de uma só vez? Isso mostra como usar as técnicas de seção 8.1 para animar muitos flocos de neve caindo.

8.2.1 Explicação do Código

Video: Neve Animada

Para iniciar este progama, comece com o template base e abra uma tela limpa. Os fontes desse template pygame_base_template.py pode ser baixado daqui:
ProgramArcadeGames.com/python_examples/show_file.php?file=pygame_base_template.py

É possível criar localizações x, y para outras coisas, como, estrelas, neve ou chuva usando números aleatórios (randomicos). A maneira mais fácil para fazer isso é usando um laço (loop) for para desenhar círculos com posições x, y aleatórios. Tente o seguinte código dentro do laço while principal.

for i in range(50):
    x = random.randrange(0, 400)
    y = random.randrange(0, 400)
	pygame.draw.circle(screen, WHITE, [x, y], 2)

Experimente, este programa tem um problema estranho! Vinte vezes por segundo, a cada interação do laço (loop), ele desenha a exibição na tela em uma nova posição aleatória. Tente ajustar a exibição do floco e veja como isso muda a imagem.

Obviamente, precisamos posicionar aleatoriamente os flocos de neve e mantê-los na mesma posição. Não queremos gerar novas posições vinte vezes por segundo. Precisamos manter em uma lista onde eles estarão cada um em um elemento dessa lista. O programa pode usar uma “python list” para fazer isto. Isto deve ser feito antes do laço principal, caso contrário o programa irá adicionar 50 flocos de neve novos à lista a cada 1/20 de segundo.

for i in range(50):
    x = random.randrange(0, 400)
    y = random.randrange(0, 400)
    snow_list.append([x, y])

Como os flocos de neve já estão posicionados na lista, eles podem ser acessados normalmente, diretamente da lista. O código seguinte imprime ambas as coordenadas, x e y no primeiro elemento da matriz (lista), isto é, os elementos na sua posição zero:

print(snow_list[0])

Porque estamos procurando justamente pelas coordenadas x ou y? Nõs temos listas dentro da lista, cada coordenada é uma lista com dois elementos o x e o y, o x (posição 0) e o y (posição 1). Por exemplo, aqui estão as coordenadas:

[[34, 10],
 [10, 50],
 [20, 18]]

para exibir y, na posição 0. Primeiro selecione a posição 0 na lista e depois a selecionoe o elemento na posição 1 da sub-lista, na seguinte forma::

print(snow_list[0][1])

Para imprimir o valor de x da vigésima primeira coordenada (posição 20) primeiro selecione a coordenada 20 e depois o valor de x, que é o elemento 0 da sub-lista, uma vez que 20 é o elemento que está na posição 21 dentro da lista:

print(snow_list[20][0])

Dentro do laço while principal, o programa vai precisar ter um laço for para desenhar cada um dos itens dentro da lista de neves. Lembre-se, len(snow_list) irá retornar o número de elementos dentro da lista de folcos de neve.

# Processa cada floco de neve na lista
for i in range(len(snow_list)):
	# Desenha um floco de neve
	pygame.draw.circle(screen, WHITE, snow_list[i], 2)

Lembre que existem dois tipos de laço for. Poderiamos usar esse outro tipo de laço for, veja aqui:

# Processa uma CÓPIA de cada posição dos flocos de neve na lista
for xy_coord in snow_list:
	# Desenha cada floco
	pygame.draw.circle(screen, WHITE, xy_coord, 2)

No entanto, como planejamos modificar as localizações dos flocos de neve na tela, não podemos usar esse tipo de laço for, porque nele não consguiremos alterar as cópias das posições dos flocos, mas nós podemos modificar a localização do floco que está dentro da posição atual na lista.

Se o programa precisa mover todos os objetos dentro da matriz para baixo, como neve, então expandindo o laço for criado acima irá incrementar as coordenadas:

# Processa cada floco de neve na lista
for i in range(len(snow_list)):

	# Desenha o folco
	pygame.draw.circle(screen, WHITE, snow_list[i], 2)

	# Move o floco para baixo um pixel
	snow_list[i][1] += 1

Isso move para baixo, mas quando chegar no fim da tela, nada de novo aparece. Adicionando o código abaixo, a neve será reiniciada no topo da tela em uma nova posição aleatória:

	# Se os flocos de neve chegaram no fim da tela
	if snow_list[i][1] > 400:
		# Reseta eles justamente acima do topo
		y = random.randrange(-50, -10)
		snow_list[i][1] = y
		# Pega uma nova posição
		x = random.randrange(0, 400)
		snow_list[i][0] = x

Também é possível adicionar coisas na lista e ter diferentes tamanhos de figuras, cores, velocidade e direação para cada item na tela. Isto é mais complexo por causa dos múltiplos tipos de dados que precisaremos adicionar dentro da lista. Por enquanto, vamos nos contentar com simples flocos de neve, mas quando formos aprender sobre “classes” no capítulo 13 isso será facil de implementar e manusear diferentes atributos para múltiplos objetos.

8.2.2 Código fonte do programa, completo

"""
 Animating multiple objects using a list.
 Sample Python/Pygame Programs
 Simpson College Computer Science
 http://programarcadegames.com/
 http://simpson.edu/computer-science/

 Explanation video: http://youtu.be/Gkhz3FuhGoI
"""

# Import a library of functions called 'pygame'
import pygame
import random

# Initialize the game engine
pygame.init()

BLACK = [0, 0, 0]
WHITE = [255, 255, 255]

# Set the height and width of the screen
SIZE = [400, 400]

screen = pygame.display.set_mode(SIZE)
pygame.display.set_caption("Snow Animation")

# Create an empty array
snow_list = []

# Loop 50 times and add a snow flake in a random x,y position
for i in range(50):
    x = random.randrange(0, 400)
    y = random.randrange(0, 400)
    snow_list.append([x, y])

clock = pygame.time.Clock()

# Loop until the user clicks the close button.
done = False
while not done:

    for event in pygame.event.get():   # User did something
        if event.type == pygame.QUIT:  # If user clicked close
            done = True   # Flag that we are done so we exit this loop

    # Set the screen background
    screen.fill(BLACK)

    # Process each snow flake in the list
    for i in range(len(snow_list)):

        # Draw the snow flake
        pygame.draw.circle(screen, WHITE, snow_list[i], 2)

        # Move the snow flake down one pixel
        snow_list[i][1] += 1

        # If the snow flake has moved off the bottom of the screen
        if snow_list[i][1] > 400:
            # Reset it just above the top
            y = random.randrange(-50, -10)
            snow_list[i][1] = y
            # Give it a new x position
            x = random.randrange(0, 400)
            snow_list[i][0] = x

    # Go ahead and update the screen with what we've drawn.
    pygame.display.flip()
    clock.tick(20)

# Be IDLE friendly. If you forget this line, the program will 'hang'
# on exit.
pygame.quit()

Este exemplo mostra cada floco de neve se movendo numa mesma direção. E se cada item precisasse ser animado separadamente, cada um em sua própria direção? Se você precisa isso para o seu jogo, veja no capítulo 13 como usar classes. Na sessão “lab” passo 8 tem como implementar centenas de diferentes itens animados, cada um em sua própria direção.

8.3 3D Animation

Video: Demonstração de um Motor de Jogo usando o 'Blender'

Estender de um ambiente 2D para um ambiente 3D completo com física de jogo (physics) não é tão difícil como parece. Embora fuja do escopo desta classe, vale a pena ver como se faz.

Aqui tem um programa 3D livre chamado Blender, que é um “game engine” que os programadores usam para criar jogos. Os objetos 3D no jogo podem ter código python anexado ao controles e ações do jogo.

fig.blender01
Figure 8.3: Blender-based Game

Veja a figura . Isto mostra uma bandeja verde com vários objetos nela. O objeto azul é controlado por um script Python que move ele na bandeja colidindo com os outros objetos. O script, mostrado abaixo, tem muitas das mesmas características que têm os programas 2D. Há um laço (loop) principal, há uma lista para as posições x, y, que são variáveis, controlando o vetor.

o laço (loop) principal do programa é controlado pelo Blender. O código python mostrado na listagem é chamado pelo Blender para cada 'frame' que o jogo processa. O código Python não mostra um laço principal no programa. Mas Ele Existe.

o objeto azul tem sua localização realizada no formato x, y, z. Ela pode ser acessada e alterada usando a variável blueobject.position. A posição 0 na matriz detém x, a posição 1 detém y e a posição 2 detém z.

Um pouco parecido com o que acontece nas variáveis change_x e change_y usadas nos exemplos 2D para fazer as alterações, este exemplo Blender usa uma matriz associativa de localizações: < br / > blueObject["x_change"]
blueObject["y_change"]

Os comandos if determinam se o objeto azul encontrou as bordas da tela e ajusta os valores para fazer a reversão. Diferentemente dos pixels usados num programa 2D, as posições aqui tem de ser armazenadas como um número de ponto flutante (flaoting point). Para posicionar um item entre 5 e 6 é permitido usar 5.5 que está entre os valores citados.

# Import Blender Game Engine
import bge

# Get a reference to the blue object
cont = bge.logic.getCurrentController()
blueObject = cont.owner

# Print the x,y coordinates where the blue object is
print(blueObject.position[0], blueObject.position[1] )

# Change x,y coordinates according to x_change and
# y_change. x_change and y_change are game properties
# associated with the blue object.
blueObject.position[0] += blueObject["x_change"]
blueObject.position[1] += blueObject["y_change"]

# Check to see of the object has gone to the edge.
# If so reverse direction. Do so with all 4 edges.
if blueObject.position[0] > 6 and blueObject["x_change"] > 0:
    blueObject["x_change"] *= -1

if blueObject.position[0] < -6 and blueObject["x_change"] < 0:
    blueObject["x_change"] *= -1

if blueObject.position[1] > 6 and blueObject["y_change"] > 0:
    blueObject["y_change"] *= -1

if blueObject.position[1] < -6 and blueObject["y_change"] < 0:
    blueObject["y_change"] *= -1

O Blender pode ser baixado de:
http://www.blender.org/

O código completo de um exemplo pode ser acessado em:
ProgramArcadeGames.com/chapters/08_intro_to_animation/simple_block_move.blend.

8.4 Revisão

8.4.1 Teste de múltiplas escolhas

Click here for a multiple-choice quiz.

8.4.2 Respostas curtas

Click here for the chapter worksheet.

8.4.3 Lab

Click here for the chapter lab.


You are not logged in. Log in here and track your progress.