Program Arcade Games
With Python And Pygame

Chapter 13: "精灵"入门

视频:精灵入门

我们的游戏需要提供对碰撞物体的支持。球弹到板上,激光击中外星生物,抑或是我们最喜欢的角色正收集着金币。这些例子都要求碰撞检测。

Pygame的库已经支持sprites(精灵). "精灵"是指大型图画场景中的下的二维图像单位。典型的精灵都是在场景中会交互的个体,比如汽车,小青娃或者水管工人。

fig.sprite

追溯源头,老的游戏主机都对精灵有原生的硬件支持。 现在这种特殊的硬件支持已经不需要了,但是我们还在使用“精灵”这个词汇。

13.1 基本的精灵和碰撞

我们来通过一个例子来一步一步学习如何使用精灵。 这个例子会展示如何先创建一屏幕黑色的方块,然后通过鼠标可能哦告知用红色的方块收集它们,如图例所示13.1. 程序虎根据收集方框的数量来记录得分。代码可以在这里找到:
ProgramArcadeGames.com/python_examples/f.php?file=sprite_collect_blocks.py

fig.example_sprite_game1
Figure 13.1: 精灵游戏的例子

我们程序的前几行和其他游戏一样:

import pygame
import random

# 定义一些颜色
BLACK = (  0,   0,   0)
WHITE = (255, 255, 255)
RED   = (255,   0,   0)

ygame的库在第一行被导入,用于支持精灵。 random的库在第二行被导入,用于支持方块随机地摆放。 5-7是对颜色的标准定义;至此没有什么新内容。

class Block(pygame.sprite.Sprite):
    """
    This class represents the ball.
    It derives from the "Sprite" class in Pygame.
    """

第9行开始定义Block的类。 请注意这个类是Sprite类的子类。pygame.sprite.具体规定了库和包,这会稍后在第14章介绍到。 现在所有Sprite类的基本功能都是都kbd>Block类的一部分了。

    def __init__(self, color, width, height):
        """ Constructor. Pass in the color of the block,
        and its x and y position. """

        # 调用父类 (精灵) 构造函数
        super().__init__()

第15行中Block类的构造函数先取了一个参数self,就像别的构造函数一样。酒 它也取了其他的参数诸如颜色,高度和宽度,用来定义对象。

调用在Sprite父类中的构造函数非常重要,这样允许精灵被初始化。这一步在第20行完成。

        # 创建一个方块的图像,涂满一种颜色。
        # 它也可以用一张硬盘中的图片代替。
        self.image = pygame.Surface([width, height])
        self.image.fill(color)

第24行和第25行创建了最终会出现在屏幕上的图像。第24行创建了一个空白图像。第25行把它涂上了黑色。如果程序要把方块更改成别的颜色,只要修改这几行。

例如,请看下面的例子:

    def __init__(self, color, width, height):
        """
        Ellipse Constructor. Pass in the color of the ellipse,
        and its size
        """
        # 调用父类 (精灵) 构造函数
        super().__init__()

        # 设置背景颜色至透明
        self.image = pygame.Surface([width, height])
        self.image.fill(WHITE)
        self.image.set_colorkey(WHITE)

        # 绘制椭圆
        pygame.draw.ellipse(self.image, color, [0, 0, width, height])

如果被替代成了上面的代码,任何物体会变成椭圆。 第29行绘制了椭圆和其轮廓,第26行把白色变成了透明,这样背景就能显示出来了。 这和第11章中把图片的白色背景变透明是一个原理。

    def __init__(self):
        """ Graphic Sprite Constructor. """

        # 调用父类 (精灵) 构造函数
        super().__init__()

        # 载入图片
        self.image = pygame.image.load("player.png").convert()

        # 设置白色为透明
        self.image.set_colorkey(WHITE)

如果想要的位图,代入上述的代码会载入一张图片(第22行)并把白色背景设置成透明(第25行)。砸这种情况下,精灵的维度会自动设置成图像的维度,并不再需要传递。 请看第15行已经没有那些参数了。

不过使用哪种精灵,在我们的构造函数中还有一行代码至关重要:

        # 抓取包含图像维度的矩形对象。
        # 通过设置值rect.x和rect.y来更新该对象的位置
        self.rect = self.image.get_rect()

属性rect是Pygame所提供的 Rect类中的一个实例的变量。矩形表示了精灵的维度。这个矩形类有x和y这样的属性可以设置。Pygame虎根据这两个属性的值绘制精灵。 所以如果要移动这个精灵,程序员需要设置mySpriteRef.rect.xmySpriteRef.rect.y,其中 mySpriteRef是指向精灵所引用的变量。

Block类已经完成。让我们开始初始化代码。

# 初始化Pygame
pygame.init()

# 设置屏幕的高度和宽度
screen_width = 700
screen_height = 400
screen = pygame.display.set_mode([screen_width, screen_height])

The code above initializes Pygame and creates a window for the game. There is nothing new here from other Pygame programs.

# 这是一个精灵的列表。程序中每一个方块会添加到这个列表。
# 这个列表是由'Group'这个类所管理的。这个lock_list = pygame.sprite.Group()

# 这是每一个精灵的列表。
# 锁哟的放库,包括玩家的方块
all_sprites_list = pygame.sprite.Group()

使用精灵的一大优势就是可以以组的形式批量对它们操作。 如果它们在同一个组之中,之中可以只有一行命令来绘制并移动所有的精灵。我们还可以检测整组精灵的碰撞情况。

上面的代码创建了两个列表。变量all_sprites_list包含了游戏中所有的经历。这个列表会用于绘制所有的精灵。 变量block_list会包含所有玩家可能会碰撞到的对象 在这个例子中,它会包括除了玩家以外的所有对象。 我们不想包含玩家在内,是因为否则当我们检查在block_list中和玩家碰撞的对象时,Pygame会总是返回玩家。

for i in range(50):
    # 这表示一个方块
    block = Block(BLACK, 20, 15)

    # 给方块一个随机地址
    block.rect.x = random.randrange(screen_width)
    block.rect.y = random.randrange(screen_height)

    # 给列表添加方块
    block_list.add(block)
    all_sprites_list.add(block)

循环从49行开始,添加了50个黑色精灵方块至屏幕。第51行创建了一个新方块,并设置了颜色,宽度和高度。 第54行和第55行设置了该物体会出现的坐标。第58行把会和玩家碰撞的方块添加到相应的列表中。 第59行把它添加了全补方块的列表中。这和实验13中你写的代码应该很相似。

# Create a RED player block
player = Block(RED, 20, 15)
all_sprites_list.add(player)

61-63设置了游戏中的玩家。第62行创建了一个红色方块,用于表示玩家。这个方块在第63行添加到了all_sprites_list, 但不在block_list中。

# 循环直至用户点击了关闭按钮。
done = False

# 设置屏幕更新的频率
clock = pygame.time.Clock()

score = 0

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

    # 清理屏幕
    screen.fill(WHITE)

上面的代码是早在第5章介绍的标准的程序主循环。 第71行把我们的score变量设置成0。

    # 获得当掐鼠标的位置。这会返回一个两个数字组成的列表。
    pos = pygame.mouse.get_pos()

    # 分别提取x和y
    # 就像我们从字符串中提取字母一样
    # 把玩家对象设置到鼠标位置鼠标   player.rect.x = pos[0]
    player.rect.y = pos[1]

第84行中抓取鼠标的办法和之前讨论国的其他Pygame程序类似。 重要的新内容是第89-90行:还有精灵的矩形会移动到新的地址。 请记得这个矩形是第31行创建的,没有它这段代码就无法工作。

    # 查看玩家方块是否和谁碰撞过了。
    blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)

这一行代码,用玩家player引用的精灵同所有block_list中的精灵作比较。 代码会返回一个有交集的精灵的列表。 如果没有交集,会返回一个空白列表。布尔值True会移除碰列表中撞国的精灵。 如果设置成False,则精灵不会被移除。

    # 检查碰撞的列表
    for block in blocks_hit_list:
        score +=1
        print(score)

这个循环会检查每一个碰撞列表中的精灵。如果发现有精灵在里面,得分就加一。 然后打印分数到屏幕。请注意第98行中的中kbd>print不会把分数打印到有精灵的主屏幕,而是会打印到控制台窗口。 如何把分数显示在主窗口是实验14的一部分内容。

    # 画出所有精灵
    all_sprites_list.draw(screen)

all_sprites_list所属的Group类有一个方法叫draw。 这个方法会循环每一个列表中的精灵并调用对应精灵的draw方法。 这就意味着只要一行代码,程序就可以让all_sprites_list中所有的精灵画出来。

    # 限制到每秒60帧
    clock.tick(60)

    # 把我们所画的更新至屏幕
    pygame.display.flip()

pygame.quit()

103-109行翻转了屏幕,并在主循环结束的时候调用quit方法。

13.2 移动精灵移动移动

视频:移动精灵

到目前位置,只有玩家精灵在移动。如何然所有的精灵都移动呢? 这很简单;只需要两步。

第一步是给Block类天际一个新方法。 这个新方法叫做update。这个更新函数会在update被整个列表调用时自动调用。

把它放到精灵里是这样的:

    def update(self):
        """ Called each frame. """

        # 向下移动一个像素一个       self.rect.y += 1

把它放进主循环里:

    # 对block_list中的所有元素调用update()方法中    block_list.update()

这代码并不完美因为落出屏幕的方块并不会重新显示。下面的代码会提升update函数,让消失的方块重新在顶部显示出来。

    def update(self):
        # 向下移动一个像素
        self.rect.y += 1
        if self.rect.y > screen_height:
			self.rect.y = random.randrange(-100, -10)
			self.rect.x = random.randrange(0, screen_width)

如果主程序需要对有些精灵重新收集到顶端,可以通过以下的代码更改精灵:

    def reset_pos(self):
        """ Reset position to the top of the screen, at a random x location.
        Called by update() or the main program loop if there is a collision.
        """
        self.rect.y = random.randrange(-300, -20)
        self.rect.x = random.randrange(0, screen_width)

    def update(self):
        """ Called each frame. """

        # 向下移动一个像素
        self.rect.y += 1

        # 如果方块在太下方,重新设置到顶端。
        if self.rect.y > 410:
            self.reset_pos()

在碰撞发生时与其销毁方块,程序可能会调用 reset_pos函数,这样把准备收集的方块移至屏幕顶端。

    # 检查玩家方块是否与任何方块相撞。
    blocks_hit_list = pygame.sprite.spritecollide(player, block_list, False)

    # 检查碰撞的列表
    for block in blocks_hit_list:
        score += 1
        print(score)

        # 把方块重设至屏幕顶端
        block.reset_pos()

这个例子的完整代码在这里:
ProgramArcadeGames.com/python_examples/f.php?file=moving_sprites.py

如果你其实想看弹跳的精灵,请看这里:
ProgramArcadeGames.com/python_examples/f.php?file=moving_sprites_bounce.py

如果你想看它在圆中移动,请看这里:
ProgramArcadeGames.com/python_examples/f.php?file=sprite_circle_movement.py

13.3 游戏的类

回到第九章我们介绍函数的地方。在章末我们介绍了一种使用main函数的选项。当程序越来越大时,这种方法可以帮助我们避免发现问题是需要从一大堆代码中寻找的问题。 我们现在的程序还不算打。单色我知道有些人想要从一开始就更好地管理代码。

这里是另一种管理你的代码的办法。 观看下视频来对程序的原理有个大概了解。
ProgramArcadeGames.com/python_examples/f.php?file=game_class_example.py

13.4 其他例子

这里还有一些你可以用精灵完成的例子。有部分例子附有视频用来解释代码。

13.4.1 射击

fig.bullets
Figure 13.2: 射击

对射击游戏感兴趣吗? 比如像经典的太空侵略者? 这个例子展示了如何用精灵表示子弹:
ProgramArcadeGames.com/python_examples/f.php?file=bullets.py

13.4.2 墙

你是否对冒险游戏有更多的需求? 你不想让玩家在各种迷宫里绕圈吗? 这个例子展示了如果添加墙壁来阻止玩家的移动:
ProgramArcadeGames.com/python_examples/f.php?file=move_with_walls_example.py

fig.walls
Figure 13.3: 移动的墙

等下? 一个房间不够冒险的? 你希望玩家从一个屏幕移动到另一个屏幕? 我们也能做到! 来看一下这个例子,其中的玩家穿越了多个房价的迷宫:
ProgramArcadeGames.com/python_examples/f.php?file=maze_runner.py

fig.maze_runner
Figure 13.4: 多个房间的迷宫

13.4.3 平台

对像大金刚之类的平台类游戏感兴趣? 我们需要用到墙的例子中的概念,但是增加重力效果:
ProgramArcadeGames.com/python_examples/f.php?file=platform_jumper.py

fig.platform_jumper
Figure 13.5: 在平台间跳跃

好的平台类游戏可以从一侧移到另一侧。下面就是个滚轴平台类游戏:
ProgramArcadeGames.com/python_examples/f.php?file=platform_scroller.py

fig.platform_scroller
Figure 13.6: 滚轴平台类游戏

Even cooler platform games have platforms that move! See how that is done with this example:
ProgramArcadeGames.com/python_examples/f.php?file=platform_moving.py

fig.platform_moving
Figure 13.7: 移动平台

13.4.4 蛇/蜈蚣

也曾经有学生对贪吃蛇类游戏感兴趣。你能控制一条多个分段的蛇。这需要每一段都是一个列表。尽管需要学习两个新的名利,游戏背后的概念并不难。

在屏幕中控制一条蛇的移动:
ProgramArcadeGames.com/python_examples/f.php?file=snake.py

fig.snake
Figure 13.8: 蛇

13.4.5 使用精灵列表

这是一个扩展的例子,使用了给平台类游戏提供图形的“精灵列表” 它支持多层和平台移动It supports multiple levels and moving platforms as well. 游戏被分成了多个文件。 ProgramArcadeGames.com/python_examples/en/sprite_sheets

fig.sprite_sheet
Figure 13.9: 精灵列表的平台类游戏

13.4.6 选择题小测验

Click here for a multiple-choice quiz.

13.4.7 练习表

There is no worksheet for this chapter.

13.4.8 实验

Click here for the chapter lab.