Pygame Zero 是一个基于 Pygame 的简化游戏开发框架,特别适合初学者和快速原型开发。它隐藏了许多底层的复杂性,使得开发者可以更专注于游戏逻辑的实现。本文将通过分析提供的代码,详细介绍如何使用 Pygame Zero 进行游戏开发。
本教程的案例代码存放于开源代码托管平台“gitcode”,项目名称为:StudyFlow。
1. 环境设置
本教程案例测试环境说明如下:
- 操作系统: Win10
- Python:3.13
- 包管理:uv
首先,确保你已经安装了 Pygame Zero。可以通过以下命令安装:
pip install pgzero
2. 基本结构
Pygame Zero 的游戏通常包含以下几个部分:
- 导入模块:导入所需的库和模块。
- 全局变量:定义游戏中的全局变量,如屏幕大小、角色位置等。
- 初始化函数:init() 函数用于初始化游戏状态。
- 更新函数:update() 函数用于更新游戏逻辑。
- 绘制函数:draw() 函数用于绘制游戏画面。
3. 代码分析
3.1 导入模块
import math
import pygame
from pgzero.actor import Actor, POS_TOPLEFT, ANCHOR_CENTER, transform_anchor
from pgzero import game, loaders
import types
import sys
import time
- math:用于数学计算,如距离和角度的计算。
- pygame:Pygame 库,Pygame Zero 的基础。
- pgzero.actor:Pygame Zero 的 Actor 类,用于管理游戏中的角色。
- pgzero.game 和 pgzero.loaders:Pygame Zero 的游戏和资源加载模块。
- sys 和 time:系统和时间模块,用于处理全屏切换和动画计时。
3.2 全屏切换
mod = sys.modules['__main__']
_fullscreen = False
def set_fullscreen():
global _fullscreen
mod.screen.surface = pygame.display.set_mode((mod.WIDTH, mod.HEIGHT), pygame.FULLSCREEN)
_fullscreen = True
def set_windowed():
global _fullscreen
mod.screen.surface = pygame.display.set_mode((mod.WIDTH, mod.HEIGHT))
_fullscreen = False
def toggle_fullscreen():
if _fullscreen:
set_windowed()
else:
set_fullscreen()
- set_fullscreen() 和 set_windowed():分别用于设置全屏和窗口模式。
- toggle_fullscreen():切换全屏和窗口模式。
3.3 鼠标显示控制
def hide_mouse():
pygame.mouse.set_visible(False)
def show_mouse():
pygame.mouse.set_visible(True)
- hide_mouse() 和 show_mouse():分别用于隐藏和显示鼠标指针。
3.4 Actor 类
Actor 类是 Pygame Zero 的核心类之一,用于管理游戏中的角色。它继承自 pgzero.actor.Actor,并添加了许多实用的方法。
3.4.1 初始化
def __init__(self, image, pos=POS_TOPLEFT, anchor=ANCHOR_CENTER, **kwargs):
self._flip_x = False
self._flip_y = False
self._scale = 1
self._mask = None
self._animate_counter = 0
self.fps = 5
self.direction = 0
super().__init__(image, pos, anchor, **kwargs)
- image:角色的图像。
- pos:角色的初始位置,默认为屏幕左上角。
- anchor:角色的锚点,默认为中心。
- _flip_x 和 _flip_y:用于控制角色图像的水平和垂直翻转。
- _scale:角色的缩放比例。
- _mask:用于像素级碰撞检测的掩码。
- fps:动画的帧率。
- direction:角色的移动方向。
3.4.2 距离和方向计算
def distance_to(self, actor):
dx = actor.x - self.x
dy = actor.y - self.y
return math.sqrt(dx**2 + dy**2)
def direction_to(self, actor):
dx = actor.x - self.x
dy = self.y - actor.y
angle = math.degrees(math.atan2(dy, dx))
if angle > 0:
return angle
return 360 + angle
- distance_to(actor):计算当前角色到另一个角色的距离。
- direction_to(actor):计算当前角色到另一个角色的方向(角度)。
3.4.3 移动和转向
def move_towards(self, actor, dist):
angle = math.radians(self.direction_to(actor))
dx = dist * math.cos(angle)
dy = dist * math.sin(angle)
self.x += dx
self.y -= dy
def point_towards(self, actor):
print(self.direction_to(actor))
self.angle = self.direction_to(actor)
def move_in_direction(self, dist):
angle = math.radians(self.direction)
dx = dist * math.cos(angle)
dy = dist * math.sin(angle)
self.x += dx
self.y -= dy
def move_forward(self, dist):
angle = math.radians(self.angle)
dx = dist * math.cos(angle)
dy = dist * math.sin(angle)
self.x += dx
self.y -= dy
def move_left(self, dist):
angle = math.radians(self.angle + 90)
dx = dist * math.cos(angle)
dy = dist * math.sin(angle)
self.x += dx
self.y -= dy
def move_right(self, dist):
angle = math.radians(self.angle - 90)
dx = dist * math.cos(angle)
dy = dist * math.sin(angle)
self.x += dx
self.y -= dy
def move_back(self, dist):
angle = math.radians(self.angle)
dx = -dist * math.cos(angle)
dy = -dist * math.sin(angle)
self.x += dx
self.y -= dy
- move_towards(actor, dist):向另一个角色移动指定距离。
- point_towards(actor):将当前角色转向另一个角色。
- move_in_direction(dist):按照当前方向移动指定距离。
- move_forward(dist):向前移动指定距离。
- move_left(dist):向左移动指定距离。
- move_right(dist):向右移动指定距离。
- move_back(dist):向后移动指定距离。
3.4.4 动画
@property
def images(self):
return self._images
@images.setter
def images(self, images):
self._images = images
if len(self._images) != 0:
self.image = self._images[0]
def next_image(self):
if self.image in self._images:
current = self._images.index(self.image)
if current == len(self._images) - 1:
self.image = self._images[0]
else:
self.image = self._images[current + 1]
else:
self.image = self._images[0]
def animate(self):
now = int(time.time() * self.fps)
if now != self._animate_counter:
self._animate_counter = now
self.next_image()
- images:角色的动画帧列表。
- next_image():切换到下一帧图像。
- animate():根据帧率播放动画。
3.4.5 图像变换
@property
def angle(self):
return self._angle
@angle.setter
def angle(self, angle):
self._angle = angle
self._transform_surf()
@property
def scale(self):
return self._scale
@scale.setter
def scale(self, scale):
self._scale = scale
self._transform_surf()
@property
def flip_x(self):
return self._flip_x
@flip_x.setter
def flip_x(self, flip_x):
self._flip_x = flip_x
self._transform_surf()
@property
def flip_y(self):
return self._flip_y
@flip_y.setter
def flip_y(self, flip_y):
self._flip_y = flip_y
self._transform_surf()
@property
def image(self):
return self._image_name
@image.setter
def image(self, image):
self._image_name = image
self._orig_surf = self._surf = loaders.images.load(image)
self._update_pos()
self._transform_surf()
def _transform_surf(self):
self._surf = self._orig_surf
p = self.pos
if self._scale != 1:
size = self._orig_surf.get_size()
self._surf = pygame.transform.scale(self._surf, (int(size[0] * self.scale), int(size[1] * self.scale)))
if self._flip_x:
self._surf = pygame.transform.flip(self._surf, True, False)
if self._flip_y:
self._surf = pygame.transform.flip(self._surf, False, True)
self._surf = pygame.transform.rotate(self._surf, self._angle)
self.width, self.height = self._surf.get_size()
w, h = self._orig_surf.get_size()
ax, ay = self._untransformed_anchor
anchor = transform_anchor(ax, ay, w, h, self._angle)
self._anchor = (anchor[0] * self.scale, anchor[1] * self.scale)
self.pos = p
self._mask = None
- angle:角色的旋转角度。
- scale:角色的缩放比例。
- flip_x 和 flip_y:控制角色图像的水平和垂直翻转。
- image:角色的当前图像。
- _transform_surf():根据当前的旋转、缩放和翻转状态,对角色图像进行变换。
3.4.6 碰撞检测
def collidepoint_pixel(self, x, y=0):
if isinstance(x, tuple):
y = x[1]
x = x[0]
if self._mask == None:
self._mask = pygame.mask.from_surface(self._surf)
xoffset = int(x - self.left)
yoffset = int(y - self.top)
if xoffset < 0 or yoffset < 0: return 0 width height='self._mask.get_size()' if xoffset> width or yoffset > height:
return 0
return self._mask.get_at((xoffset, yoffset))
def collide_pixel(self, actor):
for a in [self, actor]:
if a._mask == None:
a._mask = pygame.mask.from_surface(a._surf)
xoffset = int(actor.left - self.left)
yoffset = int(actor.top - self.top)
return self._mask.overlap(actor._mask, (xoffset, yoffset))
def collidelist_pixel(self, actors):
for i in range(len(actors)):
if self.collide_pixel(actors[i]):
return i
return -1
def collidelistall_pixel(self, actors):
collided = []
for i in range(len(actors)):
if self.collide_pixel(actors[i]):
collided.append(i)
return collided
def obb_collidepoints(self, actors):
angle = math.radians(self._angle)
costheta = math.cos(angle)
sintheta = math.sin(angle)
width, height = self._orig_surf.get_size()
half_width = width / 2
half_height = height / 2
i = 0
for actor in actors:
tx = actor.x - self.x
ty = actor.y - self.y
rx = tx * costheta - ty * sintheta
ry = ty * costheta + tx * sintheta
if rx > -half_width and rx < half_width and ry> -half_height and ry < half_height: return i i return -1 def obb_collidepointself x y='0):' if isinstancex tuple: y='x[1]' x='x[0]' angle='math.radians(self._angle)' costheta='math.cos(angle)' sintheta='math.sin(angle)' width height='self._orig_surf.get_size()' half_width='width' 2 half_height='height' 2 tx='x' - self.x ty='y' - self.y rx='tx' costheta - ty sintheta ry='ty' costheta tx sintheta if rx> -half_width and rx < half_width and ry> -half_height and ry < half_height:
return True
return False
def circle_collidepoints(self, radius, actors):
rSquare = radius ** 2
i = 0
for actor in actors:
dSquare = (actor.x - self.x)**2 + (actor.y - self.y)**2
if dSquare < rSquare:
return i
i += 1
return -1
def circle_collidepoint(self, radius, x, y=0):
if isinstance(x, tuple):
y = x[1]
x = x[0]
rSquare = radius ** 2
dSquare = (x - self.x)**2 + (y - self.y)**2
if dSquare < rSquare:
return True
return False
- collidepoint_pixel(x, y):检测指定点是否与角色发生像素级碰撞。
- collide_pixel(actor):检测当前角色是否与另一个角色发生像素级碰撞。
- collidelist_pixel(actors):检测当前角色是否与角色列表中的任何一个角色发生像素级碰撞,并返回第一个碰撞的索引。
- collidelistall_pixel(actors):检测当前角色是否与角色列表中的多个角色发生像素级碰撞,并返回所有碰撞的索引列表。
- obb_collidepoints(actors):检测当前角色的 OBB(Oriented Bounding Box)是否与角色列表中的任何一个角色发生碰撞,并返回第一个碰撞的索引。
- obb_collidepoint(x, y):检测指定点是否与当前角色的 OBB 发生碰撞。
- circle_collidepoints(radius, actors):检测当前角色的圆形碰撞区域是否与角色列表中的任何一个角色发生碰撞,并返回第一个碰撞的索引。
- circle_collidepoint(radius, x, y):检测指定点是否与当前角色的圆形碰撞区域发生碰撞。
3.4.7 绘制和获取矩形
def draw(self):
game.screen.blit(self._surf, self.topleft)
def get_rect(self):
return self._rect
- draw():将角色绘制到屏幕上。
- get_rect():获取角色的矩形区域。
4. 使用示例:弹球游戏
1. 建立项目及环境
以下是一个简单的 Pygame Zero 游戏示例,展示 Pygame Zero 的基本用法。
本案例使用uv管理项目和依赖包,命令如下:
# 创建项目,指定 Python 版本为 3.13
uv init PygameZeroEg -p 3.13
# 进入项目目录
cd PygameZeroEg
# 添加Pygame Zoer 库
uv add pgzero
# 同步项目环境
uv sync
# 激活项目虚拟环境
.\.venv\Scripts\activate
操作如下图所示:
2. 编写游戏源代码
在项目目录下创建ball.py脚本并添加如下代码:
import pgzrun
import random
# 屏幕大小
WIDTH = 800
HEIGHT = 600
# 弹球板
paddle_width = 150
paddle_height = 20
paddle = Rect((WIDTH // 2 - paddle_width // 2, HEIGHT - 50), (paddle_width, paddle_height))
paddle_speed = 5
# 球
ball_radius = 10
ball = Rect((WIDTH // 2 - ball_radius, HEIGHT // 2 - ball_radius), (ball_radius * 2, ball_radius * 2))
ball_speed_x = 3
ball_speed_y = -3
# 砖块
bricks = []
BRICK_WIDTH = 80
BRICK_HEIGHT = 30
BRICK_ROWS = 4
BRICK_COLS = WIDTH // BRICK_WIDTH
# 初始化砖块
def create_bricks():
for row in range(BRICK_ROWS):
for col in range(BRICK_COLS):
brick = Rect(
(col * BRICK_WIDTH, row * BRICK_HEIGHT + 50),
(BRICK_WIDTH - 5, BRICK_HEIGHT - 5) # 留出间隙
)
bricks.append(brick)
create_bricks()
# 更新游戏逻辑
def update():
global ball_speed_x, ball_speed_y
# 移动弹球板
if keyboard.left and paddle.left > 0:
paddle.x -= paddle_speed
if keyboard.right and paddle.right < WIDTH:
paddle.x += paddle_speed
# 移动球
ball.x += ball_speed_x
ball.y += ball_speed_y
# 球碰到边界反弹
if ball.left < 0 or ball.right> WIDTH:
ball_speed_x *= -1
if ball.top < 0: ball_speed_y if ball.colliderectpaddle: ball_speed_y for brick in bricks: if ball.colliderectbrick: ball_speed_y bricks.removebrick break if ball.top> HEIGHT:
print("Game Over!")
reset_game()
# 所有砖块被击碎,游戏胜利
if not bricks:
print("You Win!")
reset_game()
# 重置游戏
def reset_game():
global ball, bricks, ball_speed_x, ball_speed_y
ball.x = WIDTH // 2 - ball_radius
ball.y = HEIGHT // 2 - ball_radius
ball_speed_x = 3
ball_speed_y = -3
bricks = []
create_bricks()
# 绘制游戏画面
def draw():
screen.clear()
# 绘制弹球板
screen.draw.filled_rect(paddle, (0, 255, 0)) # 绿色
# 绘制球
screen.draw.filled_circle((ball.x + ball_radius, ball.y + ball_radius), ball_radius, (255, 0, 0)) # 红色
# 绘制砖块
for brick in bricks:
screen.draw.filled_rect(brick, (0, 0, 255)) # 蓝色
# 运行游戏
pgzrun.go()
2. 代码说明
弹球板
- 使用 Rect 类表示弹球板的位置和大小。
- 通过 screen.draw.filled_rect 绘制一个绿色的矩形作为弹球板。
球
- 使用 Rect 类表示球的位置和大小。
- 通过 screen.draw.filled_circle 绘制一个红色的圆形作为球。
砖块
- 使用 Rect 类表示每个砖块的位置和大小。
- 通过 screen.draw.filled_rect 绘制多个蓝色的矩形作为砖块。
- 砖块之间留出 5 像素的间隙,使画面更美观。
游戏逻辑
- 弹球板移动:通过键盘的左右箭头键控制弹球板的移动。
- 球反弹:球碰到边界、弹球板或砖块时会反弹。
- 砖块击碎:球碰到砖块后,砖块会从列表中移除。
- 游戏结束:如果球掉到底部,游戏结束。
- 游戏胜利:如果所有砖块被击碎,游戏胜利。
3. 运行游戏
- 将代码保存为 ball.py。
- 确保已安装 Pygame Zero(pip install pgzero)。
- 运行脚本:python ball.py。
本示例代码,游戏失败,会在终端提示“Game Over”,然后继续重新开始游戏。
4. 扩展功能
你可以为这个游戏添加更多功能,例如:
- 计分系统:每击碎一个砖块增加分数。
- 关卡系统:增加更多关卡,每关砖块数量和排列方式不同。
- 音效:添加球反弹、砖块击碎等音效。
- 难度调整:随着游戏进行,增加球的速度或减少弹球板的宽度。
5. 总结
Pygame Zero 是一个非常适合初学者和快速原型开发的游戏框架。通过本文的代码分析和示例,你应该能够理解如何使用 Pygame Zero 创建简单的游戏。
本案例通过一个简单的弹球游戏示例展示了如何完全通过代码绘制游戏中的图形,而不依赖外部图像文件。通过这个案例,你可以学习到如何使用 Pygame Zero 的绘图功能创建简单的游戏。希望这个案例能帮助你更好地理解 Pygame Zero 的使用,并激发你开发更多有趣的游戏!
提示: Pygame Zero 不仅仅可以用来开发游戏,也可以用于开发其他软件。
附录:Pygame Zero 主要类、API参考
注:Pygame Zero 是一个简化版的 Pygame,专为初学者和教育场景设计。
1.基本结构
Pygame Zero 游戏的基本结构如下:
import pgzrun
# 游戏初始化
def init():
pass
# 更新游戏逻辑
def update():
pass
# 绘制游戏画面
def draw():
pass
# 运行游戏
pgzrun.go()
2.API 原型说明
1. pgzrun.go()
o 功能:启动 Pygame Zero 游戏循环。 o 参数:无。 o 示例:
pgzrun.go()
2. init()
o 功能:游戏初始化函数,在游戏启动时自动调用一次。 o 参数:无。 o 示例:
def init():
global score
score = 0
3. update()
o 功能:更新游戏逻辑的函数,每帧调用一次。 o 参数:无。 o 示例:
def update():
global player_x
if keyboard.right:
player_x += 5
4. draw()
o 功能:绘制游戏画面的函数,每帧调用一次。 o 参数:无。 o 示例:
def draw():
screen.clear()
screen.draw.filled_rect(Rect((player_x, 100), (50, 50)), "red")
5. screen 对象
o 功能:用于绘制游戏画面的对象。 o 常用方法: o screen.clear():清除屏幕。 o screen.draw.filled_rect(rect, color):绘制填充矩形。 o screen.draw.circle(pos, radius, color):绘制圆形。 o screen.draw.text(text, pos, color):绘制文本。 o 示例:
def draw():
screen.clear()
screen.draw.filled_rect(Rect((100, 100), (50, 50)), "blue")
screen.draw.circle((200, 200), 30, "red")
screen.draw.text("Hello, Pygame Zero!", (50, 50), color="white")
6. keyboard 对象
o 功能:用于检测键盘输入的对象。 o 常用属性: o keyboard.left:左箭头键是否按下。 o keyboard.right:右箭头键是否按下。 o keyboard.up:上箭头键是否按下。 o keyboard.down:下箭头键是否按下。 o keyboard.space:空格键是否按下。 o 示例:
def update():
if keyboard.left:
player_x -= 5
if keyboard.right:
player_x += 5
7. mouse 对象
o 功能:用于检测鼠标输入的对象。 o 常用属性: o mouse.pos:鼠标当前位置((x, y))。 o mouse.buttons:鼠标按钮状态((left, middle, right))。 o 示例:
def update():
if mouse.buttons[0]: # 左键按下
print("Left mouse button clicked at:", mouse.pos)
8. Rect 类
o 功能:表示矩形区域。 o 构造函数:
Rect((x, y), (width, height))
o 常用属性: o rect.x:矩形左上角的 x 坐标。 o rect.y:矩形左上角的 y 坐标。 o rect.width:矩形宽度。 o rect.height:矩形高度。 o rect.left:矩形左边界。 o rect.right:矩形右边界。 o rect.top:矩形上边界。 o rect.bottom:矩形下边界。 o 示例:
player = Rect((100, 100), (50, 50))
9. Actor 类
o 功能:表示游戏中的角色或对象。 o 构造函数:
Actor(image, pos=(x, y))
o 常用属性: o actor.x:角色的 x 坐标。 o actor.y:角色的 y 坐标。 o actor.pos:角色的位置((x, y))。 o actor.image:角色的图像。 o 常用方法: o actor.draw():绘制角色。 o actor.colliderect(other):检测与另一个矩形或角色是否碰撞。 o 示例:
player = Actor("player", pos=(100, 100))
def draw():
player.draw()
10. clock 对象
o 功能:用于控制游戏时间。 o 常用方法: o clock.schedule(callback, delay):在指定延迟后调用回调函数。 o clock.schedule_interval(callback, interval):每隔指定时间调用回调函数。 o 示例:
def spawn_enemy():
enemies.append(Actor("enemy", pos=(random.randint(0, WIDTH), 0)))
clock.schedule_interval(spawn_enemy, 1.0) # 每秒生成一个敌人
11. music 对象
o 功能:用于播放背景音乐和音效。 o 常用方法: o music.play(name):播放音乐。 o music.stop():停止音乐。 o music.set_volume(volume):设置音量(0.0 到 1.0)。 o 示例:
music.play("background_music")
12. sounds 对象
o 功能:用于播放音效。 o 常用方法: o sounds[name].play():播放指定音效。 o 示例:
sounds.explosion.play()
关注我,将陆续获取更多Python编程学习资料!
头条号作者:ICodeWR
发布时间:2025年3月14日
标签:#python自学# #每天学python# #python#