Program Arcade Games
With Python And Pygame

Chapter 8: 动画入门

8.1 弹跳的矩形

视频:弹跳的矩形

在开始我们第一个动画之前,我们先使用第五章的pygame基础模板程序打开一个空白的屏幕。 代码pygame_base_template.py可以在这里找到:
ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py

我们会写一个在黑色背景下弹跳的白色矩形。 你也可以挑选自己喜欢的颜色,只要保证背景色和矩形颜色是不同的!

fig.bouncing_rectangle

第一步,从基础模板起手把背景色从白色改成黑色。改动大概在46行左右。

screen.fill(BLACK)

接下来,画一个矩形。一个简单的巨新就足够了。这段代码应该在清理过屏幕之后,以及显示到显示器之前。

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

每次循环的时候,矩形都会被画在(50,50)的位置。这是由列表里前两个50控制的。这两个不变更,矩形是不会移动的。

矩形是50像素宽,50像素高。维度是由列表里后两个数字控制的。 我们也可以把这个矩形称为正方形,因为它有着相同的宽和高。 我还是会坚持称它了矩形因为所有的正方形都还是矩形,而且根据不同的显示器和分辨率,像素也不一定是正方形的。 如果对此感兴趣的话查看像素比例表

我们如何不停地改变位置而不是停在(50, 50)呢? 当然是使用一个变量! 下面的代码是这样做的第一步:

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

把矩形向右移, 可以把x每一帧增加1。 下面这段代码很接近,但并没有真正实现。:

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

问题就在于rect_x每次循环的时候都会重设成50。 要解决这个问题,把rect_x的初始化挪到循环之外。 下面这段代码就可以成功地将矩形向右侧移动。

# 从矩形的x位置开始
# 注意它是如何在主循环之外的
rect_x = 50

# -------- 程序主循环 -----------
while not done:
	for event in pygame.event.get(): # 用户做了些操作
		if event.type == pygame.QUIT: # 如果用户点击了关闭
			done = True # 树下旗帜表明我们已经完成了所以要退出循环

	# 设置背景色
	screen.fill(BLACK)

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

为了让盒子移动的更快些,增加rect_x的值可以不是1,而是每次增加5:

rect_x += 5

We can expand this code and increase both x and y, causing the square to move both down and right:

# 矩形的起始位置
rect_x = 50
rect_y = 50

# -------- 程序主循环 -----------
while not done:
	for event in pygame.event.get():
		if event.type == pygame.QUIT:
			done = True

	# 设置屏幕背景
	screen.fill(BLACK)

	# 画出矩形
	pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])

	# 移动矩形的起始位置
	rect_x += 5
	rect_y += 5

矩形移动的方向和速度可以用一个矢量来存储。 这样就使得改变放下和速度更加简单。 下面这段代码就使用了这样的变量来存储。

# 矩形的起始位置
rect_x = 50
rect_y = 50

# 矩形的方向和速度
rect_change_x = 5
rect_change_y = 5

# -------- 循环主程序 -----------
while done == False:
	for event in pygame.event.get(): # 用户做了些操作
		if event.type == pygame.QUIT: # 如果用户点击了关闭
			done = True # 树下旗帜表明我们已经完成了所以要退出循环

	# 设置屏幕背景
	screen.fill(BLACK)

	# 画出矩形
	pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])

	# 移动矩形的起始位置
	rect_x += rect_change_x
	rect_y += rect_change_y

一旦矩形触及到了屏幕边缘仍然会继续前进。 没有什么能让矩形反弹回来。要反转矩形移动的方向继续前行,一旦触到了屏幕底部,rect_change_y需要从5变成-5,也就是当rect_y比屏幕高度大的时候。 虾米的代码就会作出这样的检查并反转方向:

# 如有需要反转移动的方向
if rect_y > 450:
	rect_change_y = rect_change_y * -1
fig.rect_loc
Figure 8.1: 基于y轴的位置

为什么检查rect_y是不是450? 如果屏幕是500像素高,那么检查500才应该是更加看似合理的猜想。 但请记得矩形是从左上角 开始画的。如果起始位置是500, 那么它会从500画到550, 在反弹之前已经超出屏幕了。 请看图例 8.1.

所以把矩形是50像素高考虑进去的虎,正确的反弹的位置是:
$500-50=450$.

下面的代码展示了一个在700x400的窗口中四面都会反弹的矩形:

# 如有需要反转移动的方向
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

感兴趣在尝试一个比矩形复杂一些的形状吗? 许多绘图的命令都是根据rect_xrect_y的基础上操作的。 下面的代码画了一个嵌套在白色巨新里的红色矩形。红色矩形离白色的矩形的左上角的两边相距各10个像素, 所以总共维度小了20,看起来就想白色矩形包围着红色矩形,间隔10个像素。请看图例8.2.

fig.two_part_rectangle
Figure 8.2: 白色矩形嵌套红色矩形在中央
# 在一个白色巨新内画一个红色矩形矩形ygame.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 下雪动画

只有一个物体在动是不是不够? 一次让成百上千的物体一起动如何? 这里会使用从8.1节介绍的技术来绘制雪花下坠的动画。

8.2.1 代码解释

视频:下雪动画

牙开始写这个程序,首先使用pygame的基础模板来打开一个空白屏幕。 代码pygame_base_template.py还是可以在这里找到:
ProgramArcadeGames.com/python_examples/f.php?file=pygame_base_template.py

我们可以使用随机数来生成诸如星星,雪花和雨滴的坐标位置。 最简单的办法是使用一个for循环在随机的坐标画圆形。 在主循环while循环内试着运行以下一些一下

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

试一下,会发现这个程序有一个奇怪的问题!每秒20次、每次经过循环的时候,都会在新的随机位置生成雪花。 试着调整雪花的数量来看它如何改变图像的。

很明显地,我们需要随机雪花的定位然后让它们聚集砸一个点上,我们不希望每秒生成20次新的定位。 我们需要一个列表来记录它们在哪儿。我们可以使用Python的列表来完成。这必须完成在主循环之前,否则程序会在每1/20秒给列表添加50个新的雪花。

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

一旦雪花的位置被添加以后,它么可以像一般的列表一样访问。 下面的代码会同时打印第一个落点的x和y坐标, 存储在位置0:

print(snow_list[0])

我们只想要x坐标或者只想要y坐标怎么做? 我么可以列表嵌套列表。主列表记录所以的坐标。 在主列表里,每个坐标是一个列表(x的位置是0,y的位置是1)。 例如,这是三个坐标:

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

要打印位置0的y坐标,先选择坐标0,然后选择在位置1的y的值。代码如下:

print(snow_list[0][1])

要打印位置是20的x的值,先选择坐标20,然后选择位置0的x的值: first select coordinate 20, and then the x value at position 0:

print(snow_list[20][0])

在主循环while循环至内, 我们可以使用一个for循环来画雪片列表里的每一个雪片。 请记得len(snow_list)会返回雪片列表的所有元素的数量。

# 处理雪片列表里的每一个雪片
for i in range(len(snow_list)):
	# 画雪片
	pygame.draw.circle(screen, WHITE, snow_list[i], 2)

请记得,有两种类型的for循环。另一种类型的for循环也可以使用,就像这样:

# 处理列表里每一个雪片位置的复制品
for xy_coord in snow_list:
	# 画雪片
	pygame.draw.circle(screen, WHITE, xy_coord, 2)

然而,因为我们是打算更改雪片的位置,所以这种更改其复制品的位置没有实际效用,所以不能采用。

如果一个程序的所有物体都会向下移动,就像下雪,那么我们需要扩展之前的for循环,来增加y坐标的值:

# 处理雪片列表里的每一个雪片
for i in range(len(snow_list)):

	# 画雪片
	pygame.draw.circle(screen, WHITE, snow_list[i], 2)

	# 把雪片向下移动一个像素
	snow_list[i][1] += 1

这会使得雪花下落,但是一旦离开了屏幕就没有新的出现了。添加以下的代码,雪花会重新在顶部的随机位置生成:

	# 如果雪片已经落在屏幕底端之外
	if snow_list[i][1] > 400:
		# 把它重设在顶端之上
		y = random.randrange(-50, -10)
		snow_list[i][1] = y
		# 给它一个新的x坐标值
		x = random.randrange(0, 400)
		snow_list[i][0] = x

我们也可以给列表里的不同元素添加不同尺寸、形状、颜色、速度和方向。 这会变得有些复杂,因为列表里需要记录各种数据类型。 我们现在就还是保持简单,不过一旦我们学习里12章中的“类”,管理多个对象的不同属性会变得很简单。

8.2.2 完整程序的清单

"""
 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()

这个例子展示了每个雪片在同一方向移动。 如何让每个雪片有自己的动画,有自己的方向? 如果你的游戏需要这些,请前去12章节学习如何使用类。 实验8会一步一步指导你创造成百上千不的元素,运动在不同的方向。

8.3 3D动画

视频: Blender游戏引擎的示例

从2D环境拓展到3D环境以及伴随的游戏物理机制的更改并没有想象中那么困难。 尽管这超出了这门课的范畴,但是还是值得来瞧一瞧这是如何湾趁的。

有一个免费的3D程序叫作Blender,它有一个游戏引擎可供程序员创建3D游戏。游戏中的3D物体可以通过附着的Python代码来控制它们的行动。

fig.blender01
Figure 8.3: Blender引擎为基础的游戏

看一下这个图例8.3。它展示了一个绿色的器皿盛着几个物件。 蓝色的物件是由一个Python脚本控制、围绕着器皿移动、再同其他物体碰撞的。脚本如下所示,有许多同2D程序相同的特征。 有一个主循环,有一个x,y坐标的列表,有一些变量控制矢量。

主程序是由Blender控制的。下面的Python代码会砸游戏的每一帧渲染时被Blender召用 这也是为何Python代码没有主循环。但它是存在的。存砸存在>蓝色物体以x,y,z的形式记录位置。它可以通过blue_object.position这个变量来访问或者更改。位置0存x,位置1存y,位置2存z。

不像这一章先前介绍2D例子时使用的change_xchange_y变量,这个Blender的例子使用了关联数组:
blue_object["x_change"]
blue_object["y_change"]

if语句用拉检查蓝色物体是否抵达了屏幕边缘和反置的方向。 不像2D游戏里的像素,这里物理的坐标肯恩是浮点类型。 定义在5和6之间的一个位置,设置成5.5是被允许的。

# 导入Blender游戏引擎
import bge

# 获得一份蓝色物体的参考
cont = bge.logic.getCurrentController()
blue_object = cont.owner

# 打印蓝色物体的坐标位置x,y
print(blue_object.position[0], blue_object.position[1] )

# 根据x_change和y_change来改变位置
# x_change和y_change是蓝色物体这个对象所关联的游戏属性
blue_object.position[0] += blue_object["x_change"]
blue_object.position[1] += blue_object["y_change"]

# 检查物体是否到达了边缘
# 如果是的,反转方向。4个边缘都要囊括。
if blue_object.position[0] > 6 and blue_object["x_change"] > 0:
    blue_object["x_change"] *= -1

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

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

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

Blender可以在这里下载:
http://www.blender.org/

一个完整的Blender的例子在这里可以下载:
ProgramArcadeGames.com/chapters/08_intro_to_animation/simple_block_move.blend.

8.4 复习

8.4.1 选择题小测验

Click here for a multiple-choice quiz.

8.4.2 练习表

Click here for the chapter worksheet.

8.4.3 实验

Click here for the chapter lab.


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