Pygame-Pyrhon-游戏开发即时入门-全-

Pygame Pyrhon 游戏开发即时入门(全)

原文:zh.annas-archive.org/md5/21dfb011fb3bbc021c45d7dacabf3690

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Pygame 是一个有趣的 Python API,我们可以用它轻松创建简单游戏。它支持绘图、声音、图像、动画、OpenGL 等。本书将探索 Pygame 的许多方面,包含 20 多个配方。本书是自包含的,并且仅假设你具备基本的 Python 编程知识。我们鼓励你尝试所有示例游戏,并根据你的需求进行修改。

本书涵盖内容

准备你的开发环境(简单) 是一个基本的配方,将帮助你安装开发环境中所需的所有必要软件。

运行简单游戏(简单) 是我们将创建一个基本游戏以开始的地方。这个游戏遵循了 Hello world 示例的传统,展示了字体和屏幕管理。

使用 Pygame 绘图(简单) 教我们如何绘制基本形状,如矩形、椭圆形、圆形、线条等。我们还将学习有关颜色和颜色管理的重要信息。

动画对象(简单) 从本书的“击中角色!”跑步游戏示例开始。我们将学习如何在我们的有趣小游戏中动画化对象。

使用字体(简单) 是关于字体和字体管理。

使用 Matplotlib 与 Pygame(简单) 允许我们在 Pygame 中利用惊人的开源 Python 绘图库 Matplotlib 绘制图表。Matplotlib 功能强大,提供了大量绘图和可视化的功能。

访问表面像素数据(中级) 展示了如何操作存储在特殊数组中的像素数据以进行高效绘图。在这个配方中介绍了高效的 NumPy 开源 Python 数学库。

访问声音数据(简单) 让我们以数组的形式处理音频数据。这个配方要求你在运行此配方的示例代码时仔细聆听。

播放电影(中级) 指导我们完成播放电影所需的步骤。在游戏中播放电影将为你的游戏增添的价值是无价的。

Pygame 在 Android 上(中级) 介绍了 Android 的奇妙世界。Android 是一个由 Google 创建的知名开源移动计算框架。在这个过程中,我们创建了一个 Android 游戏示例。

人工智能(中级) 是当今的热门话题。我们直接使用流行的 Scikits-learn 开源 Python 框架深入探讨。当然,这是一个巨大的话题,可能需要数年才能掌握。我们只展示了冰山一角,并尝试了聚类。

绘制精灵(中级) 介绍了精灵管理。精灵是来自计算机图形学的术语,表示我们可以在屏幕上操作的二维可见对象。精灵可以分组以便于管理。

使用 Pygame 和 OpenGL(高级) 帮助我们掌握 OpenGL,这是一个著名的开源图形框架,可在各种平台和编程语言上使用。OpenGL 在行业中用于渲染复杂二维和三维对象。

检测碰撞(中级) 对于良好的游戏开发至关重要,无论我们是撞到一辆车,部署空对空导弹,还是踢足球。我们将提供如何轻松检测碰撞的技巧。

添加网络功能(高级) 将带我们了解基本的客户端-服务器设置。我们将使用出色的开源 Python Twisted 框架来创建一个网络游戏。这个游戏要求我们猜测一个只在服务器端已知单词。

调试你的游戏(中级) 提供了你创建健壮游戏所需的生命拯救调试技术。调试很有压力,所以当你有可靠的工具时会有所帮助。我们将介绍这样的工具。

对你的代码进行性能分析(中级) 是你应该做的事情,以确保你的游戏表现良好。本食谱中提供了提示和想法,以简化性能分析过程。

使用 Pygame 的拼图游戏(高级) 展示了一个交互式客户端-服务器游戏,基于之前所学到的所有材料。

使用 Pygame 模拟(高级) 以简单而有趣的方式模拟生活。

你需要这本书什么

这本书相当独立。安装所需软件的说明贯穿全书。

这本书面向谁

这本书是为对学习如何使用所有附加功能创建游戏感兴趣的 Python 程序员而编写的。即使你对 Python 不太了解,这本书也应该容易理解。

惯例

在这本书中,你会找到许多不同风格的文本,以区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。

文本中的代码单词如下所示:“我们可以使用以下 sort 命令进行排序。”

代码块如下设置:

import os, pygame
from pygame.locals import *
import numpy
from scipy import ndimage

def get_pixar(arr, weights):
  states = ndimage.convolve(arr, weights, mode='wrap')

  bools = (states == 13) | (states == 12 ) | (states == 3)

  return bools.astype(int)

任何命令行输入或输出都如下所示:

ffmpeg -i <infile> -vcodec mpeg1video -acodec libmp3lame -intra <outfile.mpg>

新术语重要词汇 以粗体显示。你在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“你应该看到Hello被显示,然后是1 重要消息19 重要消息”。

注意

警告或重要注意事项以如下框的形式出现。

小贴士

小贴士和技巧看起来是这样的。

读者反馈

我们欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢什么或可能不喜欢什么。读者反馈对我们开发你真正从中受益的标题非常重要。

要向我们发送一般反馈,只需发送一封电子邮件到 <feedback@packtpub.com>,并在邮件主题中提及书名。

如果你有一本书需要,并希望我们看到它出版,请通过www.packtpub.com上的建议标题表单或发送电子邮件至 <suggest@packtpub.com> 给我们留言。

如果你在一个你具有专业知识的话题上感兴趣,无论是写作还是为书籍做出贡献,请参阅我们在www.packtpub.com/authors上的作者指南。

客户支持

现在你已经是 Packt 书籍的骄傲拥有者,我们有许多事情可以帮助你从购买中获得最大收益。

下载示例代码

你可以从www.PacktPub.com的账户下载你购买的所有 Packt 书籍的示例代码文件。如果你在其他地方购买了这本书,你可以访问www.PacktPub.com/support并注册,以便将文件直接通过电子邮件发送给你。

勘误

尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果你在我们的书中发现错误——可能是文本或代码中的错误——如果你能向我们报告这一点,我们将不胜感激。通过这样做,你可以帮助其他读者避免挫败感,并帮助我们改进本书的后续版本。如果你发现任何勘误,请通过访问www.packtpub.com/support,选择你的书籍,点击勘误提交表链接,并输入你的勘误详情来报告它们。一旦你的勘误得到验证,你的提交将被接受,勘误将被上传到我们的网站,或添加到该标题的勘误部分下的现有勘误列表中。任何现有的勘误都可以通过从www.packtpub.com/support选择你的标题来查看。

盗版

互联网上版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果你在网上遇到任何我们作品的非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过 <copyright@packtpub.com> 联系我们,并提供疑似盗版材料的链接。

我们感谢你在保护我们的作者和为你提供有价值内容的能力方面所提供的帮助。

问题

如果你在这本书的任何方面遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决。

第一章. Instant Pygame for Python Game Development How-to

欢迎使用 Pygame for Python Game Development How-to。这本书是为那些想要快速、轻松地使用 Pygame 创建游戏并熟悉其重要方面的开发者而编写的。你通常会学到以下典型内容:

  • Pygame 基础

  • 精灵

  • OpenGL

Pygame 是由 Pete Shinners 编写的 Python 框架的一部分,正如其名称所暗示的,可以用来创建视频游戏。Pygame 自 2004 年以来是免费和开源的,并受 GPL 许可证的许可,这意味着你可以基本上制作任何类型的游戏。Pygame 是建立在 Simple DirectMedia LayerSDL)之上的。SDL 是一个 C 框架,它为包括 Linux、Mac OS X 和 Windows 在内的各种操作系统上的图形、声音、键盘和其他输入设备提供了访问权限。

准备你的开发环境(简单)

我们将安装 Python、Pygame 以及我们需要的其他软件。

准备工作

在我们安装 Pygame 之前,我们需要确保 Python 已经安装。在某些操作系统上,Python 已经预装了。Pygame 应该与所有 Python 版本兼容。我们还需要 NumPy 数值库。我是 Packt Publishing 出版的两本关于 NumPy 的书的作者——NumPy 初学者指南NumPy 烹饪书。请参考这些书籍以获取更多关于 NumPy 的信息。

如何操作...

  • 在 Debian 和 Ubuntu 上安装

    Python 可能已经在 Debian 和 Ubuntu 上安装,但通常开发头文件没有安装。在 Debian 和 Ubuntu 上,使用以下命令安装 python 和 python-dev:

    sudo apt-get install python
    sudo apt-get install python-dev
    
    

    Pygame 可以在 Debian 存档 packages.qa.debian.org/p/pygame.html 中找到。我们可以使用以下命令安装 NumPy:

    sudo apt-get install python-numpy
    
    
  • 在 Windows 上安装

    Windows 的 Python 安装程序可以在 www.python.org/download 找到。在这个网站上,我们还可以找到 Mac OS X 和 Linux、Unix、Mac OS X 的源代码压缩包。

    从 Pygame 网站 (www.pygame.org/download.shtml),我们可以下载适用于我们使用的 Python 版本的相应二进制安装程序。

    从 SourceForge 网站下载 Windows 的 NumPy 安装程序(sourceforge.net/projects/numpy/files/)。

  • 在 Mac 上安装 Python

    Python 预装在 Mac OS X 上。我们也可以通过 MacPorts、Fink 或类似的项目来获取 Python。例如,我们可以通过运行以下命令来安装 Python 2.6 版本的端口:

    sudo port install python26
    
    

    二进制 Pygame 软件包可以从 www.pygame.org/download.shtml 下载,适用于 Mac OS X 10.3 及以上版本。我们可以从 SourceForge 网站获取 NumPy 安装程序(sourceforge.net/projects/numpy/files/)。通常,最新版本是最好的。

  • 从源代码安装

    Pygame 使用distutils系统进行编译和安装。要使用默认选项开始安装 Pygame,只需运行以下命令:

    python setup.py
    
    

    如果您需要有关可用选项的更多信息,请键入以下命令:

    python setup.py help
    
    

    为了编译代码,您需要为您的操作系统安装一个编译器。设置此内容超出了本书的范围。有关在 Windows 上编译 Pygame 的更多信息,请参阅pygame.org/wiki/CompileWindows。有关在 Mac OS X 上编译 Pygame 的更多信息,请参阅pygame.org/wiki/MacCompile

运行一个简单的游戏(简单)

我们将创建一个简单的游戏,我们将在书中进一步改进它。正如编程书籍的传统做法,我们将从一个 Hello World!示例开始。这本身不是一个游戏。重要的是要注意所谓的“主游戏循环”,在这里所有动作发生,以及使用Font模块来渲染文本。在这个程序中,我们将操作 Pygame 的Surface对象,用于绘图,并且我们将处理退出事件。

如何做...

  1. 导入:首先,我们将导入所需的 Pygame 模块。如果 Pygame 安装正确,我们应该不会出现错误,否则请返回到准备你的开发环境(简单)食谱:

    import pygame, sys
    from pygame.locals import *
    
  2. 初始化:我们将通过创建一个 400 x 300 像素的显示并设置窗口标题为Hello World来初始化 Pygame:

    pygame.init()
    screen = pygame.display.set_mode((400, 300))
    
    pygame.display.set_caption('Hello World!')
    
  3. 主游戏循环:游戏通常有一个游戏循环,它永远运行,直到例如发生退出事件。在这个例子中,我们将只在坐标(100,100)处设置一个带有文本Hello world的标签。文本的字体大小为 19,红色,并回退到默认字体:

    while True: 
       sys_font = pygame.font.SysFont("None", 19)
       rendered = sys_font.render('Hello World', 0, (255, 100, 100))
       screen.blit(rendered, (100, 100))
    
       for event in pygame.event.get():
          if event.type == QUIT:
             pygame.quit()
             sys.exit()
    
       pygame.display.update()
    

    我们得到以下截图作为最终结果:

    如何做...

    以下是为 Hello World 示例提供的完整代码:

    import pygame, sys
    from pygame.locals import *
    
    pygame.init()
    screen = pygame.display.set_mode((400, 300))
    
    pygame.display.set_caption('Hello World!')
    
    while True: 
       sysFont = pygame.font.SysFont("None", 19)
       rendered = sysFont.render('Hello World', 0, (255, 100, 100))
       screen.blit(rendered, (100, 100))
       for event in pygame.event.get():
          if event.type == QUIT:
             pygame.quit()
             sys.exit()
    
       pygame.display.update()
    

它是如何工作的...

这可能看起来不多,但我们在这一步学到了很多。通过审查的函数总结在下表中:

函数 描述
pygame.init() 此函数执行初始化,需要在调用任何其他 Pygame 函数之前调用。
pygame.display.set_mode((400, 300)) 此函数创建一个所谓的Surface对象进行绘制。我们给这个函数一个表示表面宽度和高度的元组。
pygame.display.set_caption('Hello World!') 此函数将窗口标题设置为指定的字符串值。
pygame.font.SysFont("None", 19) 此函数从逗号分隔的字体列表(在这种情况下为无)和字体大小参数创建系统字体。
sysFont.render('Hello World', 0, (255, 100, 100)) 此函数在表面上绘制文本。第二个参数指示是否使用抗锯齿。最后一个参数是一个表示颜色 RGB 值的元组。
screen.blit(rendered, (100, 100)) 此函数在表面上绘制。
pygame.event.get() 此函数获取一个Event对象列表。事件代表系统中的某些特殊事件,例如用户退出游戏。
pygame.quit() 此函数清理 Pygame 使用的资源。在退出游戏之前调用此函数。
pygame.display.update() 此函数刷新表面。

使用 Pygame 进行绘图(简单)

在我们开始创建酷炫的游戏之前,我们需要了解 Pygame 的绘图功能。正如我们在前面的菜谱中注意到的,在 Pygame 中我们在Surface对象上绘制。有无数种绘图选项——不同的颜色、矩形、多边形、线条、圆形、椭圆、动画和不同的字体。

如何操作...

以下步骤将帮助你了解你可以使用 Pygame 进行的不同绘图选项:

  1. 导入:我们需要 NumPy 库来随机生成用于颜色的 RGB 值,因此我们将添加一个额外的导入:

    import numpy 
    
  2. 初始化颜色:使用 NumPy 生成包含三个 RGB 值的四个元组:

    colors = numpy.random.randint(0, 255, size=(4, 3))
    

    然后定义白色为变量:

    WHITE = (255, 255, 255)
    
  3. 设置背景颜色:我们可以使用以下代码使整个屏幕变为白色:

    screen.fill(WHITE)
    
  4. 绘制圆形:使用我们生成的第一种颜色在窗口中心绘制一个圆形:

    pygame.draw.circle(screen, colors[0], (200, 200), 25, 0)
    
  5. 绘制线条:要绘制线条,我们需要一个起点和一个终点。我们将使用第二种随机颜色,并给线条设置厚度为3

    pygame.draw.line(screen, colors[1], (0, 0), (200, 200), 3)
    
  6. 绘制矩形:绘制矩形时,我们需要指定颜色、矩形左上角的坐标以及其尺寸:

    pygame.draw.rect(screen, colors[2], (200, 0, 100, 100))
    
  7. 绘制椭圆:你可能惊讶地发现,绘制椭圆需要与矩形类似的参数。这些参数实际上描述了一个可以围绕椭圆绘制的想象中的矩形:

    pygame.draw.ellipse(screen, colors[3], (100, 300, 100, 50), 2)
    

    使用随机颜色绘制的包含圆形、线条、矩形和椭圆的窗口结果:

    如何操作...

    绘图演示的代码如下:

    import pygame, sys
    from pygame.locals import *
    import numpy 
    
    pygame.init()
    screen = pygame.display.set_mode((400, 400))
    
    pygame.display.set_caption('Drawing with Pygame')
    colors = numpy.random.randint(0, 255, size=(4, 3))
    
    WHITE = (255, 255, 255)
    
    #Make screen white
    screen.fill(WHITE)
    
    #Circle in the center of the window
    pygame.draw.circle(screen, colors[0], (200, 200), 25, 0)
    
    # Half diagonal from the upper-left corner to the center
    pygame.draw.line(screen, colors[1], (0, 0), (200, 200), 3)
    
    pygame.draw.rect(screen, colors[2], (200, 0, 100, 100))
    
    pygame.draw.ellipse(screen, colors[3], (100, 300, 100, 50), 2)
    
    while True: 
       for event in pygame.event.get():
          if event.type == QUIT:
             pygame.quit()
             sys.exit()
    
       pygame.display.update()
    

简单对象动画

现在我们已经知道了如何使用 Pygame 进行绘图,是时候尝试一些更动态的内容了。大多数游戏,即使是静态的,都有一定程度的动画。从程序员的角度来看,动画不过是将对象在不同的时间和不同的位置显示出来,从而模拟运动。

Pygame 提供了一个Clock对象,用于管理每秒绘制多少帧。这确保了动画与用户 CPU 的速度无关。

如何操作...

我们将加载一张图片,并使用 NumPy 再次定义一个围绕屏幕的顺时针路径:

  1. 首先,我们需要创建一个时钟,如下所示:

    clock = pygame.time.Clock()
    
  2. 加载图像:作为本书附带源代码的一部分,应该有一张头像图片。我们将加载这张图片并在屏幕上移动它:

    img = pygame.image.load('head.jpg')
    
  3. 初始化数组:我们将定义一些数组来保存动画期间图像位置的坐标,我们希望在动画中放置图像的位置。由于对象将被移动,路径有四个逻辑部分:右、下、左和上。每个部分将有 40 个等距步骤。我们将初始化这些部分的所有值为 0:

    steps = numpy.linspace(20, 360, 40).astype(int)
    right = numpy.zeros((2, len(steps)))
    down = numpy.zeros((2, len(steps)))
    left = numpy.zeros((2, len(steps)))
    up = numpy.zeros((2, len(steps)))
    
  4. 设置位置坐标:设置图像位置坐标是微不足道的。然而,有一个需要注意的技巧,[::-1]表示法会导致数组元素的顺序反转:

    right[0] = steps
    right[1] = 20
    down[0] = 360
    down[1] = steps
    
    left[0] = steps[::-1]
    left[1] = 360
    
    up[0] = 20
    up[1] = steps[::-1]
    
  5. 连接部分:路径部分可以连接,但在我们能够这样做之前,数组必须使用 T 操作符进行转置:

    pos = numpy.concatenate((right.T, down.T, left.T, up.T))
    
  6. 设置时钟速率:在主事件循环中,我们将让时钟以每秒 30 帧的速度滴答:

       clock.tick(30)
    

    以下截图是移动头部的:

    如何操作...

    注意

    你应该能够在www.youtube.com/watch?v=m2TagGiq1fs上观看这个动画的电影。

    此示例的代码几乎使用了我们迄今为止学到的所有内容,但仍然足够简单,易于理解:

    import pygame, sys
    from pygame.locals import *
    import numpy
    
    pygame.init()
    clock = pygame.time.Clock()
    screen = pygame.display.set_mode((400, 400))
    
    pygame.display.set_caption('Animating Objects')
    img = pygame.image.load('head.jpg')
    
    steps = numpy.linspace(20, 360, 40).astype(int)
    right = numpy.zeros((2, len(steps)))
    down = numpy.zeros((2, len(steps)))
    left = numpy.zeros((2, len(steps)))
    up = numpy.zeros((2, len(steps)))
    
    right[0] = steps
    right[1] = 20
    
    down[0] = 360
    down[1] = steps
    
    left[0] = steps[::-1]
    left[1] = 360
    
    up[0] = 20
    up[1] = steps[::-1]
    
    pos = numpy.concatenate((right.T, down.T, left.T, up.T))
    i = 0
    
    while True: 
       # Erase screen
       screen.fill((255, 255, 255))
    
       if i >= len(pos):
          i = 0
    
       screen.blit(img, pos[i])
       i += 1
    
       for event in pygame.event.get():
          if event.type == QUIT:
             pygame.quit()
             sys.exit()
    
       pygame.display.update()
       clock.tick(30)
    

它是如何工作的...

在这个食谱中,我们了解了一些关于动画的知识。我们学到的最重要的概念是时钟。我们使用的新的函数描述如下表:

函数 描述
pygame.time.Clock() 此函数创建一个游戏时钟
numpy.linspace(20, 360, 40) 此函数创建一个包含 20 和 360 之间 40 个等距值的数组
numpy.zeros((2, len(steps))) 此函数创建一个指定维度的数组,并用零填充
numpy.concatenate((right.T, down.T, left.T, up.T)) 此函数将数组连接起来形成一个新的数组
clock.tick(30) 此函数执行游戏时钟的滴答,其中 30 是每秒的帧数

使用字体(简单)

经常需要显示一些文本,例如计数器或消息。

如何操作...

Pygame 有一个 font 模块可以帮助我们显示文本。

  1. 创建字体:我们可以通过指定字体文件名和字体大小作为构造函数参数来创建字体:

    font = pygame.font.Font('freesansbold.ttf', 32)
    
  2. 显示文本:由于我们在之前的食谱中使图像在边缘移动,所以在屏幕中央显示计数器和图像位置,背景为蓝色,文字为红色,将会非常棒。以下代码片段实现了这一点:

    text = "%d %d %d" % (i, pos[i][0], pos[i][1])
    rendered = font.render(text, True, RED, BLUE)
    screen.blit(rendered, (150, 200))
    

    注意

    动画截图如下,也应该在www.youtube.com/watch?v=xhjfcFhaXN0上。

    如何操作...

    代码几乎与之前的食谱相同,只是增加了创建和显示字体的代码:

    import pygame, sys
    from pygame.locals import *
    import numpy
    
    pygame.init()
    clock = pygame.time.Clock()
    screen = pygame.display.set_mode((400, 400))
    
    pygame.display.set_caption('Animating Objects')
    img = pygame.image.load('head.jpg')
    
    steps = numpy.linspace(20, 360, 40).astype(int)
    right = numpy.zeros((2, len(steps)))
    down = numpy.zeros((2, len(steps)))
    left = numpy.zeros((2, len(steps)))
    up = numpy.zeros((2, len(steps)))
    
    right[0] = steps
    right[1] = 20
    
    down[0] = 360
    down[1] = steps
    left[0] = steps[::-1]
    left[1] = 360
    
    up[0] = 20
    up[1] = steps[::-1]
    
    pos = numpy.concatenate((right.T, down.T, left.T, up.T))
    i = 0
    
    # create a font
    font = pygame.font.Font('freesansbold.ttf', 32)
    RED = (255, 0, 0)
    BLUE = (0, 0, 255)
    
    while True: 
       # Erase screen
       screen.fill((255, 255, 255))
    
       if i >= len(pos):
          i = 0
    
       screen.blit(img, pos[i])
    
       # displaying text in the center of the screen
       text = "%d %d %d" % (i, pos[i][0], pos[i][1])
       rendered = font.render(text, True, RED, BLUE)
       screen.blit(rendered, (150, 200))
       i += 1
    
       for event in pygame.event.get():
          if event.type == QUIT:
             pygame.quit()
             sys.exit()
    
       pygame.display.update()
       clock.tick(30)
    

使用 Matplotlib 和 Pygame(简单)

Matplotlib 是一个易于绘图的开源库。我们可以将 Matplotlib 集成到 Pygame 游戏中并创建各种图表。您可以在matplotlib.org/users/installing.html找到 Matplotlib 的安装说明。

如何操作...

在本食谱中,我们将使用前一个食谱中的位置坐标并绘制它们的图表:

  1. 使用非交互式后端:为了将 Matplotlib 与 Pygame 集成,我们需要使用非交互式后端,否则 Matplotlib 将默认为我们提供一个 GUI 窗口。我们将导入主 Matplotlib 模块并调用use函数。此函数必须在导入主matplotlib模块之后以及导入其他matplotlib模块之前立即调用:

    import matplotlib
    
    matplotlib.use("Agg")
    
  2. 创建 Matplotlib 画布:非交互式图表可以绘制在 Matplotlib 画布上。创建此画布需要导入、一个图形和一个子图。我们将指定图形大小为 3 英寸乘以 3 英寸。更多详细信息可以在本食谱的末尾找到:

    import matplotlib.pyplot as plt
    import matplotlib.backends.backend_agg as agg
    
    fig = plt.figure(figsize=[3, 3])
    ax = fig.add_subplot(111)
    canvas = agg.FigureCanvasAgg(fig)
    
  3. 绘图数据:在非交互式模式下,绘图比默认模式复杂一些。由于我们需要重复绘图,将绘图代码组织在函数中是有意义的。图表最终绘制在画布上。画布给我们的设置增加了一些复杂性。在本例的末尾,您可以找到关于函数的更详细说明:

    def plot(data):
       ax.plot(data)
       canvas.draw()
       renderer = canvas.get_renderer()
    
       raw_data = renderer.tostring_rgb()
       size = canvas.get_width_height()
    
       return pygame.image.fromstring(raw_data, size, "RGB")
    

    注意

    以下截图显示了动画的实际效果。您还可以在 YouTube 上观看一个屏幕录制视频,链接为www.youtube.com/watch?v=t6qTeXxtnl4

    如何操作...

    修改后,我们得到以下代码:

    import pygame, sys
    from pygame.locals import *
    import numpy
    import matplotlib
    
    matplotlib.use("Agg")
    
    import matplotlib.pyplot as plt
    import matplotlib.backends.backend_agg as agg
    
    fig = plt.figure(figsize=[3, 3])
    ax = fig.add_subplot(111)
    canvas = agg.FigureCanvasAgg(fig)
    
    def plot(data):
       ax.plot(data)
       canvas.draw()
       renderer = canvas.get_renderer()
    
       raw_data = renderer.tostring_rgb()
       size = canvas.get_width_height()
    
       return pygame.image.fromstring(raw_data, size, "RGB")
    
    pygame.init()
    clock = pygame.time.Clock()
    screen = pygame.display.set_mode((400, 400))
    
    pygame.display.set_caption('Animating Objects')
    img = pygame.image.load('head.jpg')
    
    steps = numpy.linspace(20, 360, 40).astype(int)
    right = numpy.zeros((2, len(steps)))
    down = numpy.zeros((2, len(steps)))
    left = numpy.zeros((2, len(steps)))
    up = numpy.zeros((2, len(steps)))
    
    right[0] = steps
    right[1] = 20
    
    down[0] = 360
    down[1] = steps
    
    left[0] = steps[::-1]
    left[1] = 360
    
    up[0] = 20
    up[1] = steps[::-1]
    
    pos = numpy.concatenate((right.T, down.T, left.T, up.T))
    i = 0
    history = numpy.array([])
    surf = plot(history)
    
    while True: 
       # Erase screen
       screen.fill((255, 255, 255))
    
       if i >= len(pos):
          i = 0
          surf = plot(history)
    
       screen.blit(img, pos[i])
       history = numpy.append(history, pos[i])
       screen.blit(surf, (100, 100))
    
       i += 1
    
       for event in pygame.event.get():
          if event.type == QUIT:
             pygame.quit()
             sys.exit()
    
       pygame.display.update()
       clock.tick(30)
    

工作原理...

本表中解释了与绘图相关的函数:

函数 描述
matplotlib.use("Agg") 此函数指定使用非交互式后端
plt.figure(figsize=[3, 3]) 此函数创建一个 3 英寸乘以 3 英寸的图形
fig.add_subplot(111) 此函数创建一个子图(在这种情况下我们只需要一个子图)
agg.FigureCanvasAgg(fig) 此函数以非交互式模式创建一个画布
ax.plot(data) 此函数使用指定数据创建一个图表
canvas.draw() 此函数在画布上绘制
canvas.get_renderer() 此函数获取画布的渲染器

访问表面像素数据(中级)

Pygame surfarray 模块处理 Pygame Surface对象和 NumPy 数组之间的转换。如您所回忆的,NumPy 可以以快速和高效的方式操作大型数组。

如何操作...

在本食谱中,我们将使用小图像填充游戏屏幕。

  1. 将像素复制到数组中array2d函数将像素复制到二维数组中。还有一个类似的三维数组函数。我们将从头像图像中复制像素到一个数组中:

    pixels = pygame.surfarray.array2d(img)
    
  2. 创建游戏屏幕:NumPy 数组有一个形状属性,对应于数组的维度。这个属性是一个元组。例如,一个二维数组将有一个包含两个元素的形状元组。让我们使用数组的shape属性从像素数组的形状创建游戏屏幕。屏幕在两个方向上都会大七倍:

    X = pixels.shape[0] * 7
    Y = pixels.shape[1] * 7
    screen = pygame.display.set_mode((X, Y))
    
  3. 平铺图像:使用 NumPy 的tile函数平铺图像很容易。数据需要转换为整数值,因为颜色定义为整数:

    new_pixels = numpy.tile(pixels, (7, 7)).astype(int)
    
  4. 显示数组surfarray模块有以下特殊函数(blit_array)用于在屏幕上显示数组:

    pygame.surfarray.blit_array(screen, new_pixels)
    

    以下截图显示了代码的结果:

    如何做...

    以下代码执行图像平铺:

    import pygame, sys
    from pygame.locals import *
    import numpy
    
    pygame.init()
    img = pygame.image.load('head.jpg')
    pixels = pygame.surfarray.array2d(img)
    X = pixels.shape[0] * 7
    Y = pixels.shape[1] * 7
    screen = pygame.display.set_mode((X, Y))
    pygame.display.set_caption('Surfarray Demo')
    new_pixels = numpy.tile(pixels, (7, 7)).astype(int)
    
    while True: 
       screen.fill((255, 255, 255))
       pygame.surfarray.blit_array(screen, new_pixels)
    
       for event in pygame.event.get():
          if event.type == QUIT:
             pygame.quit()
             sys.exit()
    
       pygame.display.update()
    

工作原理...

以下表格简要描述了我们使用的新函数和属性:

函数 描述
pygame.surfarray.array2d(img) 这将像素数据复制到一个二维数组中
pixels.shape[0] shape属性以元组的形式持有 NumPy 数组的维度
numpy.tile(pixels, (7, 7)) 这将数组平铺到指定的维度,维度由元组指定
pygame.surfarray.blit_array(screen, new_pixels) 这将在屏幕上显示数组值

访问声音数据(简单)

一款好的游戏需要优秀的音乐和音效。Pygame 的mixer模块让我们可以播放声音或任何音频。

如何做...

我们将使用标准的 Python 下载 WAV 音频文件。当游戏退出时,我们将播放这个声音。这个例子要求你实际执行示例代码,因为这本书没有音频支持。

  1. 创建声音对象:在指定音频文件名后,我们可以创建一个 Pygame Sound对象。这个类正如你所期望的那样,体现了声音的概念:

    audio = pygame.mixer.Sound(WAV_FILE)
    
  2. 播放声音Sound对象有一个play方法,它有几个循环参数。如果这个参数的值设置为-1,声音将无限循环:

    audio.play(-1)
    
  3. 暂停游戏:有时我们需要暂停游戏的执行,就像在我们的例子中,以便能够听到声音。我们可以使用以下代码片段来完成:

    pygame.time.delay(TIMEOUT * 1000)
    

    延迟以毫秒为单位指定,这就是为什么我们要乘以 1000。

  4. 停止声音:过了一段时间后,我们需要使用相应的stop方法来停止声音:

    audio.stop()
    

    音频演示代码如下:

    import pygame, sys
    from pygame.locals import *
    import numpy
    import urllib2
    import time
    
    WAV_FILE = 'smashingbaby.wav'
    
    def play():
        audio = pygame.mixer.Sound(WAV_FILE)
        audio.play(-1)
        TIMEOUT = 1
        pygame.time.delay(TIMEOUT * 1000)
        audio.stop()
        time.sleep(TIMEOUT)
    
    pygame.init()
    pygame.display.set_caption('Sound Demo')
    response = urllib2.urlopen('http://www.thesoundarchive.com/austinpowers/smashingbaby.wav')
    filehandle = open(WAV_FILE, 'w')
    filehandle.write(response.read())
    filehandle.close()
    screen = pygame.display.set_mode((400, 400))
    
    while True: 
       sys_font = pygame.font.SysFont("None", 19)
       rendered = sys_font.render('Smashing Baby', 0, (255, 100, 100))
       screen.blit(rendered, (100, 100))
    
       for event in pygame.event.get():
          if event.type == QUIT:
             play()
             pygame.quit()
             sys.exit()
    
       pygame.display.update()
    

工作原理...

本演示最重要的函数总结如下表:

函数 描述
pygame.mixer.Sound(WAV_FILE) 这个函数根据文件名创建一个Sound对象。
audio.play(-1) 这个函数播放并无限循环(-1 表示无限)。默认情况下,声音只播放一次。这对应于 0 次循环。如果值为 2,声音将播放一次,然后重复播放 2 次。
pygame.time.delay(TIMEOUT * 1000) 此函数使游戏暂停指定的毫秒数。
audio.stop() 此函数停止音频播放。

播放电影(中级)

如今,大多数商业游戏都有小电影片段,试图向我们解释剧情。例如,一款第一人称射击游戏可能有一个显示下一场任务简报的电影。电影播放是一个很酷的功能。Pygame 为 MPEG 视频提供了有限的支持。

准备工作

我们需要为这个演示准备一个 MPEG 电影。一旦您有了电影,您可以使用以下命令将其转换为在 Pygame 游戏中使用:

ffmpeg -i <infile> -vcodec mpeg1video -acodec libmp3lame -intra <outfile.mpg>

安装ffmpeg和命令行选项超出了本书的范围,但不应太难(见ffmpeg.org/)。

如何操作...

电影播放的设置与我们在前一个菜谱中介绍的声音播放类似。以下代码演示了播放 MPEG 视频。请注意play函数:

import pygame, sys
from pygame.locals import *
import time

pygame.init()
screen = pygame.display.set_mode((400, 400))
pygame.display.set_caption('Movie Demo')

def play():
    movie = pygame.movie.Movie('out.mpg')
    movie.play()
    TIMEOUT = 7
    pygame.time.delay(TIMEOUT * 1000)
    movie.stop()

while True: 
   screen.fill((255, 255, 255))

   for event in pygame.event.get():
      if event.type == QUIT:
         play()
         pygame.quit()
         sys.exit()

   pygame.display.update()

工作原理...

电影播放的相关函数可以在以下表中找到:

函数 描述
pygame.movie.Movie('out.mpg') 此函数根据 MPEG 电影的文件名创建一个Movie对象
movie.play() 此函数开始播放电影
movie.stop() 此函数停止电影的播放

Pygame 在 Android 上(中级)

Android 是一个由 Google 最初开发的开源智能手机操作系统。大多数 Android 应用程序是用 Java 编程语言编写的,并在基于 Java 的虚拟机上运行。幸运的是,我们可以为 Android 手机创建 Pygame 游戏。这不是一件小事,我们只会介绍最基本的内容。

准备工作

我们将安装Pygame Subset For AndroidPGS4A)。在我们开始之前,您需要安装 JDK、Python 2.7 或更高版本。从pygame.renpy.org/dl下载适合您操作系统的相应软件。

要安装必要的软件,我们需要一个互联网连接和相当大的硬盘空间。如果您没有几个 GB 的空闲空间,您可能需要腾出更多空间。我们可以通过运行以下命令来安装 Android SDK 和其他我们将需要的软件,如 Apache Ant:

android.py installsdk

这将启动一个向导,引导您完成安装。在安装过程中,您可以接受所有默认选项,但您确实需要生成一个密钥。除非您真的认真对待创建应用程序,否则您不必担心这个密钥的安全性。

如何操作...

我们将创建一个简单的游戏,打印"Hello World From Android!",并将其命名为mygame

  1. 设置游戏:创建一个与游戏名称相同的目录,并在其中放置一个包含以下内容的main.py文件:

    import pygame
    
    # Import the android module. If we can't import it, set it to None - this
    # lets us test it, and check to see if we want android-specific # behavior.
    try:
        import android
    except ImportError:
        android = None
    
    # Event constant.
    TIMEREVENT = pygame.USEREVENT
    
    # The FPS the game runs at.
    FPS = 30
    
    def main():
        pygame.init()
    
        # Set the screen size.
        screen = pygame.display.set_mode((480, 800))
    
        # Map the back button to the escape key.
        if android:
            android.init()
            android.map_key(android.KEYCODE_BACK, pygame.K_ESCAPE)
    
        # Use a timer to control FPS.
        pygame.time.set_timer(TIMEREVENT, 1000 / FPS)
    
        while True:
            ev = pygame.event.wait()
    
            # Android-specific:
            if android:
                if android.check_pause():
                    android.wait_for_resume()
    
            # Draw the screen based on the timer.
            if ev.type == TIMEREVENT:
                screen.fill((255, 255, 255))
                font = pygame.font.Font('freesansbold.ttf', 32)
                rendered = font.render('Hello From Android!', 0, (255, 100, 100))
                screen.blit(rendered, (100, 100))
                pygame.display.flip()
    
            # When the user hits back, ESCAPE is sent. Handle it and
            # end the game.
            elif ev.type == pygame.KEYDOWN and ev.key == pygame.K_ESCAPE:
                break
    
    # This isn't run on Android.
    if __name__ == "__main__":
        main()
    

    这基本上是将 PGS4A 网站上的代码修改为打印欢迎信息的代码。更详细的解释将在食谱的末尾给出。

  2. 配置游戏: 我们可以使用以下命令配置游戏:

    android.py configure mygame
    
    

    我们将接受所有默认设置并将存储设置设置为内部。

  3. 构建、安装和运行游戏: Android 本质上是一个 Java 框架,因此涉及大量的编译。这和 Python 世界有点不同。由于这个游戏很简单,构建不会花费很长时间。首先,我们将启动模拟器——这是一个模拟实际手机行为的程序。找到 Android SDK 中的android可执行文件。启动它,在打开的 GUI 应用程序中选择工具 | 管理 AVD... | 新建...。创建一个Android 虚拟设备AVD)并给它命名。点击启动...按钮。一个手机模拟器将启动。如果它被锁定,你可以按F2解锁。

    我们现在可以使用以下命令构建和安装游戏:

    android.py build mygame release install
    
    

它是如何工作的...

在此代码中使用的相关函数描述如下:

函数 描述
android.init() 此函数初始化 Android
android.map_key(android.KEYCODE_BACK, pygame.K_ESCAPE) 此函数将 Android 返回按钮映射到 Pygame 的 escape 按钮
pygame.time.set_timer(TIMEREVENT, 1000 / FPS) 此函数在指定的毫秒时间间隔内触发事件
android.check_pause() 此函数检查暂停请求
android.wait_for_resume() 此函数将游戏置于睡眠模式

人工智能(中级)

在游戏中,我们经常需要模拟智能行为。scikits-learn项目旨在提供机器学习的 API。我最喜欢它的地方是它惊人的文档。

准备中

我们可以在命令行中输入以下命令来安装scikit-learn

pip install -U scikit-learn

或者:

easy_install -U scikit-learn

这可能因为权限问题而无法工作,因此你可能需要在命令前加上sudo或者以管理员身份登录。

如何做到这一点...

我们将生成一些随机点并将它们聚类,这意味着彼此靠近的点将被放入同一个聚类中。这是你可以使用scikits-learn应用到的许多技术之一。聚类是一种旨在根据相似性对项目进行分组的机器学习算法。

  1. 生成随机点: 我们将在一个 400 像素乘 400 像素的正方形内生成 30 个随机点位置:

    positions = numpy.random.randint(0, 400, size=(30, 2))
    
    
  2. 计算亲和矩阵: 我们将使用到原点的欧几里得距离作为亲和度度量。亲和矩阵是一个包含亲和度分数的矩阵,在这种情况下是距离:

    positions_norms = numpy.sum(positions ** 2, axis=1)
    S = - positions_norms[:, numpy.newaxis] - positions_norms[numpy.newaxis, :] + 2 * numpy.dot(positions, positions.T)
    
    
  3. 聚类点: 将上一步的结果传递给AffinityPropagation类。此类将点标记为适当的聚类编号:

    aff_pro = sklearn.cluster.AffinityPropagation().fit(S)
    labels = aff_pro.labels_
    
    
  4. 绘制多边形:我们将为每个簇绘制多边形。涉及到的函数需要一个点的列表、一个颜色(让我们将其涂成红色),和一个表面:

    pygame.draw.polygon(screen, (255, 0, 0), polygon_points[i])
    
    

    结果是每个簇都有一组多边形,如下面的截图所示:

    如何做到这一点...

    簇示例代码如下所示:

    import numpy
    import sklearn.cluster
    import pygame, sys
    from pygame.locals import *
    
    positions = numpy.random.randint(0, 400, size=(30, 2))
    
    positions_norms = numpy.sum(positions ** 2, axis=1)
    S = - positions_norms[:, numpy.newaxis] - positions_norms[numpy.newaxis, :] + 2 * numpy.dot(positions, positions.T)
    
    aff_pro = sklearn.cluster.AffinityPropagation().fit(S)
    labels = aff_pro.labels_
    
    polygon_points = []
    
    for i in xrange(max(labels) + 1):
       polygon_points.append([])
    
    # Sorting points by cluster
    for i, l in enumerate(labels):
       polygon_points[l].append(positions[i])
    
    pygame.init()
    screen = pygame.display.set_mode((400, 400))
    
    while True: 
       for point in polygon_points:
          pygame.draw.polygon(screen, (255, 0, 0), point)
    
       for event in pygame.event.get():
          if event.type == QUIT:
             pygame.quit()
             sys.exit()
    
       pygame.display.update()
    

它是如何工作的...

人工智能配方中最重要的一行在以下表格中描述得更为详细:

函数 描述
numpy.random.randint(0, 400, size=(30, 2)) 这创建了一个 30 行 2 列的随机整数数组。这对应于二维空间中的 30 个点。值在 0 到 400 之间。
numpy.sum(positions ** 2, axis=1) 这计算了位置数组平方的数组之和。
numpy.dot(positions, positions.T) 这计算了位置数组和其转置的点积。
sklearn.cluster.AffinityPropagation().fit(S) 这创建了一个 AffinityPropagation 对象并使用亲和矩阵进行拟合。
pygame.draw.polygon(screen, (255, 0, 0), polygon_points[i]) 这根据一个表面、一个颜色(在这种情况下是红色)和一系列点绘制一个多边形。

绘制精灵(中级)

精灵是计算机图形学中的一个术语,指的是一个二维的可视对象,它已经被优化用于渲染。Pygame 提供了处理精灵的 Sprite 类。它可以在 Surface 对象上绘制精灵。它还具有碰撞函数。对于复杂的游戏,我们可以将精灵分组在一起以便于管理。精灵不是线程安全的,所以在使用多个线程时你应该小心。

如何做到这一点...

我们将重新做动画演示,但这次使用精灵和 Rect 对象,这些对象代表矩形。一个 Rect 对象有 lefttopwidthheight 属性。我们将在整个示例中使用这些和其他属性。此外,当鼠标按钮被点击时,我们将让角色旋转。然而,我们现在不会关心我们确切点击了哪里。

我们将创建一个扩展 Sprite 类的类。精灵类有一个 update 方法,它在每一帧都会触发。所有涉及精灵移动的逻辑都应该放在这里。

  1. 构造函数:首先,我们需要创建精灵并执行子类化。所有的初始化逻辑都放在这里。有关函数的更多详细信息可以在下一节中找到。我们定义了一个图像、矩形,以及跟踪角色移动的变量:

    class Head(pygame.sprite.Sprite):
        def __init__(self):
            pygame.sprite.Sprite.__init__(self) 
            self.image, self.rect = load_image('head.jpg', -1)
            screen = pygame.display.get_surface()
            self.area = screen.get_rect()
            self.STEP = 9
            self.MARGIN = 12
            self.xstep = self.STEP 
            self.ystep = 0
            self.dizzy = 0
            self.direction = 'right'
    
  2. 更新方法update 方法调用辅助方法,这些方法要么使头部旋转,要么按顺时针方向移动它。移动是通过以下行实现的:

    newpos = self.rect.move((self.xstep, self.ystep))
    
    

    以下行负责旋转:

    self.image = pygame.transform.rotate(self.original, self.degrees)
    
    

    备注

    你可以在 YouTube 上找到一个游戏的短片 (www.youtube.com/watch?v=EFQlc_siPrI)。以下是一个游戏的截图:

    如何做到这一点...

    以下是 Sprite 演示的完整代码列表:

    import os, pygame
    from pygame.locals import *
    
    def load_image(name, colorkey=None):
        try:
            image = pygame.image.load(name)
        except pygame.error, message:
            print 'Cannot load image:', name
    
        image = image.convert()
    
        return image, image.get_rect()
    
    class Head(pygame.sprite.Sprite):
        def __init__(self):
            pygame.sprite.Sprite.__init__(self) 
            self.image, self.rect = load_image('head.jpg', -1)
            screen = pygame.display.get_surface()
            self.area = screen.get_rect()
            self.STEP = 9
            self.MARGIN = 12
            self.xstep = self.STEP 
            self.ystep = 0
            self.degrees = 0
            self.direction = 'right'
    
        def update(self):
            if self.degrees:
                self._spin()
            else:
                self._move()
    
        def _move(self):
            newpos = self.rect.move((self.xstep, self.ystep))
    
            if self.direction == 'right' and self.rect.right > self.area.right - self.MARGIN:
                self.xstep = 0
                self.ystep = self.STEP 
                self.direction = 'down'
    
            if self.direction == 'down' and self.rect.bottom > self.area.bottom - self.MARGIN:
                self.xstep = -self.STEP
                self.ystep = 0
                self.direction = 'left'
    
            if self.direction == 'left' and self.rect.left < self.area.left + self.MARGIN:
                self.xstep = 0
                self.ystep = -self.STEP
                self.direction = 'up'
    
            if self.direction == 'up' and self.rect.top < self.area.top + self.MARGIN:
                self.xstep = self.STEP
                self.ystep = 0
                self.direction = 'right'
    
            self.rect = newpos
    
        def _spin(self):
            center = self.rect.center
            self.degrees = self.degrees + 12
            if self.degrees >= 360:
                self.degrees = 0
                self.image = self.original
            else:
                self.image = pygame.transform.rotate(self.original, self.degrees)
            self.rect = self.image.get_rect(center=center)
    
        def hit(self):
            if not self.degrees:
                self.degrees = 1
                self.original = self.image
    
    def main():
        pygame.init()
        screen = pygame.display.set_mode((400, 400))
        pygame.display.set_caption('Sprite Demo')
    
        background = pygame.Surface(screen.get_size())
        background = background.convert()
        background.fill((250, 250, 250))
    
        if pygame.font:
            font = pygame.font.Font(None, 36)
            text = font.render("Hit the avatar!", 1, (0, 0, 200))
            textpos = text.get_rect(centerx = background.get_width()/2, centery = background.get_height()/2)
            background.blit(text, textpos)
    
        screen.blit(background, (0, 0))
        pygame.display.flip()
    
        clock = pygame.time.Clock()
        head = Head()
        sprite = pygame.sprite.RenderPlain(head)
    
        while True:
            clock.tick(60)
    
            for event in pygame.event.get():
                if event.type == QUIT:
                    return
                elif event.type == MOUSEBUTTONDOWN:
                   head.hit()
    
            sprite.update()
    
            screen.blit(background, (0, 0))
            sprite.draw(screen)
            pygame.display.flip()
    
    if __name__ == '__main__': 
       main()
    

如何工作...

以下是对本演示中使用的各种函数的更详细描述:

函数 描述
pygame.sprite.Sprite.__init__(self) 这将创建精灵。
screen.get_rect() 这将获取一个 Rect 对象。
pygame.display.get_surface() 这将获取一个 Surface 对象。
self.rect.move((self.xstep, self.ystep)) 这将根据 xy 坐标移动一个矩形。
pygame.transform.rotate(self.original, self.degrees) 这将根据一个 Surface 对象和角度(以度为单位)旋转一个图像。正值对应逆时针旋转,负值对应顺时针旋转。
self.image.get_rect(center=center) 这将根据图像的中心坐标获取矩形。
pygame.sprite.RenderPlain(head) 这将渲染精灵。

使用 Pygame 和 OpenGL(高级)

OpenGL 为 2D 和 3D 计算机图形指定了一个 API。该 API 由函数和常量组成。我们将专注于名为 PyOpenGL 的 Python 实现。

准备工作

使用以下命令安装 PyOpenGL:

pip install PyOpenGL PyOpenGL_accelerate

你可能需要具有 root 权限才能执行此命令。相应的 easy_install 命令如下:

easy_install PyOpenGL PyOpenGL_accelerate

如何做...

为了演示目的,我们将使用 OpenGL 绘制 Sierpinski 网格。这是由数学家 Waclaw Sierpinski 创建的三角形形状的分形模式。通过递归和原则上无限的过程获得三角形。

  1. OpenGL 初始化:首先,我们将初始化一些与 OpenGL 相关的原始数据。这包括设置显示模式和背景颜色。在食谱的末尾给出了逐行解释:

    def display_openGL(w, h):
     pygame.display.set_mode((w,h), pygame.OPENGL|pygame.DOUBLEBUF)
    
     glClearColor(0.0, 0.0, 0.0, 1.0)
     glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
    
     gluOrtho2D(0, w, 0, h)
    
    
  2. 显示点:算法要求我们显示点,越多越好。首先,我们将绘图颜色设置为红色。其次,我们定义三角形的顶点(我自己称它们为点)。然后我们定义随机索引,这些索引将用于选择三个三角形顶点中的一个。我们在中间某个地方随机选择一个点,实际上并不重要。之后,我们在前一个点和随机选择的顶点之间绘制一个中点。最后,我们“刷新”结果:

        glColor3f(1.0, 0, 0)
        vertices = numpy.array([[0, 0], [DIM/2, DIM], [DIM, 0]])
        NPOINTS = 9000
        indices = numpy.random.random_integers(0, 2, NPOINTS)
        point = [175.0, 150.0]
    
        for index in indices:
           glBegin(GL_POINTS)
           point = (point + vertices[index])/2.0
           glVertex2fv(point)
           glEnd()
    
        glFlush()
    

    Sierpinski 三角形看起来是这样的:

    如何做...

    以下展示了包含所有导入的完整 Sierpinski 网格演示代码:

    import pygame
    from pygame.locals import *
    import numpy
    
    from OpenGL.GL import *
    from OpenGL.GLU import *
    
    def display_openGL(w, h):
      pygame.display.set_mode((w,h), pygame.OPENGL|pygame.DOUBLEBUF)
    
      glClearColor(0.0, 0.0, 0.0, 1.0)
      glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
    
      gluOrtho2D(0, w, 0, h)
    
    def main():
        pygame.init()
        pygame.display.set_caption('OpenGL Demo')
        DIM = 400
        display_openGL(DIM, DIM)
        glColor3f(1.0, 0, 0)
        vertices = numpy.array([[0, 0], [DIM/2, DIM], [DIM, 0]])
        NPOINTS = 9000
        indices = numpy.random.random_integers(0, 2, NPOINTS)
        point = [175.0, 150.0]
    
        for index in indices:
           glBegin(GL_POINTS)
           point = (point + vertices[index])/2.0
           glVertex2fv(point)
           glEnd()
    
        glFlush()
        pygame.display.flip()
    
        while True:
            for event in pygame.event.get():
                if event.type == QUIT:
                    return
    
    if __name__ == '__main__':
      main()
    

如何工作...

如承诺,以下是示例中最重要的部分的逐行解释:

函数 描述
`pygame.display.set_mode((w,h), pygame.OPENGL pygame.DOUBLEBUF)`
`glClear(GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT)`
gluOrtho2D(0, w, 0, h) 这定义了一个 2D 正交投影矩阵,其中包含左、右、上、下裁剪平面的坐标。
glColor3f(1.0, 0, 0) 这使用三个浮点值定义当前绘图颜色,用于 RGB(0-1,而不是 Pygame 中常用的 0-255)。在这种情况下,我们将用红色进行绘制。
glBegin(GL_POINTS) 这定义了原语或一组原语的顶点。在这里,原语是点。
glVertex2fv(point) 这用于根据顶点渲染一个点。
glEnd() 这关闭了由glBegin开始的代码部分。
glFlush() 这强制执行 GL 命令。

检测碰撞(中级)

在精灵演示中,我们省略了碰撞检测部分。Pygame 的Rect类中有许多有用的碰撞检测函数。例如,我们可以检查一个点是否在矩形内,或者两个矩形是否重叠。

如何实现...

除了碰撞检测,我们还将用我们创建的锤子图像替换鼠标光标。这不是一个非常漂亮的图像,但比无聊的旧光标要好。

  1. 更新击中方法:我们将更新精灵演示代码中的hit方法。在新版本中,我们检查鼠标光标是否在头像精灵内。实际上为了更容易击中头部,我们创建了一个稍微大一点的矩形:

    def hit(self):
             mouse_x, mouse_y = pygame.mouse.get_pos()
             collided = False
             bigger_rect = self.rect.inflate(40, 40)
    
             if bigger_rect.collidepoint(mouse_x, mouse_y):
                collided = True
    
             if not self.degrees and collided:
                self.degrees = 1
                self.original = self.image
                self.nhits += 1
             else:                                  
                self.nmisses += 1

    
  2. 替换鼠标光标:替换鼠标光标的所有必要步骤都已经介绍过了。除了使鼠标光标不可见:

    pygame.mouse.set_visible(False)
    

    游戏的截图如下所示:

    如何实现...

本例的完整代码可以在本书的代码包中找到。

工作原理...

在这个配方中,我们了解了一些关于碰撞检测、鼠标光标和矩形的知识:

函数 描述
pygame.mouse.get_pos() 这以元组的形式获取鼠标位置。
self.rect.inflate(40, 40) 这基于偏移量创建一个更大的矩形。如果偏移量是负数,这将导致一个更小的矩形。
bigger_rect.collidepoint(mouse_x, mouse_y) 这检查一个点是否在矩形内。
pygame.mouse.set_visible(False) 这隐藏了鼠标光标。

添加网络功能(高级)

当你能够与其他人一起玩游戏时,游戏会变得更加有趣。通常这意味着通过某种客户端-服务器架构在互联网上玩游戏。在 Python 世界中,Twisted 通常用于这种架构。

准备工作

Twisted 可以根据您的操作系统以多种方式安装。更多信息请参阅twistedmatrix.com/trac/wiki/Downloads

如何实现...

很遗憾,我们无法在这个教程中创建一个大型多人游戏,但我们可以创建一个简单的客户端-服务器设置,这将为我们稍后要创建的谜题奠定基础。

  1. 服务器:首先,我们将设置服务器,它将回显客户端的消息并在其前面添加一个序列号:

    from twisted.internet import reactor, protocol
    
    class Server(protocol.Protocol):
        def __init__(self):
           self.count = 0
    
        def dataReceived(self, msg):
             self. count += 1
             self.transport.write("%d %s" % (self.count, msg))
    
    def main():
        factory = protocol.ServerFactory()
        factory.protocol = Server
        reactor.listenTCP(8888,factory)
        reactor.run()
    
    if __name__ == '__main__':
        main()
    

    如您所见,服务器在 TCP 上运行在端口 8888(见 en.wikipedia.org/wiki/Transmission_Control_Protocol)。

  2. 客户端设置:客户端通过与服务器相同的端口发送消息,并在 Pygame GUI 中显示来自服务器的消息。我们将在下一节中详细介绍。在后面的示例中,我们将使用此代码做更多有趣的事情:

    from twisted.internet import reactor, protocol
    from pygame.locals import *
    import pygame
    
    class Client(protocol.Protocol):
        def __init__(self):
           self.msg = 'Hello'
           self.end_msg = False
    
        def sendMessage(self, msg):
            self.transport.write(msg)
            self.update(msg)
    
        def dataReceived(self, msg):
           self.msg = msg
    
           if msg.startswith("19"):
              self.end_msg = True
    
        def update(self, msg):
            screen = pygame.display.get_surface()
    
            screen.fill((255, 255, 255))
            font = pygame.font.Font(None, 36)
            text = font.render(self.msg, 1, (200, 200, 200))
            textpos = text.get_rect(centerx=screen.get_width()/2, centery=screen.get_height()/2)
            screen.blit(text, textpos)
            pygame.display.flip()
    
            if self.end_msg:
               reactor.stop()
    
    def send(p):
        p.sendMessage("Hello!")
    
        for i in xrange(1, 20):
          reactor.callLater(i * .1, p.sendMessage, "IMPORTANT MESSAGE!")
    
    def main():
        pygame.init()
        screen = pygame.display.set_mode((400, 400))
        pygame.display.set_caption('Network Demo')
    
        c = protocol.ClientCreator(reactor, Client)
        c.connectTCP("localhost", 8888).addCallback(send)
        reactor.run()
    
        while True:
           for event in pygame.event.get():
             if event.type == QUIT:
                 return
    
    if __name__ == '__main__':
        main()
    

    在我们可以启动客户端之前,我们需要启动服务器。在游戏 GUI 中,您应该看到 Hello 被显示,然后是 1 重要消息!到 19 重要消息!,如下截图所示:

    如何做...

它是如何工作的...

在这个示例中,我们看到了如何使用 Pygame GUI 创建一个简单的服务器和客户端。原则上,我们现在可以扩展这个设置来创建一个多人游戏。以下给出了 Twisted 客户端和服务器设置的详细信息:

函数 描述
self.transport.write("%d %s" % (self.count, msg)) 这写入了一条消息。在这种情况下,我们在消息前面添加了一个序列号。
factory = protocol.ServerFactory() 这创建了一个 Twisted 服务器工厂,它本身创建 Twisted 服务器。
reactor.listenTCP(8888,factory) 这使用给定的工厂监听端口 8888。
reactor.run() 这将启动服务器或客户端。
reactor.stop() 这停止客户端或服务器。
reactor.callLater(i * .1, p.sendMessage, "IMPORTANT MESSAGE!") 这注册了一个在指定秒数后执行的带有参数的回调函数。
protocol.ClientCreator(reactor, Client) 这创建了一个 Twisted 客户端。
c.connectTCP("localhost", 8888).addCallback(send) 这通过 TCP 在端口 8888 上连接客户端并注册了一个回调函数。

调试游戏(中级)

调试是那些没有人真正喜欢,但非常重要需要掌握的事情之一。它可能需要数小时,而且由于墨菲定律,您很可能没有那么多时间。因此,有系统性和熟悉您的工具非常重要。在您找到错误并实施修复后,您应该有一个测试。这样至少您将不必再次经历调试的地狱。

PuDB 是一个易于安装的基于控制台的全屏 Python 调试器。PuDB 支持 cursor 键和 vi 命令。如果需要,调试器还可以与 IPython 集成。

准备工作

为了安装 puDB,我们只需要执行以下命令:

sudo easy_install pudb

如何做...

要调试碰撞演示代码,请在命令行中输入以下命令:

python -m pudb collision_demo.py

小贴士

源代码可以从 Packt Publishing 网站下载。

下面的截图显示了最重要的调试命令在顶部:

如何操作...

我们还可以看到正在调试的代码、变量、堆栈和定义的断点。输入 q 退出大多数菜单。输入 n 将调试器移动到下一行。我们还可以使用光标键或 vi 的 JK 键进行移动,例如,通过输入 b 来设置断点。

性能分析您的代码(中级)

性能对游戏来说很重要,幸运的是,有很多 Python 性能分析工具。性能分析是关于构建一个软件程序的配置文件,以便收集有关内存使用或时间复杂度的信息。

cProfile 是 Python 2.5 中引入的一个 C 扩展。它可以用于 确定性性能分析。确定性性能分析意味着时间测量是精确的,没有使用采样。与统计性能分析相对比,后者来自随机样本。

如何操作...

以下步骤将帮助您性能分析您的代码:

  1. 创建配置文件:我们将对碰撞演示代码进行性能分析,并将性能分析输出存储在以下文件中:

    python -m cProfile -o collision_demo.profile collision_demo.py
    
  2. pstats 浏览器:创建文件后,我们可以在特殊的命令行浏览器中查看和排序数据:

    python -m pstats collision_demo.profile 
    Welcome to the profile statistics browser.
    
    
  3. 获取帮助:能够获取帮助总是一件好事,只需在命令行中输入以下命令:

    collision_demo.profile% help
    
    Documented commands (type help <topic>):
    ========================================
    EOF  add  callees  callers  help  quit  read  reverse  sort  stats  strip
    
    
  4. 排序:我们可以使用以下 sort 命令进行排序:

    collision_demo.profile% sort
    Valid sort keys (unique prefixes are accepted):
    stdname -- standard name
    nfl -- name/file/line
    pcalls -- call count
    file -- file name
    calls -- call count
    time -- internal time
    line -- line number
    cumulative -- cumulative time
    module -- file name
    name -- function name
    
    
  5. 前三个被调用的函数:我们可以通过排序和调用 stats 来获取前三个被调用的函数:

    collision_demo.profile% sort calls
    collision_demo.profile% stats 3
    
     380943 function calls (380200 primitive calls) in 18.056 seconds
    
     Ordered by: call count
     List reduced from 801 to 3 due to restriction <3>
    
     ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     52156    0.013    0.000    0.013    0.000 {method 'endswith' of 'str' objects}
    31505/31368    0.003    0.000    0.003    0.000 {len}
     27573    0.022    0.000    0.022    0.000 {method 'lower' of 'str' objects}
    
    

它是如何工作的...

我们对碰撞演示进行了性能分析。以下表格总结了性能分析器的输出:

描述
Ncalls 调用次数
Tottime 函数中花费的总时间
Percall 每次调用的耗时,通过将总耗时除以调用次数计算得出
Cumtime 函数及其被调用的函数所花费的累积时间,包括递归调用

使用 Pygame 的拼图游戏(高级)

我们将在网络示例中继续进行。这次我们将创建一个拼图游戏,允许我们猜测一个单词。请注意,这只是一个原型。它还需要很多润色。

如何操作...

以下步骤将帮助您创建所需的拼图游戏:

  1. 服务器更改:服务器中的更改相当简单。我们只是检查我们是否猜对了单词:

    from twisted.internet import reactor, protocol
    
    class Server(protocol.Protocol):
        def dataReceived(self, msg):
             resp = '*' * 20
             print msg
    
             if msg == 'secret':
                resp = msg
    
             self.transport.write(resp)
    
    def main():
        factory = protocol.ServerFactory()
        factory.protocol = Server
        reactor.listenTCP(8888,factory)
        reactor.run()
    
    if __name__ == '__main__':
        main()
    
  2. 客户端更改:最重要的更改是处理输入框中的按键以及处理来自服务器的响应。输入框允许我们输入文本,使用 退格 键编辑它,并使用 回车 键提交。文本框上方的标签显示尝试次数和游戏状态。我们使用 Twisted 循环回调每 30 毫秒更新 GUI:

    from twisted.internet import reactor, protocol
    from pygame.locals import *
    import pygame
    from twisted.internet.task import LoopingCall
    
    class Client(protocol.Protocol):
        def __init__(self):
           self.STARS = '*' * 20
           self.msg = self.STARS
           self.font = pygame.font.Font(None, 22)
           self.screen = pygame.display.get_surface()
           self.label = 'Guess the word:'
           self.attempts = 0
    
        def sendMessage(self, msg):
            self.transport.write(msg)
    
        def dataReceived(self, msg):
           self.msg = msg 
    
           if self.msg != self.STARS:
             self.label = 'YOU WIN!!!!'
    
           self.update_prompt()
    
        def update_prompt(self):
            self.screen.fill((255, 255, 255))
            BG = (0, 255, 0)
            FG = (0, 0, 0)
    
            pygame.draw.rect(self.screen, BG, (100, 200, 200, 20))
    
            self.screen.blit(self.font.render(self.msg, 1, FG), (100, 200))
            self.screen.blit(self.font.render("%d %s" % (self.attempts, self.label), 1, FG), 
                  (140, 180))
            pygame.display.flip()
    
    def handle_events(p):
       while True:
          for event in pygame.event.get():
             if event.type == QUIT:
                reactor.stop()
                return
             elif event.type == KEYDOWN:
                key = event.key
    
                if p.msg == '*' * 20:
                   p.msg = ''
    
                if key == K_BACKSPACE:
                   p.msg = p.msg[0:-1]
                   p.update_prompt()
                elif key == K_RETURN:
                   p.attempts += 1
                   p.sendMessage(p.msg)
                   return
                elif ord('a') <= key <= ord('z'):
                   p.msg += chr(key)
                   p.update_prompt()
    
    def send(p):
       p.update_prompt()
       tick = LoopingCall(handle_events, p)
       tick.start(.03)
    
    def main():
        pygame.init()
        screen = pygame.display.set_mode((400, 400))
        pygame.display.set_caption('Puzzle Demo')
    
        c = protocol.ClientCreator(reactor, Client)
        c.connectTCP("localhost", 8888).addCallback(send)
        reactor.run()
    
    if __name__ == '__main__':
        main()
    

    以下截图是在猜出单词后的截图:

如何操作...

它是如何工作的...

虽然这似乎是一个相当详尽的配方,但可能只有几行代码需要一些解释:

函数 描述
LoopingCall(handle_events, p) 这创建了一个循环回调。一个定期被调用的回调函数。
tick.start(.03) 这以 30 毫秒的周期启动循环回调。

使用 Pygame 模拟(高级)

作为最后一个例子,我们将使用康威的生命游戏来模拟生命。原始的生命游戏基于一些基本规则。我们从二维正方形网格上的随机配置开始。网格中的每个单元格可以是死的或活的。这个状态取决于单元格的八个邻居。卷积可以用来评估游戏的基本规则。我们将需要 SciPy 包来进行卷积部分。

准备工作

使用以下任意一个命令安装 SciPy:

  • sudo pip install scipy

  • easy_install scipy

如何做...

以下代码是带有一些修改的生命的游戏实现:

  • 单击鼠标一次会画一个十字直到再次点击

  • R 键将网格重置为随机状态

  • B 键根据鼠标位置创建方块

  • G 创建滑翔机

代码中最重要的数据结构是一个二维数组,它存储游戏屏幕上像素的颜色值。这个数组以随机值初始化,然后在游戏循环中重新计算。有关涉及函数的更多信息,请参阅下一节。如前所述,以下代码如下:

import os, pygame
from pygame.locals import *
import numpy
from scipy import ndimage

def get_pixar(arr, weights):
  states = ndimage.convolve(arr, weights, mode='wrap')

  bools = (states == 13) | (states == 12 ) | (states == 3)

  return bools.astype(int)

def draw_cross(pixar):
   (posx, posy) = pygame.mouse.get_pos()
   pixar[posx, :] = 1
   pixar[:, posy] = 1

def random_init(n):
   return numpy.random.random_integers(0, 1, (n, n))

def draw_pattern(pixar, pattern):
     print pattern

     if pattern == 'glider':
      coords = [(0,1), (1,2), (2,0), (2,1), (2,2)]
     elif pattern == 'block':
      coords = [(3,3), (3,2), (2,3), (2,2)]
     elif pattern == 'exploder':
      coords = [(0,1), (1,2), (2,0), (2,1), (2,2), (3,3)]
     elif pattern == 'fpentomino':
      coords = [(2,3),(3,2),(4,2),(3,3),(3,4)]

     pos = pygame.mouse.get_pos()

     xs = numpy.arange(0, pos[0], 10)
     ys = numpy.arange(0, pos[1], 10)

     for x in xs:
        for y in ys:
           for i, j in coords:
               pixar[x + i, y + j] = 1

def main():
    pygame.init ()

    N = 400
    pygame.display.set_mode((N, N))
    pygame.display.set_caption("Life Demo")

    screen = pygame.display.get_surface()

    pixar = random_init(N)
    weights = numpy.array([[1,1,1], [1,10,1], [1,1,1]])

    cross_on = False

    while True:
       pixar = get_pixar(pixar, weights)

       if cross_on:
          draw_cross(pixar)

       pygame.surfarray.blit_array(screen, pixar * 255 ** 3)
       pygame.display.flip()

       for event in pygame.event.get():
         if event.type == QUIT:
             return
         if event.type == MOUSEBUTTONDOWN:
            cross_on = not cross_on
         if event.type == KEYDOWN:
            if event.key == ord('r'):
               pixar = random_init(N)
               print "Random init"
            if event.key == ord('g'):
               draw_pattern(pixar, 'glider')
            if event.key == ord('b'):
               draw_pattern(pixar, 'block')
            if event.key == ord('e'):
               draw_pattern(pixar, 'exploder')
            if event.key == ord('f'):
               draw_pattern(pixar, 'fpentomino')

if __name__ == '__main__':
    main()

注意

你应该在 YouTube 上观看一个屏幕录像,链接为 www.youtube.com/watch?v=NNsU-yWTkXM

游戏动作的截图如下所示:

如何做...

工作原理...

我们使用了一些需要解释的 NumPy 和 SciPy 函数:

函数 描述
ndimage.convolve(arr, weights, mode='wrap') 这在给定数组上应用卷积操作,使用 wrap 模式中的权重。模式与数组边界有关。有关数学细节,请参阅en.wikipedia.org/wiki/Convolution
bools.astype(int) 这将布尔数组转换为整数。
numpy.arange(0, pos[0], 10) 这创建了一个从 0pos[0] 的数组,步长为 10。所以如果 pos[0] 等于 1000,我们将得到 0, 10, 20 … 990。
posted @ 2025-09-18 14:36  绝不原创的飞龙  阅读(48)  评论(0)    收藏  举报