项目描述:基于面向对象思想,使用 pygame
模块开发一个飞机大战小游戏。
最终目标:
使用 pygame
创建图形窗口
pygame 的初始化和退出
import pygame
pygame.init()
# 游戏代码
pass
pygame.quit()
游戏中的坐标系
类似于 web 开发中的坐标系,原点在左上角,向右是 x 轴正方向,向下是 y 轴正方向。
在游戏中,所有元素都是以 矩形区域 来描述位置的,共有四个要素:(x, y) (width, height)
,分别表示矩形左上角坐标、矩形宽高。
pygame
专门提供了一个类 pygame.Rect
,用此类创建出矩形对象,就可以很方便地描述矩形区域。
hero_rect = pygame.Rect(99, 499, 119, 124)
print("坐标原点 %d %d" % (hero_rect.x, hero_rect.y))
print("英雄大小 %d %d" % (hero_rect.width, hero_rect.height))
print("英雄大小 %d %d" % hero_rect.size)
创建游戏主窗口
pygame.display
用于创建、管理游戏窗口。
pygame.display.set_mode()
:初始化游戏显示窗口set_mode(resolution=(0,0), flags=0, depth=0)
=
表示缺省值。resolution
指定屏幕的宽
和高
,默认创建的窗口大小和屏幕大小一致flags
参数指定屏幕的附加选项,例如是否全屏等等,默认不需要传递depth
参数表示颜色的位数,默认自动匹配# 创建游戏主窗口 screen = pygame.display.set_mode((480, 700))
pygame.display.update()
:刷新屏幕内容显示,稍后使用
游戏循环
游戏窗口创建完后,就可以基于这个窗口变量进行游戏主循环,设计一些游戏对象、函数了。
screen = pygame.display.set_mode((480, 700))
# 游戏循环
while True:
pass
运行之后,会出现一个没有内容的游戏窗口:
图像
图像素材
飞机大战游戏就是通过对这些图片的组合处理实现的。
背景图像
图像文件初始是保存在磁盘上的,如果想在游戏内调用,需要加载到内存。
bg = pygame.image.load("./images/background.png")
screen.blit(bg, (0, 0)) # (0, 0) 表示坐标
pygame.display.update()
英雄图像
hero = pygame.image.load("./images/me1.png")
screen.blit(hero, (189, 500))
pygame.display.update()
可以在 screen
对象完成所有 blit
方法之后,统一调用一次 display.update
方法,不必每绘制一张图像更新一次。
.png
格式的图片支持透明,所以显示出的飞机不会呈正方形,飞机图片的背景不会遮挡游戏背景。
游戏循环和游戏时钟
游戏循环
现在英雄飞机已经绘制到屏幕上了,接下来怎么能让飞机移动呢?
原理很简单:将多张图片快速地在屏幕上绘制,以产生连贯的视觉效果。
每次绘制的画面也称为 帧。每秒绘制画面的次数称为 帧数,亦即 fps。
帧数在 60 左右就足够流畅了。
游戏循环即游戏的主体。
游戏初始化:
- 设置游戏窗口
- 绘制图像初始位置
- 设置游戏时钟
游戏循环:
- 设置刷新频率
- 按 60 fps 来,即每秒刷新 60 次
- 检测用户交互
- 键盘、鼠标等
- 更新所有图像位置
- 更新屏幕显示
pygame.display.update()
游戏时钟
类 pygame.time.Clock
可以方便地设置屏幕刷新帧率,即游戏时钟。
clock = pygame.time.Clock()
i = 0
while True:
clock.tick(60)
print(i)
i += 1
英雄的简单动画实现
hero_rect = pygame.Rect(150, 500, 102, 126)
while True:
clock.tick(60)
hero_rect.y -= 1
if hero_rect.y + hero_rect.height <= 0:
hero_rect.y = 700
screen.blit(bg, (0, 0))
screen.blit(hero, hero_rect)
pygame.display.update()
注意这里的背景图像也需要更新,即 screen.blit(bg, (0, 0))
,否则只更新英雄的话会出现下面这种情况:
在游戏循环中监听事件
事件 event
就是游戏启动后,用户所做的操作。如:点击关闭按钮、点击鼠标、按下键盘 等。
监听 就是在游戏循环中,捕获用户的具体操作,针对性地做出响应。
pygame.event.get()
可以获得用户当前所做动作的事件 列表
while True:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
print("退出游戏...")
pygame.quit()
exit()
精灵和精灵组
介绍
到此为止,图像加载、位置变化、绘制图像 都需要程序员分别编写代码进行处理,这是很麻烦的事情。
为了简化开发步骤,pygame
提供了两个 类,即 精灵 和 精灵组:
pygame.sprite.Sprite
- 第一个
spirte
是模块的名称,第二个Spirte
是类的名称
- 第一个
pygame.sprite.Group
在游戏开发中,通常把 显示图像的对象 叫做 精灵 Sprite
。它有两个重要的属性:
image
:要显示的图像rect
:图像要显示在屏幕的位置
默认的 update()
方法什么事情也没做,子类可以重写此方法,在每次刷新屏幕时,更新精灵位置。
pygame.sprite.Sprite
并没有提供 image
和 rect
两个属性,需要程序员从 pygame.sprite.Sprite
派生子类,并在子类的 初始化方法 中,设置 image
和 rect
属性。
一个 精灵组 可以包含多个 精灵 对象。
调用 精灵组 对象的 update()
方法可以自动调用组内每一个精灵的 update()
方法。
调用 精灵组 对象的 draw(屏幕对象)
方法,可以将组内每一个精灵的 image
绘制在 rect
位置。
注意:仍然需要调用 pygame.display.update()
才能在屏幕看到最终结果
派生精灵子类
- 新建
plane_sprites.py
文件 - 定义
GameSprite
继承自pygame.sprite.Sprite
如果一个类的 父类 不是 object
,在重写 初始化方法 时,一定要先使用 super()
调用一下父类的 __init__
方法,保证父类中实现的初始化代码能够被正常执行。
plane_sprites.py
文件:
import pygame
class GameSprite(pygame.sprite.Sprite):
"""游戏精灵基类"""
def __init__(self, image_name, speed=1):
super().__init__()
self.image = pygame.image.load(image_name)
self.rect = self.image.get_rect()
self.speed = speed
def update(self, *args):
self.rect.y += self.speed
创建敌机
初始化部分:
from plane_sprites import *
enemy1 = GameSprite("./images/enemy1.png")
enemy2 = GameSprite("./images/enemy1.png", 10)
enemy2.rect.x = 200
enemy_group = pygame.sprite.Group(enemy1, enemy2)
游戏循环内:
enemy_group.update()
enemy_group.draw(screen)
pygame.display.update()