逐行钳位器

作者

皮特·辛纳斯

联系方式

pete@shinners.org

引言

梨果 有一个简单的例子叫“黑猩猩”。这个例子模拟了一只可以拳打脚踢的猴子在屏幕上四处走动,承诺会有财富和奖励。该示例本身非常简单,错误检查代码也不多。这个示例程序演示了pyGame的许多功能,比如创建窗口、加载图像和声音、呈现文本以及基本的事件和鼠标处理。

程序和图片可以在pyGame的标准源代码发行版中找到。可以通过运行以下命令来运行它 python -m pygame.examples.chimp 在你的航站楼里。

本教程将一块一块地介绍代码。解释代码是如何工作的。还将提到如何改进代码,以及哪些错误检查可以提供帮助。

这是一个很好的教程,让人们第一次看到 梨果 密码。一次 梨果 是完全安装的,您可以在Examples目录中自己找到并运行黑猩猩演示。

(不,这不是横幅广告,这是截图)

黑猩猩游戏横幅

Full Source

导入模块

这是将所有需要的模块导入到程序中的代码。它还检查一些可选的pyGame模块是否可用。**

# Import Modules
import os
import pygame as pg

if not pg.font:
    print("Warning, fonts disabled")
if not pg.mixer:
    print("Warning, sound disabled")

main_dir = os.path.split(os.path.abspath(__file__))[0]
data_dir = os.path.join(main_dir, "data")

首先,我们导入标准的“os”python模块。这允许我们做一些事情,比如创建独立于平台的文件路径。

在下一行中,我们将导入pyGame包。在我们的例子中,我们将pyGame导入为 pg ,以便可以从命名空间引用pyGame的所有功能。 pg

一些pyGame模块是可选的,如果找不到它们,它们的计算结果为 False 。因此,我们决定打印一条漂亮的警告消息,如果 fontmixer PYGAME中的模块不可用。(尽管它们只有在非常罕见的情况下才会不可用)。

最后,我们准备两个路径以供其余代码使用。 main_dir 使用 os.path module and the _ _FILE__`变量,用于定位游戏的PYTHON文件,并从该路径解压文件夹。然后,它准备变量 ``data_dir` 来告诉加载函数确切地在哪里查找。

正在加载资源

这里我们有两个函数可以用来加载图像和声音。在本节中,我们将分别介绍每个函数。**

def load_image(name, colorkey=None, scale=1):
    fullname = os.path.join(data_dir, name)
    image = pg.image.load(fullname)

    size = image.get_size()
    size = (size[0] * scale, size[1] * scale)
    image = pg.transform.scale(image, size)

    image = image.convert()
    if colorkey is not None:
        if colorkey == -1:
            colorkey = image.get_at((0, 0))
        image.set_colorkey(colorkey, pg.RLEACCEL)
    return image, image.get_rect()

此函数接受要加载的图像的名称。它还可以选择使用一个参数来设置图像的Colorkey,以及一个参数来缩放图像。在图形中使用Colorkey来表示透明图像的颜色。

该函数所做的第一件事是创建文件的完整路径名。在本例中,所有资源都位于“data”子目录中。通过使用 os.path.join 函数,则将创建一个路径名,该路径名适用于运行游戏的任何平台。

接下来,我们使用 pygame.image.load()load new image from a file (or file-like object) 功能。加载图像后,我们对 convert() 功能。这将创建曲面的新副本,并转换其颜色格式和深度以匹配显示。这意味着图像将以尽可能快的速度显示在屏幕上。

然后,我们使用 pygame.transform.scale()resize to new resolution 功能。此函数接受曲面及其应缩放到的大小。要按标量缩放,我们可以得到大小,并按标量缩放x和y。

最后,我们为图像设置了颜色键。如果用户为ColorKey参数提供了参数,我们将使用该值作为图像的ColorKey。这通常只是一个颜色RGB值,如(255,255,255)表示白色。也可以将值-1作为Colorkey传递。在这种情况下,该函数将在图像的顶部像素处查找颜色,并使用该颜色作为Colorkey。**

def load_sound(name):
    class NoneSound:
        def play(self):
            pass

    if not pg.mixer or not pg.mixer.get_init():
        return NoneSound()

    fullname = os.path.join(data_dir, name)
    sound = pg.mixer.Sound(fullname)

    return sound

接下来是加载声音文件的函数。此函数所做的第一件事是检查 pygame.mixerpygame module for loading and playing sounds 模块已正确导入。如果不是,它将返回一个具有虚拟Play方法的小类实例。这将足够像一个普通的Sound对象一样运行这个游戏,而不需要任何额外的错误检查。

此函数类似于图像加载函数,但处理一些不同的问题。首先,我们创建声音图像的完整路径,并加载声音文件。然后,我们只需返回加载的Sound对象。

游戏对象类

在这里,我们创建两个类来表示游戏中的对象。这个游戏几乎所有的逻辑都归结于这两类人。在这里,我们将逐一介绍它们。**

class Fist(pg.sprite.Sprite):
    """moves a clenched fist on the screen, following the mouse"""

    def __init__(self):
        pg.sprite.Sprite.__init__(self)  # call Sprite initializer
        self.image, self.rect = load_image("fist.png", -1)
        self.fist_offset = (-235, -80)
        self.punching = False

    def update(self):
        """move the fist based on the mouse position"""
        pos = pg.mouse.get_pos()
        self.rect.topleft = pos
        self.rect.move_ip(self.fist_offset)
        if self.punching:
            self.rect.move_ip(15, 25)

    def punch(self, target):
        """returns true if the fist collides with the target"""
        if not self.punching:
            self.punching = True
            hitbox = self.rect.inflate(-5, -5)
            return hitbox.colliderect(target.rect)

    def unpunch(self):
        """called to pull the fist back"""
        self.punching = False

在这里,我们创建一个类来表示玩家的拳头。它是从 Sprite class included in the pygame.spritepygame module with basic game object classes module. The _ _init__`函数在创建此类的新实例时调用。我们要做的第一件事是确保调用 `__init__ function for our base class. This allows the Sprite's `_ _init__`函数,以准备我们的对象作为精灵使用。这个游戏使用了一个精灵绘图组类。这些类可以绘制具有“image”和“rect”属性的精灵。只需更改这两个属性,渲染器就会在当前位置绘制当前图像。

所有的精灵都有一个 update() 方法。该函数通常每帧调用一次。它是您应该放置移动和更新子画面变量的代码的地方。这个 update() 方法将拳头移动到鼠标指针所在的位置。如果拳头处于“出拳”状态,它还会稍微偏移拳头的位置。

以下两个函数 punch()unpunch() 更改拳头的出拳状态。这个 punch() 如果拳头与给定的目标精灵发生碰撞,则方法也会返回True值。**

class Chimp(pg.sprite.Sprite):
    """moves a monkey critter across the screen. it can spin the
    monkey when it is punched."""

    def __init__(self):
        pg.sprite.Sprite.__init__(self)  # call Sprite intializer
        self.image, self.rect = load_image("chimp.png", -1, 4)
        screen = pg.display.get_surface()
        self.area = screen.get_rect()
        self.rect.topleft = 10, 90
        self.move = 18
        self.dizzy = False

    def update(self):
        """walk or spin, depending on the monkeys state"""
        if self.dizzy:
            self._spin()
        else:
            self._walk()

    def _walk(self):
        """move the monkey across the screen, and turn at the ends"""
        newpos = self.rect.move((self.move, 0))
        if not self.area.contains(newpos):
            if self.rect.left < self.area.left or self.rect.right > self.area.right:
                self.move = -self.move
                newpos = self.rect.move((self.move, 0))
                self.image = pg.transform.flip(self.image, True, False)
        self.rect = newpos

    def _spin(self):
        """spin the monkey image"""
        center = self.rect.center
        self.dizzy = self.dizzy + 12
        if self.dizzy >= 360:
            self.dizzy = False
            self.image = self.original
        else:
            rotate = pg.transform.rotate
            self.image = rotate(self.original, self.dizzy)
        self.rect = self.image.get_rect(center=center)

    def punched(self):
        """this will cause the monkey to start spinning"""
        if not self.dizzy:
            self.dizzy = True
            self.original = self.image

这个 Chimp 类做的工作比拳头多一点,但并不比拳头更复杂。这门课将在屏幕上来回移动黑猩猩。当猴子被打的时候,他会旋转起来,达到令人兴奋的效果。此类也从基类派生 Sprite 类,并且初始化方式与Fist相同。在初始化时,类还将属性“Area”设置为显示屏的大小。

这个 update function for the chimp simply looks at the current "dizzy" state, which is true when the monkey is spinning from a punch. It calls either the _ 自旋`或 `_walk 方法。这些函数以下划线为前缀。这只是一个标准的Python习惯用法,它表明这些方法应该仅由 Chimp 班级。我们甚至可以给它们加双下划线,这将告诉Python真正尝试使它们成为私有方法,但我们不需要这样的保护。:)

这个 _walk 方法通过将当前RECT移动给定的偏移量来为猴子创建新位置。如果这个新位置越过屏幕的显示区域,它会反转移动偏移量。它还使用 pygame.transform.flip()flip vertically and horizontally 功能。这是一种粗略的效果,让猴子看起来像是在转向他正在移动的方向。

这个 _spin 方法在猴子当前“头晕”时被调用。眩晕属性用于存储当前的旋转量。当猴子一直旋转(360度)时,它会将猴子图像重置为原始的、未旋转的版本。在调用 pygame.transform.rotate()rotate an image 函数,您将看到代码对该函数进行了本地引用,该函数被简单地命名为“Rotate”。对于本例来说,没有必要这样做,这里只是为了保持下面一行的长度稍微短一点。请注意,在调用 rotate 函数,我们总是从原来的猴子形象开始旋转。当旋转时,会有轻微的质量损失。反复旋转相同的图像,质量会一次比一次差。此外,当旋转图像时,图像的大小实际上会发生变化。这是因为图像的角将被旋转出来,使图像更大。我们确保新图像的中心与旧图像的中心匹配,因此它可以旋转而不移动。

最后一种方法是 punched() 它告诉精灵进入它的眩晕状态。这将导致图像开始旋转。它还制作名为“原始”的当前图像的副本。

初始化所有内容

在我们可以对pyGame做更多工作之前,我们需要确保它的模块已经初始化。在这种情况下,我们还将打开一个简单的图形窗口。现在我们正处于 main() 程序的功能,它实际上运行所有的东西。**

pg.init()
screen = pg.display.set_mode((1280, 480), pg.SCALED)
pg.display.set_caption("Monkey Fever")
pg.mouse.set_visible(False)

要初始化的第一行 梨果 为我们处理了一些工作。它检查进口的 梨果 模块,并尝试对每个模块进行初始化。可以返回并检查模块是否未能初始化,但我们不会在这里费心。还可以采取更多的控制并手动初始化每个特定的模块。这种类型的控制通常是不需要的,但如果您愿意,可以使用。

接下来,我们设置显示图形模式。请注意, pygame.displaypygame module to control the display window and screen 模块用于控制所有的显示设置。在本例中,我们要求的是1280x480的窗口, SCALED 显示标志。对于比窗口大得多的显示器,这会自动放大窗口。

最后,我们设置窗口标题并关闭窗口的鼠标光标。非常基本的做法,现在我们有一个小的黑色窗口准备好做我们的命令。通常,光标默认为可见,因此没有必要真正设置状态,除非我们想要隐藏它。

创建背景

我们的节目将在背景中有短信。对于我们来说,创建一个单一的表面来表示背景并重复使用它会很好。第一步是创建曲面。**

background = pg.Surface(screen.get_size())
background = background.convert()
background.fill((170, 238, 187))

这为我们创建了一个与显示窗口大小相同的新表面。请注意额外的调用 convert() 在创建曲面之后。不带参数的转换将确保我们的背景与显示窗口的格式相同,这将给我们带来最快的结果。

我们还用某种绿色填充整个背景。Fill()函数通常将RGB三元组作为参数,但支持许多输入格式。请参阅 pygame.Colorpygame object for color representations 适用于所有颜色格式。

将文本置于背景,居中

现在我们有了一个背景表面,让我们将文本呈现到它上面。我们只有在看到 pygame.fontpygame module for loading and rendering fonts 模块已正确导入。如果没有,我们就跳过这一节。**

if pg.font:
    font = pg.font.Font(None, 64)
    text = font.render("Pummel The Chimp, And Win $$$", True, (10, 10, 10))
    textpos = text.get_rect(centerx=background.get_width() / 2, y=10)
    background.blit(text, textpos)

如您所见,完成此任务需要几个步骤。首先,我们必须创建字体对象并将其渲染到新的图面中。然后我们找到新表面的中心,并将其粘贴到背景上。

该字体是使用 font 模块的 Font() 构造函数。通常,您会将TrueType字体文件的名称传递给此函数,但我们也可以传递 None ,它将使用默认字体。这个 Font 构造函数还需要知道我们想要创建的字体的大小。

然后,我们将该字体呈现到新的图面中。这个 render 函数创建一个适合文本大小的新图面。在这种情况下,我们还告诉Render创建抗锯齿文本(以获得良好的平滑外观)并使用深灰色。

接下来,我们需要找到文本在显示屏上的居中位置。我们从文本维度创建一个“Rect”对象,这使我们可以轻松地将其分配给屏幕中心。

最后,我们将文本复制或粘贴到背景图像上。

安装完成时显示背景

我们的屏幕上仍然有一扇黑色的窗户。让我们在等待加载其他资源时展示我们的背景。**

screen.blit(background, (0, 0))
pygame.display.flip()

这将使我们的整个背景显示在显示窗口上。Blit是不言而喻的,但这个翻转套路呢?

在PYGAME中,显示界面的更改不会立即可见。正常情况下,必须在已更改的区域中更新显示,以便用户可以看到它们。在本例中, flip() 函数工作得很好,因为它只处理整个窗口区域。

准备游戏对象

在这里,我们创建游戏将需要的所有对象。

whiff_sound = load_sound("whiff.wav")
punch_sound = load_sound("punch.wav")
chimp = Chimp()
fist = Fist()
allsprites = pg.sprite.RenderPlain((chimp, fist))
clock = pg.time.Clock()

首先,我们使用 load_sound 我们在上面定义的函数。然后,我们为每个精灵类创建一个实例。最后,我们创建了一个精灵 Group 它将包含我们所有的精灵。

我们实际上使用了一个特殊的精灵组,名为 RenderPlain 。这个精灵组可以将它包含的所有精灵绘制到屏幕上。它被称为 RenderPlain 因为实际上存在更高级的渲染组。但对于我们的游戏,我们只需要简单的绘画。我们通过传递一个列表来创建名为“allsprites”的组,其中包含应该属于该组的所有精灵。我们稍后可以在这个组中添加或删除精灵,但在这个游戏中我们不需要这样做。

这个 clock 我们创建的对象将被用来帮助控制游戏的帧速率。我们将在游戏的主循环中使用它,以确保它不会运行得太快。

主环路

这里没什么,只有一个无限循环。**

going = True
while going:
    clock.tick(60)

所有的游戏都在某种循环中运行。通常的顺序是检查计算机和用户输入的状态,移动和更新所有对象的状态,然后将它们绘制到屏幕上。您将看到这个示例没有什么不同。

我们还打电话给我们的 clock 对象,它将确保我们的游戏运行速度不会超过每秒60帧。

处理所有输入事件

这是处理事件队列的一个极其简单的例子。**

for event in pg.event.get():
    if event.type == pg.QUIT:
        going = False
    elif event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE:
        going = False
    elif event.type == pg.MOUSEBUTTONDOWN:
        if fist.punch(chimp):
            punch_sound.play()  # punch
            chimp.punched()
        else:
            whiff_sound.play()  # miss
    elif event.type == pg.MOUSEBUTTONUP:
        fist.unpunch()

首先,我们从pyGame中获取所有可用的事件,并遍历每个事件。前两个测试查看用户是否已退出游戏或按下了退出键。在这些情况下,我们只是设置了 goingFalse ,让我们走出了无限循环。

接下来,我们只需检查鼠标按钮是按下还是松开。如果按钮被按下,我们会询问第一个物体是否与黑猩猩相撞。我们播放适当的音效,如果猴子被击中,我们告诉他开始旋转(通过调用他的 punched() 方法)。

更新雪碧

allsprites.update()

雪碧组有一个 update() 方法,该方法只调用它所包含的所有子画面的更新方法。每个对象都将四处移动,具体取决于它们所处的状态。这就是黑猩猩会从一边走到另一边的地方,或者如果他最近被打了一下,它会旋转得更远。

绘制整个场景

现在所有的对象都在正确的位置,是时候绘制它们了。**

screen.blit(background, (0, 0))
allsprites.draw(screen)
pygame.display.flip()

第一个Blit调用将在整个屏幕上绘制背景。这将擦除我们在前一帧中看到的所有内容(效率稍低,但对这个游戏来说已经足够好了)。接下来,我们将 draw() 方法的精灵容器。因为这个精灵容器实际上是“DrawPlain”精灵组的一个实例,所以它知道如何绘制精灵。最后,我们 flip() 将PYGAME的软件内容双缓冲到屏幕上。这使得我们绘制的所有内容一次都可见。

游戏结束

用户已退出,是时候清理了。**

pg.quit()

清理运行中的游戏 梨果 非常简单。因为所有变量都是自动销毁的,所以我们实际上不需要做任何事情,只需调用 pg.quit() 明确清理了pyGame的内部结构。




Edit on GitHub