Python 3.x

您所在的位置:网站首页 洋葱炒鸡蛋怎样炒才好吃又简单窍门 Python 3.x

Python 3.x

2023-11-26 23:13| 来源: 网络整理| 查看: 265

游戏作为一个休闲娱乐的活动,吸引着众多人的参与,当你畅享游戏乐趣的时候,有没有考虑编制一个自己的游戏呢?本文将介绍如何在Python 中如何进行游戏编程。

一、认识 pygame

pygame 模块是一套用来设计游戏的第三方模块,可以运行在几乎所有的平台和操作系统上,它是基于 SDL(Simple DirectMedia Layer)库的。SDL 是一套开放源代码的跨平台多媒体开发库,使用 C 语言编写,它提供了多种控制图像、声音、输入、输出等函数,让开发者只要用相同或相似的代码就可以开发出跨平台(Linux、Windows、Mac OS X 等)的应用软件。目前 SDL 多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。Python语言通过该模块可以创建完全界面化的游戏和多媒体程序。

01 安装 pygame

pygame 模块安装的方法与其他模块类似,打开命令终端,在命令行中输入以下命令,即可实现模块的安装。

pip3 install pygame

然后在程序的开始导入所需模块,本节的例子需要导入 pygame 模块和 sys 模块,其代码为:

import pygame import sys

首先初始化所有引入的模块,因为用 pygame 做任何事情之前必须先进行初始化,下面代码为初始化 pygame 对象。

pygame.init()

由于人类眼睛的特殊生理结构,当所看画面的帧率高于 24 时,就会认为是连贯的,此现象称为视觉暂留。帧率(Frame Rate)是用于测量显示帧数的量度,其测量单位为每秒显示帧数 (Frames per Second,FPS)。一般来说 30 帧是可以接受的,将性能提升至 60 帧则可以明显提升交互感和逼真感,但是超过 75 帧就不容易察觉到有明显的流畅度提升了。

下面代码创建 clock 对象,可以用来设置游戏的帧率:

clock = pygame.time.Clock()

下面代码设定窗口的宽和高:

size = width, height = 500, 300

下面代码设定窗口中物体的移动速度,变量给出的 [2,1] 表示x 方向移动 2 个像素,y 方向移动 1 个像素:

speed = [2, 1]

下面代码设定窗口的背景颜色:

bg = (255, 255, 255)

02 surface 对象

pygame 用 surface 对象来描述图像,就像一个画板一样。pygame.display.set_mode() 函数创建一个新的 surface 对象来描述实际显示的图形。此时在 surface 上画的任何东西都将在显示器上可见。

下面代码创建了一个图形化窗口的 surface 对象:

screen = pygame.display.set_mode(size)

03 绘制图像

对生成的 Windows 窗口设置标题使用 pygame.display.set_caption(' ') 方法,括号内的字符串用来设置窗口上的标题,下面是常用的两个命令。

● pygame.display.get_caption():获得窗口的标题。

● pygame.display.set_caption(title):设置窗口的标题。

下面代码设置窗口标题为“舞动的小球”:

pygame.display.set_caption(' 舞动的小球 ')

下面代码加载图片:

ball = pygame.image.load('./images/ball.png')

下面代码返回一个覆盖整个 surface 的矩形并赋给变量 position,这个矩形的左上角位于窗口的(0,0)位置,大小与所装入的图形一样。

position = ball.get_rect()

04 移动图像

程序在循环体中不断地检测用户的输入,然后根据输入移动图像、绘制图像等。

下面代码获取事件并循环判断事件的类型,然后根据时间的类型执行不同的操作。

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

移动图像的过程中,判断小球是否超出了窗口的边界,如果超出边界,就翻转图像,向反方向运行,这样小球就回到窗口中。

position = position.move(speed) # 判断位置 if position.left < 0 or position.right > width: # 翻转图像 ball = pygame.transform.flip(ball, True, False) # 反方向移动 speed[0] = -speed[0] if position.top < 0 or position.bottom > height: speed[1] = -speed[1] ball = pygame.transform.flip(ball, False, True)

05 界面的更新

下面代码对窗口填充背景色,相当于把原先窗口中显示的内容都清除:

screen.fill(bg)

下面代码绘制小球运动后的图像:

screen.blit(ball, position)

下面代码更新窗口界面 , 使所画的小球在窗口中可见:

pygame.display.update()

06 控制速度

下面代码设置游戏的帧率:

clock.tick(100)

执行程序,效果如下图

二、事件的处理

事件是用户或浏览器自身执行的某种动作,如单击鼠标左键、鼠标右键、鼠标移动,按键盘上的某个键等。响应某个事件的函数称为事件处理程序(也称事件处理函数、事件句柄)。

01 理解事件

pygame 模块支持各种事件,如键盘事件、鼠标事件、游戏手柄事件等,事件的获取通过 pygame.event.get() 方法得到。下面通过一个实例来捕获鼠标或键盘事件,然后显示事件的结果。

pygame 中常用的事件如表。

代码前面的导入模块和创建surface对象的方法与12.1节的实例一样,不同之处的代码如下。

下面代码设置要显示字体 Font 对象的样式,此处设置为默认样式 22 像素:

font = pygame.font.Font(None, 22)

下面代码获取行高:

line_height = font.get_linesize()

下面代码给出字体的初始位置:

x,y = 0,0

下面代码设置背景的填充颜色:

screen.fill(bg)

02 捕捉事件

捕捉事件是指通过一个消息循环不断捕捉各种事件,并将事件的名称显示到窗口中。

下面代码创建一个 surface 对象,用于显示文字:

surface_font = font.render(str(event), True, (255, 0, 0))

下面代码将 surface_font 放在 screen 指定位置上:

screen.blit(surface_font,(x, y))

每次事件都显示到不同行,因此程序每次需要获取下一行的位置:

y += line_height

显示结果时,需要进行判断,如果超过边界,从 0 行重新开始显示:

if y > height: y = 0 # 填充颜色,覆盖原来的内容 screen.fill(bg)

下面代码更新界面 , 使自己所画的内容在窗口中可见:

pygame.display.flip()

下面代码设置帧率:

clock.tick(100)

运行代码后移动鼠标、单击鼠标左 / 右键或按不同的键盘键,部分结果如图。

从图 12-2 中可以看出,分别显示了按下键盘上的键(KeyDown)、松开键盘上的键(KeyUp)、移动鼠标(MouseMotion)等事件。

下面可以改写前面小球移动的例子,通过按键盘上的上、下、左、右箭头来控制小球的移动方向,控制事件的代码为:

if event.type == KEYDOWN: # 左键 if event.key == K_LEFT: speed[0] = -x # 右键 if event.key == K_RIGHT: speed[0] = x # 上键 if event.key == K_UP: speed[1] = -y # 下键 if event.key == K_DOWN: speed[1] = y

其他代码与原先代码一样,不再给出。通过上面的代码可以看出,首先判断键盘是否被按下(KeyDown),如果键盘被按下,再判断是否是上下左右箭头(K_UP 、K_DOWN 、K_LEFT、K_RIGHT),然后修改移动的方向。

三、有趣的功能

在游戏操作过程中,经常会使用全屏、调整图像大小、图像透明度及音效等功能,下面介绍如何在游戏中实现这些基本功能。

01 显示模式

前面介绍 surface 对象时,没有提到显示模式的概念。显示模式是指显示器的颜色、亮度、对比度等基本特性,在游戏创建过程中,经常会遇到窗口全屏、尺寸改变等情况,这时窗口中的内容都需要重新绘制。

(1)全屏

下面通过一个实例演示如何实现窗口全屏的转换,该程序运行时,窗口大小为 500×300像素,当按“F10”键时转换为全屏大小,再次按“F10”键,又转换为原始大小,其实现方式和 12.2 节实例中的很多地方类似。首先创建 surface 对象,其次设置其初始大小,然后在程序中捕捉键盘事件,进行窗口大小的转换,其中主要使用了 pygame.display.set_mode() 的方法,该方法的语法格式为:

pygame.display.set_mode(resolution=(0,0),flags=0,depth=0)

返回值也是一个 surface 对象,其中参数 resolution 可以控制生成 Windows 窗口的大小,flags 代表的是扩展选项,depath 不推荐设置。

flags 标志位控制显示屏的样式,主要参数有下面几个。

pygame.FULLSCREEN:控制全屏,用 0 或 1 来控制。

pygame.HWSURFACE:控制是否进行硬件加速。

pygame.RESIZABLE:控制窗口是否可以调节大小。

这几个参数值相当于是全局的常量,使用时可以通过 from pygame.locals import * 导入。

因此,如果有窗口大小,就可以直接设置窗口按照这个大小进行显示。例如:

size = width, height = 500, 300 screen = pygame.display.set_mode(size)

另外,可以使用下面的方法获取当前屏幕信息:

current = pygame.display.Info()

然后使用下面的代码设置窗口为全屏:

screen = pygame.display.set_mode((current.current_w, current.current_h), FULLSCREEN | HWSURFACE)

程序中捕捉键盘的代码和 12.2 节一样,只不过这里判断是否等于 F10,代码为:

event.key == K_F10

另外,可以通过一个变量 isfull 的值来判断当前是否是全屏,如果是则其值为 True,否则为 False,然后根据这个变量的值判断是否需要在全屏和原始大小之间转换,代码为:

if event.key == K_F10: if isfull: screen = pygame.display.set_mode(size) else: # 设置全屏 screen = pygame.display.set_mode((current.current_ w, current.current_h), FULLSCREEN | HWSURFACE) isfull = not isfull

(2)调节窗口尺寸

上面的实例窗口大小只能在原始尺寸和全屏之间通过控制 F10 键进行转换,不能任意改变窗口的大小,下面介绍如何改变窗口的大小。

与前面类似,首先在 pygame.display.set_mode 方法中有一个参数 RESIZABLE 控制窗口是否可以调节大小。

然后程序监听事件进行相应的操作,此时监听事件为 VIDEORESIZE,即当鼠标移动到窗口边缘时可以进行拖动以变换大小,代码为:

if event.type == VIDEORESIZE: size = event.size screen = pygame.display.set_mode(size, RESIZABLE)

除了修改窗口的显示模式外,还可以变换其中图像的大小或内容,实现不同图像之间的转换,并且可以调节图像的透明度。

02 调整图像大小

下面的实例可以改变窗口中图像的大小,当按下键盘上的“=”键(或“+”键)时图像增大;当按下键盘上的“-”键时图像变小;当按下键盘上的“Space”键时,图像恢复原始大小。变换大小主要通过 pygame.transform.scale() 方法实现,该方法有两个参数,格式为:

pygame.transform.scale(old_ball,current_size)

其中,第一个参数是要调整图像的大小,第二个参数是调整尺寸。在这个实例中,当按下不同键时,可以设定图像变换大小的比例。

主要代码为:

if event.type == KEYDOWN: if event.key == K_MINUS or event.key == K_EQUALS or event. key == K_SPACE: if event.key == K_MINUS: if rate > 0.5: rate -= 0.1 elif event.key == K_EQUALS: if rate < 3: rate += 0.1 elif event.key == K_SPACE: rate = 1.0 current_size = (int(old_position.width * rate),int(old_ position.height * rate)) ball = pygame.transform.scale(old_ball,current_size)

下是图像调整大小前的界面。

图像原始大小

下是图像放大的界面。

放大的图像

当然,pygame.transform 还可以用来对图像进行翻转、调整分辨率等操作。

03 转换图像透明度

在游戏运行过程中,经常需要转换其中的图像,有时还会对图像的透明度进行转换,下面介绍如何实现这些功能。

实现图形更换,首先需要加载图片然后实现转换,转换图片使用方法 convert()。例如,下面代码导入两幅图像并进行转换:

bg = pygame.image.load('./images/bg.png').convert() dog = pygame.image.load('./images/dog.png').convert()

注意,此处导入图像与前面实例中的导入方法不一样,前面实例导入图像是使用下面的代码:

ball = pygame.image.load('./images/ball.png')

而使用方法 convert() 可以方便地对图像进行透明度等修改,因此导入图像时建议使用该方法。

文二代码中的 .blit() 方法为更换图像,其第一个参数是前面导入的转换图像,第二个参数是图像的位置。

而透明度修改使用 set_alpha() 方法,其中,参数 alpha 的值为 0~255,0 代表完全透明,255 是完全不透明。

在程序中设置透明度使用键盘控制,当按下键盘上的“=”键(或“+”键)时,透明度增加;

当按下键盘上的“-”键时透明度变小;当按下键盘上的“Space”键时,透明度恢复原始值。

if event.type == KEYDOWN: if event.key == K_MINUS or event.key == K_EQUALS or event. key == K_SPACE: if event.key == K_MINUS: if alpha > 50: alpha -= 50 if alpha255: alpha = 255 else: alpha = 255

运行程序,原始图像的透明度效果如下左图所示;当按下键盘上的“-”键时,透明度变小,效果如下右图所示。

04 音效

游戏音乐会给人带来更加现实的感受。音效可以分为背景音效和特效音效。其中,背景音效是当程序启动时就有的,而特效音效可以在程序运行过程中通过按下键盘上的不同键产生。可以使用下面两种方法产生音效。

pygame.mixer.Sound(filename)

pygame.mixer.Sound() 方法返回一个 Sound 对象。调用它的 .play( ) 方法,即可播放较短的音频文件(如玩家受到伤害、收集到金币等)。

pygame.mixer.music.load(filename)

pygame.mixer.music.load() 方法用来加载背景音乐。调用该方法即可播放背景音乐(在同一个时刻 pygame 只允许加载一个背景音乐)。

下面通过代码介绍如何实现加载背景音效。

首先需要加载背景音乐,也是使用surface对象,使用pygame.mixer.music.load()方法导入,其中参数设置为音乐文件的路径:

pygame.mixer.music.load('./sounds/bg.wav')

同时需要使用 set_volume() 方法设置音效的音量:

pygame.mixer.music.set_volume(0.1)

然后使用 play() 方法播放:

pygame.mixer.music.play()

对于游戏中的控制音效,可以先导入文件,并设置音量,然后在程序中根据需要监听键盘按键,根据设置的按键进行播放。例如,下面就是设置一段控制音效的代码:

dog_sound = pygame.mixer.Sound('./sounds/dog.wav') dog_sound.set_volume(0.8)

上面这段代码中虽然导入文件并设置了音量,但是并没有使用 play() 方法播放,在程序中通过监听键盘按键,如果按下的是“Enter”键则播放控制音效,如果按下的是“Space”键则停止播放控制音效,其代码为:

if event.type == KEYDOWN: if event.key == K_RETURN or event.key == K_SPACE: if event.key == K_RETURN: # 播放 dog_sound.play() else: # 停止 dog_sound.stop()

四、图形的绘制

在游戏中,绘制线段、矩形、多边形、圆形、椭圆形等图形时需要使用 pygame.draw模块,该模块用于在 surface 对象上绘制一些简单的形状。常用的图形绘制函数有以下几类。

● pygame.draw.rect():绘制矩形。

● pygame.draw.polygon():绘制多边形。

● pygame.draw.circle():根据圆心和半径绘制圆形。

● pygame.draw.ellipse():根据限定矩形绘制一个椭圆形。

● pygame.draw.arc():绘制弧线。

● pygame.draw.line():绘制线段。

● pygame.draw.lines():绘制多条连续的线段。

● pygame.draw.aaline():绘制抗锯齿的线段。

● pygame.draw.aalines():绘制多条连续的线段(抗锯齿)。

大部分函数用 width 参数指定图形边框的大小,如果 width = 0 则表示填充整个图形。所有的绘图函数仅能在 surface 对象的剪切区域生效。这些函数返回一个 Rect,表示包含实际绘制图形的矩形区域。大部分函数都有一个 color 参数,传入一个表示 RGB 颜色值的三元组,当然也支持 RGBA 四元组。其中的 A 是指 Alpha ,用于控制透明度。不过该模块的函数并不绘制透明度,而是直接传入对应 surface 对象的 pixel alphas 中。color 参数也可以是已经映射到 surface 对象像素格式中的整型像素值。当这些函数在绘制时,必须暂时锁定 surface 对象。许多连续绘制的函数可以通过一次性锁定直到画完再解锁来提高效率。

下面通过一些实例介绍如何使用这些函数。

01 绘制线段

绘制线段包含绘制单条线段、多条连续的线段及抗锯齿的线段,基本语法分别如下。绘制线段的语法格式为:

line(Surface, color, start_pos, end_pos, width=1)

其中,start_pos、 end_pos 分别是线段开始点和结束点的坐标。

绘制多条连续线段的语法格式为:

lines(Surface, color, closed, pointlist, width=1)

其中,pointlist 参数是一系列点的坐标。如果 closed 参数设置为 True,则绘制首尾相连的线段。

绘制抗锯齿线段的语法格式为:

aaline(Surface, color, startpos, endpos, blend=1)

其中,blend 参数指定是否通过绘制混合背景的阴影来实现抗锯齿功能。

绘制多条连续线段(抗锯齿)的语法格式为:

aalines(Surface, color, closed, pointlist, blend=1)

其中,pointlist 参数是一系列端点。如果 closed 参数设置为 True,则绘制首尾相连的线段。blend 参数指定是否通过绘制混合背景的阴影来实现抗锯齿功能。

例如,下面的两段代码分别绘制红色的线段和消除锯齿的线段。

pygame.draw.line(screen, RED, (10, 10), (450, 30), 1) pygame.draw.lines(screen, RED, 1, [(100,40),(200,40),(290,240),(10,240)], 1)

上面第一行代码从点 (10, 10) 到点 (450, 30) 之间绘制一条红色的线段;第二行代码在点(100,40), 点 (200,40), 点 (290,240), 点 (10,240) 之间绘制 3 条连续的红色线段。

pygame.draw.aaline(screen, BLACK, (30, 300), (480, 80), 1) pygame.draw.aalines(screen, BLACK, 1, [(100, 340), (200, 340), (290,540), (10, 540)], 1)

上面第一行代码从点 (30, 300) 到点 (480, 80) 之间绘制一条黑色的抗锯齿的线段;第二行代码在点 (100,340), 点 (200,340), 点 (290,540), 点 (10,540) 之间绘制 3 条连续的黑色抗锯齿的线段。

绘制线段是先创建一个 surface 对象,用于显示屏幕上的所有信息,并设置窗口的大小和标题,以及游戏的帧率,最后更新界面,使所画的线段在窗口中可见。代码的运行结果如图。

绘制各种线段

02 绘制矩形

矩形绘制的基本语法格式为:

rect(Surface, color, Rect, width=0)

以上代码表示在 surface 对象上绘制一个矩形。其中,Rect 参数指定矩形的位置和尺寸,width 参数指定边框的宽度,如果设置为 0 则表示填充该矩形。

例如以下代码:

pygame.draw.rect(screen, RED, (10, 100,100, 200), 0) pygame.draw.rect(screen, GREEN, (120, 100,100, 200), 1) pygame.draw.rect(screen, GREEN, (330, 100,100, 200), 10)

上面代码中第一行表示绘制一个矩形,矩形的左顶点坐标是 (10, 100),长和宽分别是100 像素和 200 像素,矩形边界颜色是红色,并且填充该矩形;第二行表示绘制一个矩形,矩形的左顶点坐标是 (120, 100),长和宽分别是 100 像素和 200 像素,矩形边界颜色是绿色,并且边框宽度是 1 像素;第三行表示绘制一个矩形,矩形的左顶点坐标是 (330, 100),长和宽分别是 100 像素和 200 像素,矩形边界颜色是绿色,并且边框宽度是 10 像素。

程序的运行结果如图

矩形的绘制

03 绘制多边形

绘制多边形的基本语法格式为:

polygon(Surface, color, pointlist, width=0)

在 surface 对象上绘制一个多边形。其中,pointlist 参数指定多边形的各个顶点,width 参数指定边框的宽度,如果设置为 0 则表示填充该矩形。

例如:

pygame.draw.polygon(screen, RED, [[100, 100], [0, 200], [200, 200]], 1)

上面代码绘制一个多边形,多边形的各个顶点坐标依次为(100, 100), (0, 200), (200, 200),边界宽度为 1 像素。

程序代码执行结果如图:

多边形的绘制

04 绘制圆形

绘制圆形需要根据圆心和半径绘制,其基本语法格式为:

circle(Surface, color, pos, radius, width=0)

在 surface 对象上绘制一个圆形。其中,pos 参数指定圆心的位置,radius 参数指定圆的半径,width 参数指定边框的宽度,如果设置为 0 则表示填充该矩形。

例如:

pygame.draw.circle(screen, RED, [60, 250], 40,2)

上面代码以点(60, 250)为圆心,以 40 为半径,绘制一个红色的圆形,边界宽度为 2。

程序执行结果如图

圆形的绘制

05 绘制椭圆形

根据限定矩形绘制一个椭圆形,基本语法格式为:

ellipse(Surface, color, Rect, width=0)

在 surface 对象上绘制一个椭圆形。其中,Rect 参数指定椭圆外围的限定矩形,width 参数指定边框的宽度,如果设置为 0 则表示填充该矩形。

例如:

pygame.draw.ellipse(screen, RED, [20,20,200,100],2) pygame.draw.ellipse(screen, RED, [200,200,180,180],2)

上面代码第一行绘制一个椭圆,椭圆外围的限定矩形由(20,20,200,100)确定,边框为红色,宽度为 2;第二行代码与第一行类似,但是由于此时限定矩形为(200,200,180,180),长和宽都是 180,因此此时绘制的是圆形。

程序执行结果如图

椭圆形的绘制

06 绘制弧线

绘制弧线的基本语法格式为:

arc(Surface, color, Rect, start_angle, stop_angle, width=1)

以上代码表示在 surface 对象上绘制一条弧线。其中,Rect 参数指定弧线所在椭圆外围的限定矩形,两个 angle 参数指定弧线的开始和结束位置,width 参数指定边框的宽度。

例如:

pygame.draw.arc(screen, BLACK, [210, 75, 150, 200], 0, pi / 2, 2) pygame.draw.arc(screen, GREEN, [210, 75, 150, 200], pi / 2, pi, 4) pygame.draw.arc(screen, BLUE, [210, 75, 150, 200], pi, 3 * pi / 2, 3) pygame.draw.arc(screen, RED, [210, 75, 150, 200], 3 * pi / 2, 2 * pi, 1)

上面代码分别绘制 4 条弧线,如第一行代码表示指定弧线所在椭圆外围的限定矩形为(210, 75, 150, 200),起始和结束的位置的角度分别为 0 和 pi / 2,边界宽度为 2。

程序执行结果如图

弧线的绘制

五、碰撞检测

游戏中的图像会经常运动,如果和其他物体产生碰撞要如何处理呢,下面介绍实现碰撞的代码。

01 动画精灵

精灵(sprite)在游戏动画中一般是指一个独立运动的画面元素,但在 pygame 中,可以是一个带有图像(surface)和大小位置(rect)的对象。精灵可以认为是一个个小图片,是一种可以在屏幕上移动的图形对象,并且可以与其他图形对象交互。精灵图像可以是使用pygame 绘制函数绘制的图像,也可以是原来就有的图像文件。

pygame.sprite.Sprite 是 pygame 中用来实现精灵的一个类。在使用时,并不需要对它进行实例化,只需要继承它,然后按需写出自己的类即可,因此非常简单实用。这个实例要实现的效果是由若干个小球在窗口中运动,当小球碰到窗口的边界时会反弹回来。

动画精灵

要实现上面的效果,首先继承 pygame.sprite.Sprite 类,代码为:

class Ball(pygame.sprite.Sprite): def _init_(self, image, position, speed, screen_size): pygame.sprite.Sprite._init_(self) self.image = pygame.image.load(image).convert() self.rect = self.image.get_rect() self.rect.left, self.rect.top = position self.speed = speed self.screen_size = screen_size def move(self): # 移动 self.rect = self.rect.move(self.speed) # 判断 if self.rect.left < 0 or self.rect.right > self.screen_size[0]: # 翻转图像 self.image = pygame.transform.flip(self.image, True, False) # 反方向移动 self.speed[0] = -self.speed[0] if self.rect.top < 0 or self.rect.bottom > self.screen_size[1]: # 翻转图像 self.image = pygame.transform.flip(self.image, False, True) # 反方向移动 self.speed[1] = -self.speed[1]

在这个继承的 Ball 类中定义了两个方法,其中 _init_() 方法初始化一些基本参数,如图像的初始位置、移动速度和屏幕大小,move() 方法定义小球如何移动,如碰到边界时如何翻转、反方向移动等。

然后在主程序中分别定义这些小球,代码为:

balls = [] # 创建 6 个小球 for i in range(6): # 随机位置 position = randint(0, width-50), randint(0, height-50) # 随机速度 speed = [randint(-3, 3), randint(-3, 3)] # 判断 if speed == [0,0]: speed=[2,2] # 创建精灵对象 ball = Ball('./images/ball.png', position, speed, screen_size) # 添加到集合中 balls.append(ball)

为便于管理各个动画精灵,创建一个元组 balls 存储所有小球,循环体中随机创建 6 个小球,每个小球的开始位置、速度都使用随机函数 randint() 产生,然后调用前面创建的 Ball类分别创建这些对象,其中的参数分别是小球所在的目录及文件名、初始位置、速度及屏幕的大小。

最后使用下面代码调用 move() 方法控制每个小球的移动,然后再执行位置显示,并按照设定的帧率更新页面。

for item in balls: item.move() screen.blit(item.image, item.rect) pygame.display.flip() clock.tick(100)

在上面代码中,小球运动到边界时,使用的并不是碰撞检测,而是根据小球位置是否超出窗口大小进行判断,当超过窗口时,其运动为反方向运动。两个小球在空间相遇的情况,并没有考虑。

02 碰撞检测

碰撞就是游戏中的元素是否碰到一起,如打飞机游戏,没有躲开炮弹就算碰撞。碰撞需要及时检测出来,当出现碰撞时要么游戏结束,要么重新开始游戏。

pygame 支持非常多的冲突检测技术,下面是一些常用的检测技术。

(1)两个精灵之间的矩形检测:在只有两个精灵时可以使用 pygame.sprite.collide_rect()函数来进行一对一的冲突检测。这个函数需要传递两个参数,并且每个参数都是需要继承自pygame.sprite.Sprite。

(2)圆形冲突检测:矩形冲突检测并不适用于所有形状的精灵,因此 pygame 中还有圆形冲突检测。pygame.sprite.collide_circle() 函数是基于每个精灵的半径值来进行检测的。

(3)两个精灵之间的像素遮罩检测: pygame 还提供了一个更加精确的检测——pygame.sprite.collide_mask()。这个函数接收两个精灵作为参数,返回值是一个 bool 变量。

(4)精灵和组之间的矩形冲突检测:调用pygame.sprite.spritecollide(sprite,sprite_group,bool) 函数时,一个组中的所有精灵都会逐个地对另外一个单个精灵进行冲突检测,发生冲突的精灵会作为一个列表返回。这个函数的第一个参数就是单个精灵,第二个参数是精灵组,第三个参数是一个 bool 值。最后这个参数起了很大的作用,当为 True 时,会删除组中所有冲突的精灵,当为 False 时不会删除冲突的精灵。基本语法格式为:

list_collide = pygame.sprite.spritecollide(sprite,sprite_group,False);

● 精灵组之间的矩形冲突检测:利用 pygame.sprite.groupcollide() 函数可以检测两个组之间的冲突,返回一个字典。

下面通过实例学习一下如何使用这些检测技术。

与上面动画精灵程序不一样的地方是此时通过创建一个精灵组进行操作,代码为:

balls = [] group = pygame.sprite.Group() for i in range(6): position = randint(0, width-50), randint(0, height-50) speed = [randint(-3, 3), randint(-3, 3)] if speed == [0,0]: speed=[2,2] ball = Ball('./images/ball.png', position, speed, screen_size) while pygame.sprite.spritecollide(ball, group, False): ball.rect.left, ball.rect.top = randint(0, width - 100), randint(0, height - 100) balls.append(ball) group.add(ball)

六、飞机大战

通过前面的学习,大家已经认识了 pygame 创建游戏的基本方法和使用过程,下面就通过一个综合的游戏—飞机大战,来加深对这些基本知识的理解。

首先看一下飞机大战游戏情况的基本说明,该游戏需要具有以下功能。

① 敌方有小、中、大 3 种飞机,速度不同。

② 消灭小飞机、中飞机、大飞机分别需要 1、8、20 发子弹。

③ 每消灭一架小飞机、中飞机、大飞机分别得 1000、6000、10000 分。

④ 每 30 秒有一个随机的子弹补给:全屏炸弹和双倍子弹。其中,全屏炸弹最多 3 个,双倍子弹最多使用 18 秒。

⑤ 根据分数逐步提高游戏难度,通过飞机数量的增多和速度的加快来实现。

⑥ 玩家有 3 次机会。

⑦ 游戏结束后,显示分数。

效果如图所示。

经过分析,这个游戏需要许多第三方模块来实现,并且监听键盘和鼠标事件及碰撞检测。同时,飞机需要发射不同的子弹与敌方的飞机作战。游戏过程中可以暂停游戏,并且可以控制游戏的难度,游戏可以随机发放补给,在结束时有提示画面,同时还有声音等其他效果。下面结合基本功能介绍代码如何实现。

首先导入所需模块,代码为:

import pygame import sys import traceback from pygame.locals import * from random import *

(1)敌方有小、中、大 3 种飞机,速度不同,消灭小飞机、中飞机、大飞机分别需要 1、8、20 发子弹。

要实现 3 种飞机,可以根据飞机的相同点和功能定义一个飞机父类,代码为:

class Enemy(pygame.sprite.Sprite): def _init_(self, image_name, image_down_names, bg_size, rate1, rate2, speed, active, blood_volume=1, hit=False): pygame.sprite.Sprite._init_(self) self.image = pygame.image.load(image_name).convert_alpha() self.destroy_images = [] for each in image_down_names: self.destroy_images.append(pygame.image.load(each).convert_ alpha()) self.rect = self.image.get_rect() self.speed = speed self.active = True self.width, self.height = bg_size[0], bg_size[1] self.left_top = ( randint(0, self.width - self.rect.width), randint(-rate1 * self.height, -rate2 * self.height)) self.rect.left, self.rect.top = self.left_top self.mask = pygame.mask.from_surface(self.image) self.blood_volume = blood_volume self.hit = hit # 移动 def move(self): if self.rect.top < self.height: self.rect.top += self.speed else: self.reset() # 重置 def reset(self): self.active = True self.rect.left, self.rect.top = self.left_top

上面代码定义敌机父类 Enemy,其中定义了 3 个方法。_init_() 方法定义飞机的初始化过程,包含若干初始化参数(图像、中弹后图像、背景大小、初始位置参数 1、初始位置参数 2、速度、是否活动着、血量、是否被击中),同时还定义了飞机移动的方法 move() 和重置的方法 reset()。

接下来根据 Enemy 父类定义 3 种飞机子类,代码为:

class SmallEnemy(Enemy): def _init_(self, bg_size): super()._init_('./images/enemy1.png', [ \ './images/enemy1_down1.png', \ './images/enemy1_down2.png', \ './images/enemy1_down3.png', \ './images/enemy1_down4.png'], \ bg_size, 5, 0, 2, True )

上面代码定义小敌机类 SmallEnemy,该飞机中一枪就会毙命,其中重新定义了 _init_()方法。

class MidEnemy(Enemy): blood_volume = 8 def _init_(self, bg_size): super()._init_('./images/enemy2.png', [ \ './images/enemy2_down1.png', \ './images/enemy2_down2.png', \ './images/enemy2_down3.png', \ './images/enemy2_down4.png'], \ bg_size, 10, 1, 1, True, 8 ) self.image_hit = pygame.image.load('./images/enemy2_hit.png'). convert_alpha() def reset(self): super().reset() self.blood_volume = MidEnemy.blood_volume

上面代码定义敌机类 MidEnemy 中 8 枪才会毙命,并且重新定义了 _init_() 方法。

class BigEnemy(Enemy): blood_volume = 20 def _init_(self, bg_size): super()._init_('./images/enemy3_n1.png', [ \ './images/enemy3_down1.png', \ './images/enemy3_down2.png', \ './images/enemy3_down3.png', \ './images/enemy3_down4.png', \ './images/enemy3_down5.png', \ './images/enemy3_down6.png'], \ bg_size, 15, 5, 1, True, 20 ) self.image1 = self.image self.image2 = pygame.image.load('./images/enemy3_n2.png'). convert_alpha() self.image_hit = pygame.image.load('./images/enemy3_hit.png'). convert_alpha() def reset(self): super().reset() self.blood_volume = BigEnemy.blood_volume

上面代码定义大敌机类 BigEnemy 中 20 枪才会毙命,其中重新定义了 _init_() 方法。

(2)消灭飞机的子弹类定义如下。

同样,安装面向对象的编程思想,编制子弹的父类,代码为:

class Bullet(pygame.sprite.Sprite): # 初始化参数 ( 位置,图像,速度,是否活动着 ) def _init_(self,position,image_name,speed,active): pygame.sprite.Sprite._init_(self) self.image = pygame.image.load(image_name).convert_alpha() self.rect = self.image.get_rect() self.rect.left, self.rect.top = position self.speed = speed self.active = active self.mask = pygame.mask.from_surface(self.image) # 移动 def move(self): self.rect.top -= self.speed if self.rect.top < 0: self.active = False # 重置 def reset(self, position): self.rect.left, self.rect.top = position self.active = True

下面分别定义两种子弹子类:

class Bullet1(Bullet): def _init_(self, position): super()._init_(position,'./images/bullet1.png',12,False) class Bullet2(Bullet): def _init_(self, position): super()._init_(position,'./images/bullet2.png',14,False)

(3)我方战机的定义。

定义我方战机游戏开始时的位置,以及在游戏中如何进行上下左右移动,具体实现代码为:

class MyPlane(pygame.sprite.Sprite):

# 初始化参数 def _init_(self, bg_size): pygame.sprite.Sprite._init_(self) self.image1 = pygame.image.load('./images/me1.png').convert_alpha() self.image2 = pygame.image.load('./images/me2.png').convert_alpha() self.destroy_images = [] self.destroy_images.extend([ pygame.image.load('./images/me_destroy_1.png').convert_alpha(), pygame.image.load('./images/me_destroy_2.png').convert_alpha(), pygame.image.load('./images/me_destroy_3.png').convert_alpha(), pygame.image.load('./images/me_destroy_4.png').convert_alpha() ]) self.rect = self.image1.get_rect() self.width, self.height = bg_size[0], bg_size[1] self.rect.left, self.rect.top = \ (self.width - self.rect.width) // 2, \ self.height - self.rect.height - 60 self.speed = 10 self.active = True self.invincible = False self.mask = pygame.mask.from_surface(self.image1) # 向上移动 def moveUp(self): if self.rect.top > 0: self.rect.top -= self.speed else: self.rect.top = 0 # 向下移动 def moveDown(self): if self.rect.bottom < self.height - 60: self.rect.top += self.speed else: self.rect.bottom = self.height - 60 # 向左移动 def moveLeft(self): if self.rect.left > 0: self.rect.left -= self.speed else: self.rect.left = 0 # 向右移动 def moveRight(self): if self.rect.right < self.width: self.rect.left += self.speed else: self.rect.right = self.width # 重置 def reset(self): self.rect.left, self.rect.top = \ (self.width - self.rect.width) // 2, \ self.height - self.rect.height - 60 self.active = True self.invincible = True

(4)每 30 秒有一个随机的子弹补给:全屏炸弹和双倍子弹。其中,全屏炸弹最多 3 个,双倍子弹最多使用 18 秒。

下面代码定义补给包的父类:

class Supply(pygame.sprite.Sprite): # 初始化参数 ( 图背景大小、图像、速度、是否活动 ) def _init_(self, bg_size, image_name, speed, active): pygame.sprite.Sprite._init_(self) self.image = pygame.image.load(image_name).convert_alpha() self.rect = self.image.get_rect() self.width, self.height = bg_size[0], bg_size[1] self.rect.left, self.rect.bottom = randint(0, self.width - self.rect.width), -100 self.speed = speed self.active = active self.mask = pygame.mask.from_surface(self.image) # 移动 def move(self): if self.rect.top < self.height: self.rect.top += self.speed else: self.active = False # 重置 def reset(self): self.active = True self.rect.left, self.rect.bottom = randint(0, self.width - self.rect.width), -100

下面代码定义子弹补给包:

class Bullet_Supply(Supply): def _init_(self, bg_size): super()._init_(bg_size, './images/bullet_supply.png', 5, False)

下面代码定义全屏炸弹补给包:

class Bomb_Supply(Supply): def _init_(self, bg_size): super()._init_(bg_size, './images/bomb_supply.png', 4, False)

(5)下面代码给出程序的初始化:

pygame.init() pygame.mixer.init() bg_size = width, height = 480, 650 screen = pygame.display.set_mode(bg_size) pygame.display.set_caption(' 飞机大战 ') background = pygame.image.load('./images/background.png').convert() BLACK = (0, 0, 0) WHITE = (255, 255, 255) GREEN = (0, 255, 0) RED = (255, 0, 0)

上面代码定义了屏幕设置、背景图,以及颜色,同时下面代码载入游戏音乐:

pygame.mixer.music.load('sound/game_music.ogg') pygame.mixer.music.set_volume(0.2) bullet_sound = pygame.mixer.Sound('sound/bullet.wav') bullet_sound.set_volume(0.2) bomb_sound = pygame.mixer.Sound('sound/use_bomb.wav') bomb_sound.set_volume(0.2) supply_sound = pygame.mixer.Sound('sound/supply.wav') supply_sound.set_volume(0.2) get_bomb_sound = pygame.mixer.Sound('sound/get_bomb.wav') get_bomb_sound.set_volume(0.2) get_bullet_sound = pygame.mixer.Sound('sound/get_bullet.wav') get_bullet_sound.set_volume(0.2) upgrade_sound = pygame.mixer.Sound('sound/upgrade.wav') upgrade_sound.set_volume(0.2) enemy3_fly_sound = pygame.mixer.Sound('sound/enemy3_flying.wav') enemy3_fly_sound.set_volume(0.2) enemy1_down_sound = pygame.mixer.Sound('sound/enemy1_down.wav') enemy1_down_sound.set_volume(0.2) enemy2_down_sound = pygame.mixer.Sound('sound/enemy2_down.wav') enemy2_down_sound.set_volume(0.2) enemy3_down_sound = pygame.mixer.Sound('sound/enemy3_down.wav') enemy3_down_sound.set_volume(0.5) me_down_sound = pygame.mixer.Sound('sound/me_down.wav') me_down_sound.set_volume(0.2)

根据获取的不同事件播放不同的音乐。

(6)根据分数逐步提高游戏难度,通过飞机数量的增多和速度的加快来实现。游戏等级设置为 4 级,每一级根据游戏分数确定,当分数小于 50000 分时属于 1 级;当分数大于 50000 分、小于 300000 分时属于 2 级;当分数大于 300000 分、小于 600000 分时属于 3 级;当分数大于 600000 分、小于 1000000 分时属于 4 级;当分数大于 1000000 分时属于 5 级。每一级的速度相比上一级的速度增加一定比例。代码为:

if level == 1 and score > 50000: level = 2 upgrade_sound.play() # 增加 3 架小型敌机、2 架中型敌机和 1 架大型敌机 add_small_enemies(small_enemies, enemies, 3) add_mid_enemies(mid_enemies, enemies, 2) add_big_enemies(big_enemies, enemies, 1) # 提升小型敌机的速度 inc_speed(small_enemies, 1) elif level == 2 and score > 300000: level = 3 upgrade_sound.play() # 增加 5 架小型敌机、3 架中型敌机和 2 架大型敌机 add_small_enemies(small_enemies, enemies, 5) add_mid_enemies(mid_enemies, enemies, 3) add_big_enemies(big_enemies, enemies, 2) # 提升小型敌机的速度 inc_speed(small_enemies, 1) inc_speed(mid_enemies, 1) elif level == 3 and score > 600000: level = 4 upgrade_sound.play() # 增加 5 架小型敌机、3 架中型敌机和 2 架大型敌机 add_small_enemies(small_enemies, enemies, 5) add_mid_enemies(mid_enemies, enemies, 3) add_big_enemies(big_enemies, enemies, 2) # 提升小型敌机的速度 inc_speed(small_enemies, 1) inc_speed(mid_enemies, 1) elif level == 4 and score > 1000000: level = 5 upgrade_sound.play() # 增加 5 架小型敌机、3 架中型敌机和 2 架大型敌机 add_small_enemies(small_enemies, enemies, 5) add_mid_enemies(mid_enemies, enemies, 3) add_big_enemies(big_enemies, enemies, 2) # 提升小型敌机的速度 inc_speed(small_enemies, 1) inc_speed(mid_enemies, 1)

(7)游戏结束后,显示分数。

使用一个全局变量判断是否打破游戏纪录,如果打破纪录,则修改基本得分并保存到文件 'best.txt' 中。

if not recorded: recorded = True # 读取历史最高得分 with open('best.txt', 'r') as f: record_score = int(f.read()) # 如果玩家得分高于历史最高得分,则存档 if score > record_score: with open('best.txt', 'w') as f: f.write(str(score))

同时游戏在运行时,一直监听键盘上的哪个键被按下,或者鼠标哪个键被按下。当按下键盘上的上、下、左、右键时,控制飞机的移动;当按下“Space”键时,飞机发出子弹;当子弹发出之后,使用 pygame.sprite.spritecollide() 方法判断是否碰撞,并根据碰撞的次数确定不同敌机的坠毁;当我方 3 个战机都坠毁后游戏结束,此时给出提示信息:是否重新开始或结束游戏。具体代码为:

record_score_text = score_font.render('Best : %d' % record_score, True, (255, 255, 255)) screen.blit(record_score_text, (50, 50)) gameover_text1 = gameover_font.render('Your Score', True, (255, 255, 255)) gameover_text1_rect = gameover_text1.get_rect() gameover_text1_rect.left, gameover_text1_rect.top = \ (width - gameover_text1_rect.width) // 2, height // 3 screen.blit(gameover_text1, gameover_text1_rect) gameover_text2 = gameover_font.render(str(score), True, (255, 255, 255)) gameover_text2_rect = gameover_text2.get_rect() gameover_text2_rect.left, gameover_text2_rect.top = \ (width - gameover_text2_rect.width) // 2, \ gameover_text1_rect.bottom + 10 screen.blit(gameover_text2, gameover_text2_rect) again_rect.left, again_rect.top = \ (width - again_rect.width) // 2, \ gameover_text2_rect.bottom + 50 screen.blit(again_image, again_rect) gameover_rect.left, gameover_rect.top = \ (width - again_rect.width) // 2, \ again_rect.bottom + 10 screen.blit(gameover_image, gameover_rect) # 检测用户的鼠标操作 # 如果用户按下鼠标左键 if pygame.mouse.get_pressed()[0]: # 获取鼠标坐标 pos = pygame.mouse.get_pos() # 如果用户单击“重新开始”按钮 if again_rect.left < pos[0] < again_rect.right and \ again_rect.top < pos[1] < again_rect.bottom: # 调用 main() 函数,重新开始游戏 main() # 如果用户单击“结束游戏”按钮 elif gameover_rect.left < pos[0] < gameover_rect.right and \ gameover_rect.top < pos[1] < gameover_rect.bottom: # 退出游戏 pygame.quit() sys.exit()

初始界面如图

初始界面

此时如果用户单击“重新开始”按钮,则重新进行游戏;如果用户单击“结束游戏”按钮,则退出游戏。

上面是飞机大战游戏的基本实现过程,请读者认真分析代码,理解实现过程。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3