目录
- Python游戏开发入门:Pygame实战
-
- [1. Pygame简介与环境搭建](#1. Pygame简介与环境搭建)
-
- [1.1 什么是Pygame](#1.1 什么是Pygame)
- [1.2 环境安装与配置](#1.2 环境安装与配置)
- [2. Pygame基础概念](#2. Pygame基础概念)
-
- [2.1 游戏循环与事件处理](#2.1 游戏循环与事件处理)
- [2.2 坐标系与基础图形](#2.2 坐标系与基础图形)
- [3. 实战项目:太空射击游戏](#3. 实战项目:太空射击游戏)
-
- [3.1 游戏设计与架构](#3.1 游戏设计与架构)
- [4. 高级游戏开发技巧](#4. 高级游戏开发技巧)
-
- [4.1 物理引擎集成](#4.1 物理引擎集成)
- [4.2 音效与音乐系统](#4.2 音效与音乐系统)
- [5. 完整游戏项目:平台跳跃游戏](#5. 完整游戏项目:平台跳跃游戏)
-
- [5.1 游戏设计与实现](#5.1 游戏设计与实现)
- [6. 完整代码结构与部署](#6. 完整代码结构与部署)
-
- [6.1 项目文件结构](#6.1 项目文件结构)
- [6.2 配置与设置文件](#6.2 配置与设置文件)
- [6.3 主程序入口](#6.3 主程序入口)
- [7. 总结与进阶学习](#7. 总结与进阶学习)
-
- [7.1 Pygame开发要点总结](#7.1 Pygame开发要点总结)
- [7.2 数学基础回顾](#7.2 数学基础回顾)
- [7.3 进阶学习路径](#7.3 进阶学习路径)
- [7.4 性能优化建议](#7.4 性能优化建议)
- [7.5 社区资源推荐](#7.5 社区资源推荐)
- [7.6 下一步学习建议](#7.6 下一步学习建议)
『宝藏代码胶囊开张啦!』------ 我的 CodeCapsule 来咯!✨
写代码不再头疼!我的新站点 CodeCapsule 主打一个 "白菜价"+"量身定制 "!无论是卡脖子的毕设/课设/文献复现 ,需要灵光一现的算法改进 ,还是想给项目加个"外挂",这里都有便宜又好用的代码方案等你发现!低成本,高适配,助你轻松通关!速来围观 👉 CodeCapsule官网
Python游戏开发入门:Pygame实战
1. Pygame简介与环境搭建
1.1 什么是Pygame
Pygame是一个开源的Python模块集,专门用于开发2D游戏和多媒体应用程序。它构建在SDL(Simple DirectMedia Layer)库之上,提供了对图形、声音、输入设备等游戏开发核心功能的简单访问接口。
Pygame的核心优势:
- 跨平台支持(Windows、macOS、Linux)
- 简单易学的API设计
- 活跃的社区和丰富的文档
- 完全免费和开源
- 与Python生态完美集成
1.2 环境安装与配置
python
# setup_environment.py
"""
Pygame开发环境设置脚本
"""
import sys
import subprocess
import importlib
from pathlib import Path
def check_python_version():
"""检查Python版本"""
version = sys.version_info
print(f"Python版本: {version.major}.{version.minor}.{version.micro}")
if version.major < 3 or (version.major == 3 and version.minor < 7):
print("❌ 需要Python 3.7或更高版本")
return False
return True
def install_pygame():
"""安装Pygame及相关依赖"""
packages = [
"pygame==2.5.2",
"numpy==1.24.3",
"pillow==10.0.1", # 图像处理
"pymunk==6.7.0", # 物理引擎
"pyopengl==3.1.7", # 3D图形支持
"noise==1.2.2" # 噪声生成(用于地形等)
]
for package in packages:
try:
print(f"安装 {package}...")
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
print(f"✅ {package} 安装成功")
except subprocess.CalledProcessError:
print(f"❌ {package} 安装失败")
return False
return True
def verify_installation():
"""验证安装结果"""
try:
import pygame
import numpy as np
from PIL import Image
print("✅ 核心库导入成功")
print(f"Pygame版本: {pygame.version.ver}")
print(f"NumPy版本: {np.__version__}")
print(f"PIL版本: {Image.__version__}")
# 测试Pygame初始化
pygame.init()
print("✅ Pygame初始化成功")
pygame.quit()
return True
except ImportError as e:
print(f"❌ 导入失败: {e}")
return False
def create_project_structure():
"""创建项目目录结构"""
directories = [
"assets/images",
"assets/sounds",
"assets/fonts",
"src/game",
"src/entities",
"src/levels",
"src/utils",
"config",
"docs"
]
for directory in directories:
Path(directory).mkdir(parents=True, exist_ok=True)
print(f"创建目录: {directory}")
# 创建示例文件
sample_files = {
"src/main.py": "# 游戏主入口文件",
"src/game/__init__.py": "# 游戏核心模块",
"src/entities/player.py": "# 玩家角色模块",
"config/settings.py": "# 游戏配置",
"README.md": "# Pygame项目说明"
}
for file_path, content in sample_files.items():
with open(file_path, "w", encoding="utf-8") as f:
f.write(content)
print(f"创建文件: {file_path}")
def main():
"""主安装流程"""
print("🎮 Pygame开发环境安装")
print("=" * 50)
if not check_python_version():
return
if not install_pygame():
print("❌ 依赖安装失败")
return
if not verify_installation():
print("❌ 环境验证失败")
return
create_project_structure()
print("\n" + "=" * 50)
print("🎉 环境设置完成!")
print("接下来可以开始Pygame游戏开发了!")
if __name__ == "__main__":
main()
2. Pygame基础概念
2.1 游戏循环与事件处理
游戏开发的核心是游戏循环(Game Loop),它持续运行直到游戏结束。每个循环周期包含三个主要步骤:
- 处理输入:检测玩家操作
- 更新游戏状态:根据输入和游戏逻辑更新对象
- 渲染输出:将游戏状态绘制到屏幕
python
# src/game/core.py
"""
Pygame游戏核心框架
"""
import pygame
import sys
from typing import Dict, List, Callable, Any
from enum import Enum
class GameState(Enum):
"""游戏状态枚举"""
RUNNING = 1
PAUSED = 2
GAME_OVER = 3
MENU = 4
class PygameEngine:
"""Pygame游戏引擎基类"""
def __init__(self, title: str, width: int = 800, height: int = 600, fps: int = 60):
"""
初始化游戏引擎
Args:
title: 窗口标题
width: 屏幕宽度
height: 屏幕高度
fps: 帧率
"""
# 初始化Pygame
pygame.init()
# 屏幕设置
self.screen_width = width
self.screen_height = height
self.screen = pygame.display.set_mode((width, height))
pygame.display.set_caption(title)
# 游戏时钟
self.clock = pygame.time.Clock()
self.fps = fps
# 游戏状态
self.game_state = GameState.RUNNING
self.running = True
# 颜色定义
self.colors = {
'black': (0, 0, 0),
'white': (255, 255, 255),
'red': (255, 0, 0),
'green': (0, 255, 0),
'blue': (0, 0, 255),
'gray': (128, 128, 128)
}
# 事件处理器字典
self.event_handlers = {}
self.key_handlers = {}
# 游戏对象列表
self.game_objects = []
print(f"🎮 游戏引擎初始化完成: {title}")
def handle_events(self):
"""处理所有事件"""
for event in pygame.event.get():
# 退出事件
if event.type == pygame.QUIT:
self.running = False
# 键盘按下事件
elif event.type == pygame.KEYDOWN:
self.handle_keydown(event.key)
# 键盘释放事件
elif event.type == pygame.KEYUP:
self.handle_keyup(event.key)
# 鼠标事件
elif event.type == pygame.MOUSEBUTTONDOWN:
self.handle_mouse_click(event.button, event.pos)
# 自定义事件处理
if event.type in self.event_handlers:
for handler in self.event_handlers[event.type]:
handler(event)
def handle_keydown(self, key: int):
"""处理键盘按下事件"""
if key in self.key_handlers:
for handler in self.key_handlers[key]:
handler()
# 全局快捷键
if key == pygame.K_ESCAPE:
self.running = False
elif key == pygame.K_p:
self.toggle_pause()
def handle_keyup(self, key: int):
"""处理键盘释放事件"""
pass
def handle_mouse_click(self, button: int, position: tuple):
"""处理鼠标点击事件"""
pass
def toggle_pause(self):
"""切换暂停状态"""
if self.game_state == GameState.RUNNING:
self.game_state = GameState.PAUSED
print("游戏暂停")
elif self.game_state == GameState.PAUSED:
self.game_state = GameState.RUNNING
print("游戏继续")
def update(self, delta_time: float):
"""更新游戏状态
Args:
delta_time: 距离上一帧的时间(秒)
"""
if self.game_state != GameState.RUNNING:
return
# 更新所有游戏对象
for obj in self.game_objects[:]: # 使用切片创建副本以避免在迭代时修改列表
if hasattr(obj, 'update'):
obj.update(delta_time)
def render(self):
"""渲染游戏画面"""
# 清空屏幕
self.screen.fill(self.colors['black'])
# 渲染所有游戏对象
for obj in self.game_objects:
if hasattr(obj, 'render'):
obj.render(self.screen)
# 显示帧率
self.show_fps()
# 更新显示
pygame.display.flip()
def show_fps(self):
"""显示帧率"""
fps_text = f"FPS: {int(self.clock.get_fps())}"
font = pygame.font.Font(None, 36)
text_surface = font.render(fps_text, True, self.colors['white'])
self.screen.blit(text_surface, (10, 10))
def add_event_handler(self, event_type: int, handler: Callable):
"""添加事件处理器"""
if event_type not in self.event_handlers:
self.event_handlers[event_type] = []
self.event_handlers[event_type].append(handler)
def add_key_handler(self, key: int, handler: Callable):
"""添加键盘事件处理器"""
if key not in self.key_handlers:
self.key_handlers[key] = []
self.key_handlers[key].append(handler)
def add_game_object(self, obj: Any):
"""添加游戏对象"""
self.game_objects.append(obj)
def remove_game_object(self, obj: Any):
"""移除游戏对象"""
if obj in self.game_objects:
self.game_objects.remove(obj)
def run(self):
"""运行游戏主循环"""
print("🚀 开始游戏循环...")
previous_time = pygame.time.get_ticks()
while self.running:
# 计算时间增量
current_time = pygame.time.get_ticks()
delta_time = (current_time - previous_time) / 1000.0 # 转换为秒
previous_time = current_time
# 游戏循环的三个核心步骤
self.handle_events()
self.update(delta_time)
self.render()
# 控制帧率
self.clock.tick(self.fps)
self.cleanup()
def cleanup(self):
"""清理资源"""
pygame.quit()
print("🎯 游戏结束")
# 基础游戏实体类
class GameObject:
"""游戏对象基类"""
def __init__(self, x: float, y: float):
"""
初始化游戏对象
Args:
x: x坐标
y: y坐标
"""
self.x = x
self.y = y
self.velocity_x = 0
self.velocity_y = 0
self.active = True
def update(self, delta_time: float):
"""更新对象状态"""
# 基础位置更新(基于速度)
self.x += self.velocity_x * delta_time
self.y += self.velocity_y * delta_time
def render(self, surface: pygame.Surface):
"""渲染对象"""
pass
def get_position(self) -> tuple:
"""获取位置"""
return (self.x, self.y)
def set_position(self, x: float, y: float):
"""设置位置"""
self.x = x
self.y = y
def get_velocity(self) -> tuple:
"""获取速度"""
return (self.velocity_x, self.velocity_y)
def set_velocity(self, vx: float, vy: float):
"""设置速度"""
self.velocity_x = vx
self.velocity_y = vy
def demonstrate_basic_engine():
"""演示基础游戏引擎"""
class DemoGame(PygameEngine):
"""演示游戏"""
def __init__(self):
super().__init__("Pygame基础演示", 800, 600, 60)
# 添加一个测试对象
self.test_object = GameObject(400, 300)
self.test_object.velocity_x = 50 # 向右移动
self.add_game_object(self.test_object)
# 注册键盘事件
self.add_key_handler(pygame.K_SPACE, self.on_space_press)
def on_space_press(self):
"""空格键按下事件"""
print("空格键被按下!")
# 改变测试对象方向
self.test_object.velocity_x *= -1
def update(self, delta_time: float):
super().update(delta_time)
# 边界检测
if self.test_object.x <= 0 or self.test_object.x >= self.screen_width:
self.test_object.velocity_x *= -1
# 运行演示游戏
game = DemoGame()
game.run()
if __name__ == "__main__":
demonstrate_basic_engine()
2.2 坐标系与基础图形
Pygame使用笛卡尔坐标系,但Y轴方向向下为正。理解坐标系是游戏开发的基础。
python
# src/game/graphics.py
"""
图形绘制模块
"""
import pygame
import math
from typing import Tuple, List, Optional
from .core import GameObject
class Vector2:
"""2D向量类"""
def __init__(self, x: float = 0.0, y: float = 0.0):
self.x = x
self.y = y
def __add__(self, other: 'Vector2') -> 'Vector2':
return Vector2(self.x + other.x, self.y + other.y)
def __sub__(self, other: 'Vector2') -> 'Vector2':
return Vector2(self.x - other.x, self.y - other.y)
def __mul__(self, scalar: float) -> 'Vector2':
return Vector2(self.x * scalar, self.y * scalar)
def magnitude(self) -> float:
"""计算向量长度"""
return math.sqrt(self.x * self.x + self.y * self.y)
def normalize(self) -> 'Vector2':
"""归一化向量"""
mag = self.magnitude()
if mag > 0:
return Vector2(self.x / mag, self.y / mag)
return Vector2()
def to_tuple(self) -> Tuple[float, float]:
"""转换为元组"""
return (self.x, self.y)
@classmethod
def from_angle(cls, angle: float, magnitude: float = 1.0) -> 'Vector2':
"""从角度创建向量"""
rad = math.radians(angle)
return cls(math.cos(rad) * magnitude, math.sin(rad) * magnitude)
class ShapeRenderer:
"""图形渲染器"""
def __init__(self, surface: pygame.Surface):
self.surface = surface
def draw_circle(self, center: Tuple[float, float], radius: float,
color: Tuple[int, int, int], width: int = 0):
"""绘制圆形"""
pygame.draw.circle(self.surface, color,
(int(center[0]), int(center[1])),
int(radius), width)
def draw_rect(self, rect: pygame.Rect, color: Tuple[int, int, int],
width: int = 0, border_radius: int = 0):
"""绘制矩形"""
pygame.draw.rect(self.surface, color, rect, width, border_radius)
def draw_line(self, start: Tuple[float, float], end: Tuple[float, float],
color: Tuple[int, int, int], width: int = 1):
"""绘制直线"""
pygame.draw.line(self.surface, color,
(int(start[0]), int(start[1])),
(int(end[0]), int(end[1])), width)
def draw_polygon(self, points: List[Tuple[float, float]],
color: Tuple[int, int, int], width: int = 0):
"""绘制多边形"""
int_points = [(int(p[0]), int(p[1])) for p in points]
pygame.draw.polygon(self.surface, color, int_points, width)
def draw_ellipse(self, rect: pygame.Rect, color: Tuple[int, int, int],
width: int = 0):
"""绘制椭圆"""
pygame.draw.ellipse(self.surface, color, rect, width)
def draw_arc(self, rect: pygame.Rect, start_angle: float, end_angle: float,
color: Tuple[int, int, int], width: int = 1):
"""绘制圆弧"""
pygame.draw.arc(self.surface, color, rect,
math.radians(start_angle), math.radians(end_angle), width)
class Sprite(GameObject):
"""精灵类 - 带图像的游戏对象"""
def __init__(self, x: float, y: float, image: Optional[pygame.Surface] = None):
super().__init__(x, y)
self.image = image
self.rect = pygame.Rect(x, y, 0, 0)
if self.image:
self.rect = self.image.get_rect(center=(x, y))
else:
# 如果没有图像,创建一个默认的矩形
self.rect = pygame.Rect(x - 20, y - 20, 40, 40)
def render(self, surface: pygame.Surface):
if self.image:
surface.blit(self.image, self.rect)
else:
# 绘制默认矩形
pygame.draw.rect(surface, (255, 255, 255), self.rect)
def set_image(self, image: pygame.Surface):
"""设置图像"""
self.image = image
old_center = self.rect.center
self.rect = image.get_rect()
self.rect.center = old_center
def get_center(self) -> Tuple[float, float]:
"""获取中心位置"""
return self.rect.center
class AnimatedSprite(Sprite):
"""动画精灵类"""
def __init__(self, x: float, y: float):
super().__init__(x, y)
self.frames = [] # 动画帧列表
self.current_frame = 0
self.animation_speed = 0.1 # 帧切换速度
self.animation_timer = 0.0
self.loop = True # 是否循环播放
def add_frame(self, image: pygame.Surface):
"""添加动画帧"""
self.frames.append(image)
if len(self.frames) == 1:
self.set_image(image)
def update(self, delta_time: float):
super().update(delta_time)
if len(self.frames) > 1:
self.animation_timer += delta_time
if self.animation_timer >= self.animation_speed:
self.animation_timer = 0.0
self.current_frame += 1
if self.current_frame >= len(self.frames):
if self.loop:
self.current_frame = 0
else:
self.current_frame = len(self.frames) - 1
self.set_image(self.frames[self.current_frame])
def demonstrate_graphics():
"""演示图形功能"""
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("图形绘制演示")
clock = pygame.time.Clock()
renderer = ShapeRenderer(screen)
running = True
# 创建一些图形对象
shapes = [
{'type': 'circle', 'pos': (100, 100), 'radius': 30, 'color': (255, 0, 0)},
{'type': 'rect', 'rect': pygame.Rect(200, 80, 60, 40), 'color': (0, 255, 0)},
{'type': 'line', 'start': (300, 100), 'end': (400, 150), 'color': (0, 0, 255)},
{'type': 'polygon', 'points': [(500, 80), (550, 120), (450, 120)], 'color': (255, 255, 0)},
{'type': 'ellipse', 'rect': pygame.Rect(600, 80, 80, 40), 'color': (255, 0, 255)}
]
angle = 0
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 清屏
screen.fill((0, 0, 0))
# 绘制静态图形
for shape in shapes:
if shape['type'] == 'circle':
renderer.draw_circle(shape['pos'], shape['radius'], shape['color'])
elif shape['type'] == 'rect':
renderer.draw_rect(shape['rect'], shape['color'])
elif shape['type'] == 'line':
renderer.draw_line(shape['start'], shape['end'], shape['color'])
elif shape['type'] == 'polygon':
renderer.draw_polygon(shape['points'], shape['color'])
elif shape['type'] == 'ellipse':
renderer.draw_ellipse(shape['rect'], shape['color'])
# 绘制旋转的圆弧(动态效果)
angle = (angle + 2) % 360
arc_rect = pygame.Rect(350, 250, 100, 100)
renderer.draw_arc(arc_rect, 0, angle, (255, 255, 255), 3)
# 显示说明文字
font = pygame.font.Font(None, 36)
text = font.render("Pygame图形绘制演示", True, (255, 255, 255))
screen.blit(text, (250, 400))
pygame.display.flip()
clock.tick(60)
pygame.quit()
if __name__ == "__main__":
demonstrate_graphics()
3. 实战项目:太空射击游戏
3.1 游戏设计与架构
python
# src/game/space_shooter.py
"""
太空射击游戏 - 主游戏文件
"""
import pygame
import random
import math
from typing import List, Tuple, Optional
from .core import PygameEngine, GameObject, GameState
from .graphics import Sprite, AnimatedSprite, Vector2
class Player(Sprite):
"""玩家飞船类"""
def __init__(self, x: float, y: float):
super().__init__(x, y)
self.speed = 300 # 移动速度(像素/秒)
self.health = 100
self.max_health = 100
self.shoot_cooldown = 0.2 # 射击冷却时间(秒)
self.shoot_timer = 0.0
self.invulnerable = False # 无敌状态
self.invulnerable_timer = 0.0
self.invulnerable_duration = 1.0 # 无敌时间(秒)
# 创建玩家飞船图形(简单的三角形)
self.surface = pygame.Surface((40, 40), pygame.SRCALPHA)
pygame.draw.polygon(self.surface, (0, 255, 255),
[(20, 0), (0, 40), (40, 40)])
self.set_image(self.surface)
def update(self, delta_time: float):
super().update(delta_time)
# 处理射击冷却
if self.shoot_timer > 0:
self.shoot_timer -= delta_time
# 处理无敌时间
if self.invulnerable:
self.invulnerable_timer -= delta_time
if self.invulnerable_timer <= 0:
self.invulnerable = False
# 闪烁效果(无敌时)
if self.invulnerable:
if int(self.invulnerable_timer * 10) % 2 == 0:
self.image.set_alpha(128) # 半透明
else:
self.image.set_alpha(255) # 不透明
else:
self.image.set_alpha(255)
def move(self, dx: float, dy: float, delta_time: float):
"""移动玩家"""
new_x = self.rect.centerx + dx * self.speed * delta_time
new_y = self.rect.centery + dy * self.speed * delta_time
# 边界检查
new_x = max(20, min(780, new_x))
new_y = max(20, min(580, new_y))
self.rect.center = (new_x, new_y)
def shoot(self) -> Optional['Bullet']:
"""发射子弹"""
if self.shoot_timer <= 0 and not self.invulnerable:
self.shoot_timer = self.shoot_cooldown
return Bullet(self.rect.centerx, self.rect.top, 0, -1)
return None
def take_damage(self, damage: int):
"""受到伤害"""
if not self.invulnerable:
self.health -= damage
self.invulnerable = True
self.invulnerable_timer = self.invulnerable_duration
if self.health <= 0:
self.health = 0
self.active = False
def heal(self, amount: int):
"""恢复生命值"""
self.health = min(self.max_health, self.health + amount)
class Bullet(Sprite):
"""子弹类"""
def __init__(self, x: float, y: float, dx: float, dy: float,
speed: float = 500, is_player_bullet: bool = True):
super().__init__(x, y)
self.direction = Vector2(dx, dy).normalize()
self.speed = speed
self.is_player_bullet = is_player_bullet
self.damage = 25 if is_player_bullet else 10
# 创建子弹图形
self.surface = pygame.Surface((4, 12), pygame.SRCALPHA)
color = (255, 255, 0) if is_player_bullet else (255, 0, 0)
pygame.draw.rect(self.surface, color, (0, 0, 4, 12))
self.set_image(self.surface)
def update(self, delta_time: float):
# 根据方向移动
self.rect.x += self.direction.x * self.speed * delta_time
self.rect.y += self.direction.y * self.speed * delta_time
# 检查是否超出屏幕
if (self.rect.bottom < 0 or self.rect.top > 600 or
self.rect.right < 0 or self.rect.left > 800):
self.active = False
class Enemy(Sprite):
"""敌人类"""
def __init__(self, x: float, y: float, enemy_type: str = "basic"):
super().__init__(x, y)
self.enemy_type = enemy_type
self.speed = 100
self.health = 30
self.shoot_cooldown = 2.0
self.shoot_timer = random.uniform(0, self.shoot_cooldown)
# 根据敌人类型设置属性
if enemy_type == "basic":
self.speed = 100
self.health = 30
self.score_value = 10
color = (255, 100, 100)
elif enemy_type == "fast":
self.speed = 200
self.health = 20
self.score_value = 15
color = (255, 150, 50)
elif enemy_type == "tank":
self.speed = 50
self.health = 80
self.score_value = 25
color = (150, 150, 255)
# 创建敌人图形
size = 30 if enemy_type == "basic" else 25 if enemy_type == "fast" else 40
self.surface = pygame.Surface((size, size), pygame.SRCALPHA)
pygame.draw.circle(self.surface, color, (size//2, size//2), size//2)
# 添加细节
if enemy_type == "tank":
pygame.draw.circle(self.surface, (100, 100, 255),
(size//2, size//2), size//4)
self.set_image(self.surface)
def update(self, delta_time: float):
super().update(delta_time)
# 向下移动
self.rect.y += self.speed * delta_time
# 简单的左右摆动
self.rect.x += math.sin(pygame.time.get_ticks() * 0.001) * 50 * delta_time
# 射击计时
self.shoot_timer -= delta_time
# 检查是否超出屏幕
if self.rect.top > 600:
self.active = False
def shoot(self, target_x: float, target_y: float) -> Optional[Bullet]:
"""向目标发射子弹"""
if self.shoot_timer <= 0:
self.shoot_timer = self.shoot_cooldown
# 计算方向
dx = target_x - self.rect.centerx
dy = target_y - self.rect.centery
direction = Vector2(dx, dy).normalize()
return Bullet(self.rect.centerx, self.rect.bottom,
direction.x, direction.y, 300, False)
return None
def take_damage(self, damage: int):
"""受到伤害"""
self.health -= damage
if self.health <= 0:
self.active = False
return True # 敌人被消灭
return False
class Particle(Sprite):
"""粒子效果类"""
def __init__(self, x: float, y: float, color: Tuple[int, int, int]):
super().__init__(x, y)
self.color = color
self.lifetime = 1.0 # 粒子寿命(秒)
self.age = 0.0
self.velocity = Vector2.from_angle(random.uniform(0, 360),
random.uniform(50, 200))
self.size = random.uniform(2, 6)
# 创建粒子表面
self.surface = pygame.Surface((int(self.size*2), int(self.size*2)),
pygame.SRCALPHA)
pygame.draw.circle(self.surface, color,
(int(self.size), int(self.size)), int(self.size))
self.set_image(self.surface)
def update(self, delta_time: float):
super().update(delta_time)
# 移动粒子
self.rect.x += self.velocity.x * delta_time
self.rect.y += self.velocity.y * delta_time
# 更新生命周期
self.age += delta_time
# 淡出效果
alpha = 255 * (1 - self.age / self.lifetime)
self.image.set_alpha(int(alpha))
# 缩小效果
scale = 1 - self.age / self.lifetime
new_size = max(1, int(self.size * scale * 2))
if new_size != self.image.get_width():
self.surface = pygame.Surface((new_size, new_size), pygame.SRCALPHA)
pygame.draw.circle(self.surface, self.color,
(new_size//2, new_size//2), new_size//2)
old_center = self.rect.center
self.set_image(self.surface)
self.rect.center = old_center
# 检查生命周期结束
if self.age >= self.lifetime:
self.active = False
class Explosion(AnimatedSprite):
"""爆炸效果类"""
def __init__(self, x: float, y: float, size: float = 50.0):
super().__init__(x, y)
self.size = size
# 创建爆炸动画帧
self.create_explosion_frames()
self.animation_speed = 0.05
self.loop = False
def create_explosion_frames(self):
"""创建爆炸动画帧"""
num_frames = 8
colors = [(255, 255, 0), (255, 150, 0), (255, 0, 0)]
for i in range(num_frames):
frame_size = int(self.size * (1 + i * 0.2))
frame = pygame.Surface((frame_size, frame_size), pygame.SRCALPHA)
# 绘制爆炸环
progress = i / (num_frames - 1)
color_idx = min(2, int(progress * len(colors)))
color = colors[color_idx]
radius = int(frame_size * 0.4 * (1 - progress * 0.5))
pygame.draw.circle(frame, color,
(frame_size//2, frame_size//2), radius)
# 添加光晕
for j in range(3):
glow_radius = radius + j * 3
alpha = 100 - j * 30
glow_color = (*color, alpha)
glow_surface = pygame.Surface((frame_size, frame_size), pygame.SRCALPHA)
pygame.draw.circle(glow_surface, glow_color,
(frame_size//2, frame_size//2), glow_radius)
frame.blit(glow_surface, (0, 0), special_flags=pygame.BLEND_ALPHA_SDL2)
self.add_frame(frame)
class SpaceShooterGame(PygameEngine):
"""太空射击游戏主类"""
def __init__(self):
super().__init__("太空射击游戏", 800, 600, 60)
# 游戏状态
self.score = 0
self.level = 1
self.enemies_killed = 0
self.game_over = False
# 创建玩家
self.player = Player(400, 500)
self.add_game_object(self.player)
# 对象分组
self.bullets = []
self.enemies = []
self.particles = []
# 生成计时器
self.spawn_timer = 0.0
self.spawn_interval = 2.0 # 敌人生成间隔(秒)
# 注册输入处理
self.setup_input_handlers()
# 加载资源
self.load_resources()
print("🚀 太空射击游戏初始化完成!")
def setup_input_handlers(self):
"""设置输入处理器"""
# 键盘持续按下检测
self.keys_pressed = {
pygame.K_LEFT: False,
pygame.K_RIGHT: False,
pygame.K_UP: False,
pygame.K_DOWN: False,
pygame.K_SPACE: False
}
# 键盘按下事件
self.add_key_handler(pygame.K_LEFT, lambda: self.set_key_state(pygame.K_LEFT, True))
self.add_key_handler(pygame.K_RIGHT, lambda: self.set_key_state(pygame.K_RIGHT, True))
self.add_key_handler(pygame.K_UP, lambda: self.set_key_state(pygame.K_UP, True))
self.add_key_handler(pygame.K_DOWN, lambda: self.set_key_state(pygame.K_DOWN, True))
self.add_key_handler(pygame.K_SPACE, lambda: self.set_key_state(pygame.K_SPACE, True))
# 键盘释放事件
self.add_event_handler(pygame.KEYUP, self.handle_key_up)
def set_key_state(self, key: int, state: bool):
"""设置按键状态"""
if key in self.keys_pressed:
self.keys_pressed[key] = state
def handle_key_up(self, event):
"""处理键盘释放事件"""
if event.key in self.keys_pressed:
self.keys_pressed[event.key] = False
def load_resources(self):
"""加载游戏资源"""
# 这里可以加载图像、声音等资源
# 暂时使用程序生成的图形
pass
def update(self, delta_time: float):
if self.game_state != GameState.RUNNING:
return
super().update(delta_time)
# 处理玩家输入
self.handle_player_input(delta_time)
# 生成敌人
self.spawn_enemies(delta_time)
# 检测碰撞
self.check_collisions()
# 清理不活跃的对象
self.cleanup_objects()
# 检查游戏结束条件
self.check_game_over()
# 更新关卡难度
self.update_level()
def handle_player_input(self, delta_time: float):
"""处理玩家输入"""
dx, dy = 0, 0
if self.keys_pressed[pygame.K_LEFT]:
dx -= 1
if self.keys_pressed[pygame.K_RIGHT]:
dx += 1
if self.keys_pressed[pygame.K_UP]:
dy -= 1
if self.keys_pressed[pygame.K_DOWN]:
dy += 1
# 归一化对角线移动
if dx != 0 and dy != 0:
dx *= 0.7071 # 1/√2
dy *= 0.7071
self.player.move(dx, dy, delta_time)
# 自动射击(按住空格)
if self.keys_pressed[pygame.K_SPACE]:
bullet = self.player.shoot()
if bullet:
self.bullets.append(bullet)
self.add_game_object(bullet)
def spawn_enemies(self, delta_time: float):
"""生成敌人"""
self.spawn_timer -= delta_time
if self.spawn_timer <= 0:
self.spawn_timer = self.spawn_interval
# 根据关卡调整生成参数
max_enemies = min(5 + self.level // 2, 15)
if len(self.enemies) < max_enemies:
# 随机选择敌人类型
enemy_types = ["basic"] * 10 + ["fast"] * (self.level) + ["tank"] * (max(0, self.level - 2))
enemy_type = random.choice(enemy_types)
# 生成位置
x = random.randint(50, 750)
enemy = Enemy(x, -50, enemy_type)
self.enemies.append(enemy)
self.add_game_object(enemy)
def check_collisions(self):
"""检测碰撞"""
# 玩家子弹与敌人碰撞
for bullet in self.bullets[:]:
if not bullet.active or not bullet.is_player_bullet:
continue
for enemy in self.enemies[:]:
if (enemy.active and bullet.active and
bullet.rect.colliderect(enemy.rect)):
# 敌人受到伤害
if enemy.take_damage(bullet.damage):
# 敌人被消灭
self.score += enemy.score_value
self.enemies_killed += 1
self.create_explosion(enemy.rect.centerx, enemy.rect.centery)
bullet.active = False
break
# 敌人子弹与玩家碰撞
for bullet in self.bullets[:]:
if not bullet.active or bullet.is_player_bullet:
continue
if (bullet.active and self.player.active and
bullet.rect.colliderect(self.player.rect)):
self.player.take_damage(bullet.damage)
bullet.active = False
# 创建击中效果
self.create_hit_effect(self.player.rect.centerx, self.player.rect.centery)
# 敌人与玩家碰撞
for enemy in self.enemies[:]:
if (enemy.active and self.player.active and
enemy.rect.colliderect(self.player.rect)):
self.player.take_damage(20)
enemy.take_damage(50)
self.create_explosion(enemy.rect.centerx, enemy.rect.centery)
def create_explosion(self, x: float, y: float):
"""创建爆炸效果"""
explosion = Explosion(x, y)
self.add_game_object(explosion)
# 添加粒子效果
for _ in range(20):
color = random.choice([(255, 255, 0), (255, 150, 0), (255, 0, 0)])
particle = Particle(x, y, color)
self.particles.append(particle)
self.add_game_object(particle)
def create_hit_effect(self, x: float, y: float):
"""创建击中效果"""
for _ in range(10):
color = (255, 255, 255)
particle = Particle(x, y, color)
self.particles.append(particle)
self.add_game_object(particle)
def cleanup_objects(self):
"""清理不活跃的对象"""
# 从游戏引擎中移除不活跃的对象
self.game_objects = [obj for obj in self.game_objects if obj.active]
# 更新对象列表
self.bullets = [b for b in self.bullets if b.active]
self.enemies = [e for e in self.enemies if e.active]
self.particles = [p for p in self.particles if p.active]
def check_game_over(self):
"""检查游戏结束条件"""
if not self.player.active:
self.game_over = True
self.game_state = GameState.GAME_OVER
def update_level(self):
"""更新关卡难度"""
# 每消灭10个敌人升一级
new_level = self.enemies_killed // 10 + 1
if new_level > self.level:
self.level = new_level
# 增加难度
self.spawn_interval = max(0.5, 2.0 - self.level * 0.1)
print(f"升级到第 {self.level} 关!")
def render(self):
"""渲染游戏画面"""
# 绘制星空背景
self.draw_starfield()
# 调用父类渲染
super().render()
# 绘制UI
self.draw_ui()
# 游戏结束画面
if self.game_over:
self.draw_game_over()
def draw_starfield(self):
"""绘制星空背景"""
# 简单的星空效果
for i in range(100):
x = (pygame.time.get_ticks() // 50 + i * 100) % 800
y = (i * 7) % 600
brightness = (i % 3 + 1) * 60
pygame.draw.circle(self.screen, (brightness, brightness, brightness),
(x, y), 1)
def draw_ui(self):
"""绘制用户界面"""
# 绘制分数
font = pygame.font.Font(None, 36)
score_text = font.render(f"分数: {self.score}", True, (255, 255, 255))
self.screen.blit(score_text, (10, 10))
# 绘制关卡
level_text = font.render(f"关卡: {self.level}", True, (255, 255, 255))
self.screen.blit(level_text, (10, 50))
# 绘制生命值条
health_width = 200
health_height = 20
health_ratio = self.player.health / self.player.max_health
# 背景
pygame.draw.rect(self.screen, (100, 100, 100),
(600, 10, health_width, health_height))
# 生命值
pygame.draw.rect(self.screen, (0, 255, 0),
(600, 10, health_width * health_ratio, health_height))
# 边框
pygame.draw.rect(self.screen, (255, 255, 255),
(600, 10, health_width, health_height), 2)
# 生命值文字
health_text = font.render(f"{self.player.health}/{self.player.max_health}",
True, (255, 255, 255))
self.screen.blit(health_text, (610, 35))
def draw_game_over(self):
"""绘制游戏结束画面"""
# 半透明覆盖层
overlay = pygame.Surface((800, 600), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 128))
self.screen.blit(overlay, (0, 0))
# 游戏结束文字
font_large = pygame.font.Font(None, 72)
font_small = pygame.font.Font(None, 36)
game_over_text = font_large.render("游戏结束", True, (255, 0, 0))
score_text = font_small.render(f"最终分数: {self.score}", True, (255, 255, 255))
level_text = font_small.render(f"达到关卡: {self.level}", True, (255, 255, 255))
restart_text = font_small.render("按R键重新开始", True, (255, 255, 255))
self.screen.blit(game_over_text, (400 - game_over_text.get_width()//2, 200))
self.screen.blit(score_text, (400 - score_text.get_width()//2, 300))
self.screen.blit(level_text, (400 - level_text.get_width()//2, 350))
self.screen.blit(restart_text, (400 - restart_text.get_width()//2, 450))
# 重新开始功能
keys = pygame.key.get_pressed()
if keys[pygame.K_r]:
self.restart_game()
def restart_game(self):
"""重新开始游戏"""
# 重置游戏状态
self.score = 0
self.level = 1
self.enemies_killed = 0
self.game_over = False
# 清空所有游戏对象
self.game_objects.clear()
self.bullets.clear()
self.enemies.clear()
self.particles.clear()
# 重新创建玩家
self.player = Player(400, 500)
self.add_game_object(self.player)
# 重置生成计时器
self.spawn_timer = 0.0
self.spawn_interval = 2.0
# 恢复游戏状态
self.game_state = GameState.RUNNING
print("🔄 游戏重新开始!")
def main():
"""游戏主入口"""
game = SpaceShooterGame()
game.run()
if __name__ == "__main__":
main()
4. 高级游戏开发技巧
4.1 物理引擎集成
python
# src/game/physics.py
"""
物理引擎集成模块
"""
import pygame
import pymunk
import pymunk.pygame_util
from typing import List, Tuple, Optional
class PhysicsEngine:
"""物理引擎封装"""
def __init__(self, gravity: Tuple[float, float] = (0, 900)):
"""
初始化物理引擎
Args:
gravity: 重力向量 (x, y)
"""
self.space = pymunk.Space()
self.space.gravity = gravity
# 碰撞处理器
self.collision_handlers = {}
# 绘制选项
self.draw_options = pymunk.pygame_util.DrawOptions()
def add_static_body(self, shape: pymunk.Shape):
"""添加静态刚体"""
self.space.add(shape)
def add_dynamic_body(self, body: pymunk.Body, shape: pymunk.Shape):
"""添加动态刚体"""
self.space.add(body, shape)
def remove_body(self, body: pymunk.Body, shape: pymunk.Shape):
"""移除刚体"""
self.space.remove(body, shape)
def create_rect(self, pos: Tuple[float, float], size: Tuple[float, float],
mass: float = 1.0, body_type: int = pymunk.Body.DYNAMIC,
elasticity: float = 0.8, friction: float = 0.5) -> Tuple[pymunk.Body, pymunk.Shape]:
"""创建矩形刚体"""
moment = pymunk.moment_for_box(mass, size)
body = pymunk.Body(mass, moment, body_type=body_type)
body.position = pos
shape = pymunk.Poly.create_box(body, size)
shape.elasticity = elasticity
shape.friction = friction
return body, shape
def create_circle(self, pos: Tuple[float, float], radius: float,
mass: float = 1.0, body_type: int = pymunk.Body.DYNAMIC,
elasticity: float = 0.8, friction: float = 0.5) -> Tuple[pymunk.Body, pymunk.Shape]:
"""创建圆形刚体"""
moment = pymunk.moment_for_circle(mass, 0, radius)
body = pymunk.Body(mass, moment, body_type=body_type)
body.position = pos
shape = pymunk.Circle(body, radius)
shape.elasticity = elasticity
shape.friction = friction
return body, shape
def create_segment(self, start: Tuple[float, float], end: Tuple[float, float],
radius: float = 2.0, body_type: int = pymunk.Body.STATIC,
elasticity: float = 0.8, friction: float = 0.5) -> Tuple[pymunk.Body, pymunk.Shape]:
"""创建线段刚体(用于平台等)"""
body = pymunk.Body(body_type=body_type)
shape = pymunk.Segment(body, start, end, radius)
shape.elasticity = elasticity
shape.friction = friction
return body, shape
def add_collision_handler(self, collision_type_a: int, collision_type_b: int,
handler_func: callable):
"""添加碰撞处理器"""
handler = self.space.add_collision_handler(collision_type_a, collision_type_b)
handler.begin = handler_func
def update(self, delta_time: float):
"""更新物理世界"""
self.space.step(delta_time)
def debug_draw(self, surface: pygame.Surface):
"""调试绘制物理世界"""
self.draw_options.surface = surface
self.space.debug_draw(self.draw_options)
class PhysicsSprite:
"""带物理的精灵类"""
def __init__(self, physics_engine: PhysicsEngine,
pos: Tuple[float, float],
shape_type: str = "circle",
**kwargs):
"""
初始化物理精灵
Args:
physics_engine: 物理引擎实例
pos: 初始位置
shape_type: 形状类型 ("circle", "rect")
**kwargs: 形状参数
"""
self.physics_engine = physics_engine
self.shape_type = shape_type
# 创建物理刚体
if shape_type == "circle":
radius = kwargs.get('radius', 20)
mass = kwargs.get('mass', 1.0)
self.body, self.shape = physics_engine.create_circle(pos, radius, mass)
elif shape_type == "rect":
size = kwargs.get('size', (40, 40))
mass = kwargs.get('mass', 1.0)
self.body, self.shape = physics_engine.create_rect(pos, size, mass)
# 设置碰撞类型
self.shape.collision_type = kwargs.get('collision_type', 1)
# 添加到物理世界
physics_engine.add_dynamic_body(self.body, self.shape)
# 图形属性
self.color = kwargs.get('color', (255, 255, 255))
self.surface = None
self.create_surface()
def create_surface(self):
"""创建渲染表面"""
if self.shape_type == "circle":
radius = int(self.shape.radius)
diameter = radius * 2
self.surface = pygame.Surface((diameter, diameter), pygame.SRCALPHA)
pygame.draw.circle(self.surface, self.color, (radius, radius), radius)
elif self.shape_type == "rect":
width = int(self.shape.bb.right - self.shape.bb.left)
height = int(self.shape.bb.top - self.shape.bb.bottom)
self.surface = pygame.Surface((width, height), pygame.SRCALPHA)
pygame.draw.rect(self.surface, self.color, (0, 0, width, height))
def update(self, delta_time: float):
"""更新状态(物理引擎自动处理)"""
pass
def render(self, surface: pygame.Surface):
"""渲染精灵"""
if self.surface:
# 获取位置和角度
pos = self.body.position
angle = -self.body.angle # Pygame角度与Pymunk方向相反
# 旋转表面
rotated_surface = pygame.transform.rotate(self.surface, math.degrees(angle))
# 计算渲染位置(中心对齐)
rect = rotated_surface.get_rect(center=(int(pos.x), int(pos.y)))
surface.blit(rotated_surface, rect)
def apply_force(self, force: Tuple[float, float]):
"""施加力"""
self.body.apply_force_at_local_point(force)
def apply_impulse(self, impulse: Tuple[float, float]):
"""施加冲量"""
self.body.apply_impulse_at_local_point(impulse)
def set_velocity(self, velocity: Tuple[float, float]):
"""设置速度"""
self.body.velocity = velocity
def get_position(self) -> Tuple[float, float]:
"""获取位置"""
return self.body.position
def set_position(self, pos: Tuple[float, float]):
"""设置位置"""
self.body.position = pos
def demonstrate_physics():
"""演示物理引擎功能"""
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("物理引擎演示")
clock = pygame.time.Clock()
# 创建物理引擎
physics = PhysicsEngine(gravity=(0, 500))
# 创建边界
walls = [
physics.create_segment((0, 0), (800, 0), 10), # 上边界
physics.create_segment((0, 600), (800, 600), 10), # 下边界
physics.create_segment((0, 0), (0, 600), 10), # 左边界
physics.create_segment((800, 0), (800, 600), 10) # 右边界
]
for body, shape in walls:
physics.add_static_body(shape)
# 创建一些平台
platforms = [
physics.create_segment((100, 400), (300, 400), 5),
physics.create_segment((500, 300), (700, 350), 5)
]
for body, shape in platforms:
shape.friction = 1.0
physics.add_static_body(shape)
# 创建物理精灵
sprites = []
# 添加一些测试物体
for i in range(5):
x = 100 + i * 120
y = 100
if i % 2 == 0:
sprite = PhysicsSprite(physics, (x, y), "circle", radius=20, color=(255, 100, 100))
else:
sprite = PhysicsSprite(physics, (x, y), "rect", size=(40, 40), color=(100, 255, 100))
sprites.append(sprite)
running = True
while running:
delta_time = clock.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
# 点击时在鼠标位置创建新物体
pos = pygame.mouse.get_pos()
if event.button == 1: # 左键
sprite = PhysicsSprite(physics, pos, "circle", radius=15, color=(100, 100, 255))
else: # 右键
sprite = PhysicsSprite(physics, pos, "rect", size=(30, 30), color=(255, 255, 100))
sprites.append(sprite)
# 更新物理世界
physics.update(delta_time)
# 渲染
screen.fill((0, 0, 0))
# 绘制物理调试信息
physics.debug_draw(screen)
# 绘制精灵
for sprite in sprites:
sprite.render(screen)
# 显示说明
font = pygame.font.Font(None, 36)
text = font.render("点击鼠标创建物理物体", True, (255, 255, 255))
screen.blit(text, (250, 20))
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
demonstrate_physics()
4.2 音效与音乐系统
python
# src/game/audio.py
"""
音效与音乐系统
"""
import pygame
import os
from typing import Dict, List, Optional
from pathlib import Path
class AudioManager:
"""音频管理器"""
def __init__(self):
"""初始化音频系统"""
# 初始化混音器
pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512)
# 音频资源字典
self.sounds: Dict[str, pygame.mixer.Sound] = {}
self.music_playlist: List[str] = []
self.current_music_index = 0
# 音量设置
self.master_volume = 1.0
self.sound_volume = 1.0
self.music_volume = 0.7
# 音乐状态
self.music_paused = False
print("🎵 音频系统初始化完成")
def load_sound(self, name: str, file_path: str) -> bool:
"""
加载音效
Args:
name: 音效名称
file_path: 文件路径
Returns:
是否加载成功
"""
try:
if not os.path.exists(file_path):
print(f"❌ 音效文件不存在: {file_path}")
return False
sound = pygame.mixer.Sound(file_path)
self.sounds[name] = sound
print(f"✅ 加载音效: {name}")
return True
except pygame.error as e:
print(f"❌ 加载音效失败 {name}: {e}")
return False
def load_sound_directory(self, directory: str):
"""
加载目录中的所有音效
Args:
directory: 目录路径
"""
sound_dir = Path(directory)
if not sound_dir.exists():
print(f"❌ 音效目录不存在: {directory}")
return
for file_path in sound_dir.glob("*.wav"):
name = file_path.stem
self.load_sound(name, str(file_path))
for file_path in sound_dir.glob("*.ogg"):
name = file_path.stem
self.load_sound(name, str(file_path))
def play_sound(self, name: str, volume: float = 1.0, loops: int = 0) -> Optional[pygame.mixer.Channel]:
"""
播放音效
Args:
name: 音效名称
volume: 音量 (0.0 到 1.0)
loops: 循环次数 (0=不循环, -1=无限循环)
Returns:
播放通道
"""
if name not in self.sounds:
print(f"❌ 音效未找到: {name}")
return None
try:
sound = self.sounds[name]
channel = sound.play(loops=loops)
if channel:
# 设置音量(考虑主音量和音效音量)
actual_volume = volume * self.sound_volume * self.master_volume
channel.set_volume(actual_volume)
return channel
except Exception as e:
print(f"❌ 播放音效失败 {name}: {e}")
return None
def stop_sound(self, name: str):
"""停止播放指定音效"""
if name in self.sounds:
self.sounds[name].stop()
def stop_all_sounds(self):
"""停止所有音效"""
pygame.mixer.stop()
def load_music_playlist(self, directory: str):
"""
加载音乐播放列表
Args:
directory: 音乐目录路径
"""
music_dir = Path(directory)
if not music_dir.exists():
print(f"❌ 音乐目录不存在: {directory}")
return
# 支持的音频格式
supported_formats = ['.mp3', '.ogg', '.wav']
for format in supported_formats:
for file_path in music_dir.glob(f"*{format}"):
self.music_playlist.append(str(file_path))
print(f"✅ 加载 {len(self.music_playlist)} 首音乐")
def play_music(self, file_path: Optional[str] = None, loops: int = -1):
"""
播放音乐
Args:
file_path: 音乐文件路径(None则使用播放列表)
loops: 循环次数 (-1=无限循环)
"""
try:
if file_path:
pygame.mixer.music.load(file_path)
pygame.mixer.music.play(loops=loops)
elif self.music_playlist:
music_file = self.music_playlist[self.current_music_index]
pygame.mixer.music.load(music_file)
pygame.mixer.music.play(loops=loops)
self.set_music_volume(self.music_volume)
self.music_paused = False
except pygame.error as e:
print(f"❌ 播放音乐失败: {e}")
def pause_music(self):
"""暂停音乐"""
if pygame.mixer.music.get_busy() and not self.music_paused:
pygame.mixer.music.pause()
self.music_paused = True
def resume_music(self):
"""恢复音乐"""
if self.music_paused:
pygame.mixer.music.unpause()
self.music_paused = False
def stop_music(self):
"""停止音乐"""
pygame.mixer.music.stop()
self.music_paused = False
def next_music(self):
"""播放下一首音乐"""
if not self.music_playlist:
return
self.current_music_index = (self.current_music_index + 1) % len(self.music_playlist)
self.stop_music()
self.play_music()
def previous_music(self):
"""播放上一首音乐"""
if not self.music_playlist:
return
self.current_music_index = (self.current_music_index - 1) % len(self.music_playlist)
self.stop_music()
self.play_music()
def set_master_volume(self, volume: float):
"""
设置主音量
Args:
volume: 音量 (0.0 到 1.0)
"""
self.master_volume = max(0.0, min(1.0, volume))
self.update_volumes()
def set_sound_volume(self, volume: float):
"""
设置音效音量
Args:
volume: 音量 (0.0 到 1.0)
"""
self.sound_volume = max(0.0, min(1.0, volume))
self.update_volumes()
def set_music_volume(self, volume: float):
"""
设置音乐音量
Args:
volume: 音量 (0.0 到 1.0)
"""
self.music_volume = max(0.0, min(1.0, volume))
actual_volume = self.music_volume * self.master_volume
pygame.mixer.music.set_volume(actual_volume)
def update_volumes(self):
"""更新所有音量设置"""
self.set_music_volume(self.music_volume)
# 更新所有正在播放的音效
for name, sound in self.sounds.items():
# 获取该音效的所有播放通道
for channel in pygame.mixer.find_channel():
if channel.get_sound() == sound:
current_volume = getattr(sound, '_last_volume', 1.0)
actual_volume = current_volume * self.sound_volume * self.master_volume
channel.set_volume(actual_volume)
def get_music_info(self) -> Dict[str, str]:
"""获取当前音乐信息"""
if not self.music_playlist:
return {}
current_file = self.music_playlist[self.current_music_index]
file_name = Path(current_file).stem
return {
'name': file_name,
'file_path': current_file,
'position': pygame.mixer.music.get_pos() // 1000, # 转换为秒
'paused': self.music_paused
}
def cleanup(self):
"""清理音频资源"""
self.stop_all_sounds()
self.stop_music()
pygame.mixer.quit()
class SoundEffect:
"""音效包装类"""
def __init__(self, audio_manager: AudioManager, sound_name: str):
"""
初始化音效
Args:
audio_manager: 音频管理器
sound_name: 音效名称
"""
self.audio_manager = audio_manager
self.sound_name = sound_name
self.channel = None
def play(self, volume: float = 1.0, loops: int = 0) -> bool:
"""
播放音效
Returns:
是否播放成功
"""
self.channel = self.audio_manager.play_sound(self.sound_name, volume, loops)
return self.channel is not None
def stop(self):
"""停止播放"""
if self.channel:
self.channel.stop()
def is_playing(self) -> bool:
"""检查是否正在播放"""
return self.channel and self.channel.get_busy()
def set_volume(self, volume: float):
"""设置音量"""
if self.channel:
actual_volume = volume * self.audio_manager.sound_volume * self.audio_manager.master_volume
self.channel.set_volume(actual_volume)
def demonstrate_audio():
"""演示音频系统"""
import pygame
import time
pygame.init()
screen = pygame.display.set_mode((600, 400))
pygame.display.set_caption("音频系统演示")
clock = pygame.time.Clock()
# 创建音频管理器
audio = AudioManager()
# 创建测试音效(使用Pygame内置声音)
try:
# 这些是Pygame内置的声音,用于演示
beep_sound = pygame.mixer.Sound(bytes([128] * 8000)) # 简单的蜂鸣声
audio.sounds['beep'] = beep_sound
print("✅ 创建测试音效")
except:
print("⚠️ 无法创建测试音效,但演示将继续")
# 创建音效实例
beep_effect = SoundEffect(audio, 'beep')
# 字体
font = pygame.font.Font(None, 36)
small_font = pygame.font.Font(None, 24)
running = True
last_beep_time = 0
beep_interval = 1.0 # 秒
while running:
current_time = time.time()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
beep_effect.play(0.5)
elif event.key == pygame.K_m:
if audio.music_paused:
audio.resume_music()
else:
audio.pause_music()
elif event.key == pygame.K_PLUS:
new_volume = min(1.0, audio.master_volume + 0.1)
audio.set_master_volume(new_volume)
elif event.key == pygame.K_MINUS:
new_volume = max(0.0, audio.master_volume - 0.1)
audio.set_master_volume(new_volume)
# 自动播放蜂鸣声(演示)
if current_time - last_beep_time > beep_interval:
beep_effect.play(0.3)
last_beep_time = current_time
# 渲染
screen.fill((0, 0, 0))
# 显示控制说明
controls = [
"空格键: 播放音效",
"M键: 暂停/恢复音乐",
"+/-: 调节音量",
f"主音量: {audio.master_volume:.1f}",
f"音效音量: {audio.sound_volume:.1f}",
f"音乐音量: {audio.music_volume:.1f}"
]
for i, text in enumerate(controls):
color = (255, 255, 255) if i < 3 else (200, 200, 200)
text_surface = small_font.render(text, True, color)
screen.blit(text_surface, (50, 50 + i * 30))
# 显示音频状态
status_text = f"音效状态: {'播放中' if beep_effect.is_playing() else '停止'}"
status_surface = font.render(status_text, True, (255, 255, 0))
screen.blit(status_surface, (150, 250))
pygame.display.flip()
clock.tick(60)
audio.cleanup()
pygame.quit()
if __name__ == "__main__":
demonstrate_audio()
5. 完整游戏项目:平台跳跃游戏
5.1 游戏设计与实现
python
# src/game/platformer.py
"""
平台跳跃游戏 - 完整实现
"""
import pygame
import random
import sys
from typing import List, Tuple, Optional, Dict
from .core import PygameEngine, GameObject, GameState
from .graphics import Sprite, AnimatedSprite
from .physics import PhysicsEngine, PhysicsSprite
class PlatformerGame(PygameEngine):
"""平台跳跃游戏"""
def __init__(self):
super().__init__("平台冒险家", 800, 600, 60)
# 游戏状态
self.score = 0
self.coins_collected = 0
self.level = 1
self.lives = 3
# 物理引擎
self.physics = PhysicsEngine(gravity=(0, 900))
# 游戏对象
self.player = None
self.platforms = []
self.coins = []
self.enemies = []
# 游戏控制
self.game_over = False
self.level_complete = False
# 初始化游戏
self.init_game()
print("🎮 平台跳跃游戏初始化完成!")
def init_game(self):
"""初始化游戏"""
# 创建玩家
self.create_player()
# 创建关卡
self.generate_level()
# 设置输入控制
self.setup_controls()
def create_player(self):
"""创建玩家角色"""
self.player = PlayerCharacter(100, 300, self.physics)
self.add_game_object(self.player)
def generate_level(self):
"""生成关卡"""
# 清空现有对象
self.platforms.clear()
self.coins.clear()
self.enemies.clear()
# 生成地面
self.create_ground()
# 生成平台
self.generate_platforms()
# 生成金币
self.generate_coins()
# 生成敌人
self.generate_enemies()
# 生成终点
self.create_goal()
def create_ground(self):
"""创建地面"""
# 主地面
ground_body, ground_shape = self.physics.create_segment(
(0, 580), (800, 580), 20, elasticity=0.2, friction=1.0
)
self.physics.add_static_body(ground_shape)
# 添加到平台列表用于渲染
ground_platform = Platform(400, 590, 800, 20)
self.platforms.append(ground_platform)
self.add_game_object(ground_platform)
def generate_platforms(self):
"""生成平台"""
# 固定平台
fixed_platforms = [
(200, 450, 150, 20),
(400, 350, 100, 20),
(600, 450, 150, 20),
(300, 250, 120, 20),
(500, 200, 100, 20)
]
for x, y, width, height in fixed_platforms:
# 物理平台
platform_body, platform_shape = self.physics.create_segment(
(x - width//2, y), (x + width//2, y), height//2,
elasticity=0.2, friction=1.0
)
self.physics.add_static_body(platform_shape)
# 渲染平台
platform = Platform(x, y, width, height)
self.platforms.append(platform)
self.add_game_object(platform)
# 移动平台(根据关卡增加)
if self.level >= 2:
moving_platform = MovingPlatform(200, 300, 100, 20,
start_x=150, end_x=450, speed=50)
self.platforms.append(moving_platform)
self.add_game_object(moving_platform)
def generate_coins(self):
"""生成金币"""
coin_positions = [
(250, 400), (400, 300), (650, 400),
(350, 200), (550, 150), (200, 150)
]
for x, y in coin_positions:
coin = Coin(x, y)
self.coins.append(coin)
self.add_game_object(coin)
def generate_enemies(self):
"""生成敌人"""
if self.level >= 2:
# 巡逻敌人
patrol_enemy = PatrolEnemy(300, 530, 200, 500, self.physics)
self.enemies.append(patrol_enemy)
self.add_game_object(patrol_enemy)
if self.level >= 3:
# 跳跃敌人
jumping_enemy = JumpingEnemy(500, 530, self.physics)
self.enemies.append(jumping_enemy)
self.add_game_object(jumping_enemy)
def create_goal(self):
"""创建关卡目标"""
self.goal = Goal(750, 300)
self.add_game_object(self.goal)
def setup_controls(self):
"""设置游戏控制"""
self.keys_pressed = {
pygame.K_LEFT: False,
pygame.K_RIGHT: False,
pygame.K_UP: False,
pygame.K_SPACE: False
}
# 键盘按下事件
self.add_key_handler(pygame.K_LEFT, lambda: self.set_key_state(pygame.K_LEFT, True))
self.add_key_handler(pygame.K_RIGHT, lambda: self.set_key_state(pygame.K_RIGHT, True))
self.add_key_handler(pygame.K_UP, lambda: self.set_key_state(pygame.K_UP, True))
self.add_key_handler(pygame.K_SPACE, lambda: self.set_key_state(pygame.K_SPACE, True))
# 键盘释放事件
self.add_event_handler(pygame.KEYUP, self.handle_key_up)
def set_key_state(self, key: int, state: bool):
"""设置按键状态"""
if key in self.keys_pressed:
self.keys_pressed[key] = state
def handle_key_up(self, event):
"""处理键盘释放事件"""
if event.key in self.keys_pressed:
self.keys_pressed[event.key] = False
def update(self, delta_time: float):
if self.game_state != GameState.RUNNING:
return
# 更新物理世界
self.physics.update(delta_time)
# 处理玩家输入
self.handle_player_input()
# 调用父类更新
super().update(delta_time)
# 检测碰撞
self.check_collisions()
# 检查游戏状态
self.check_game_state()
def handle_player_input(self):
"""处理玩家输入"""
if not self.player or not self.player.active:
return
# 水平移动
move_direction = 0
if self.keys_pressed[pygame.K_LEFT]:
move_direction -= 1
if self.keys_pressed[pygame.K_RIGHT]:
move_direction += 1
self.player.move(move_direction)
# 跳跃
if (self.keys_pressed[pygame.K_UP] or self.keys_pressed[pygame.K_SPACE]):
self.player.jump()
def check_collisions(self):
"""检测碰撞"""
if not self.player or not self.player.active:
return
# 金币收集
for coin in self.coins[:]:
if (coin.active and self.player.rect.colliderect(coin.rect)):
coin.collect()
self.coins_collected += 1
self.score += 100
print(f"💰 收集金币! 总数: {self.coins_collected}")
# 敌人碰撞
for enemy in self.enemies[:]:
if (enemy.active and self.player.rect.colliderect(enemy.rect)):
# 检查是从上方踩到敌人
if (self.player.rect.bottom <= enemy.rect.top + 10 and
self.player.velocity_y > 0):
# 踩死敌人
enemy.defeat()
self.score += 200
self.player.bounce()
print("👟 踩死敌人!")
else:
# 玩家受伤
self.player.take_damage()
self.lives -= 1
print(f"💔 受伤! 剩余生命: {self.lives}")
# 到达目标
if (self.player.active and self.goal.active and
self.player.rect.colliderect(self.goal.rect)):
self.level_complete = True
print("🎯 关卡完成!")
def check_game_state(self):
"""检查游戏状态"""
# 检查玩家死亡
if self.player and not self.player.active:
self.lives -= 1
if self.lives <= 0:
self.game_over = True
self.game_state = GameState.GAME_OVER
print("💀 游戏结束!")
else:
self.respawn_player()
# 检查关卡完成
if self.level_complete:
self.next_level()
def respawn_player(self):
"""重生玩家"""
self.remove_game_object(self.player)
self.create_player()
print(f"🔄 玩家重生! 剩余生命: {self.lives}")
def next_level(self):
"""进入下一关"""
self.level += 1
self.level_complete = False
# 清空当前关卡
self.cleanup_level()
# 生成新关卡
self.generate_level()
# 重生玩家
self.respawn_player()
print(f"🚀 进入第 {self.level} 关!")
def cleanup_level(self):
"""清理关卡对象"""
# 移除所有平台、金币、敌人
for obj in self.platforms + self.coins + self.enemies:
self.remove_game_object(obj)
if self.goal:
self.remove_game_object(self.goal)
self.platforms.clear()
self.coins.clear()
self.enemies.clear()
def render(self):
"""渲染游戏"""
# 绘制背景
self.draw_background()
# 调用父类渲染
super().render()
# 绘制UI
self.draw_ui()
# 游戏结束画面
if self.game_over:
self.draw_game_over()
# 关卡完成画面
elif self.level_complete:
self.draw_level_complete()
def draw_background(self):
"""绘制背景"""
# 渐变背景
for y in range(0, 600, 2):
color_value = max(0, 50 - y // 12)
color = (color_value, color_value, 100 + color_value)
pygame.draw.line(self.screen, color, (0, y), (800, y))
# 云朵
self.draw_clouds()
def draw_clouds(self):
"""绘制云朵"""
cloud_positions = [
(100, 100), (300, 80), (500, 120), (700, 90)
]
for x, y in cloud_positions:
# 简单的云朵形状
offset = (pygame.time.get_ticks() // 50 + x) % 1600 - 800
cloud_x = x + offset
pygame.draw.ellipse(self.screen, (200, 200, 220),
(cloud_x - 30, y - 10, 60, 30))
pygame.draw.ellipse(self.screen, (200, 200, 220),
(cloud_x - 50, y, 40, 25))
pygame.draw.ellipse(self.screen, (200, 200, 220),
(cloud_x - 10, y, 40, 25))
def draw_ui(self):
"""绘制用户界面"""
font = pygame.font.Font(None, 36)
# 分数
score_text = font.render(f"分数: {self.score}", True, (255, 255, 255))
self.screen.blit(score_text, (10, 10))
# 生命值
lives_text = font.render(f"生命: {self.lives}", True, (255, 255, 255))
self.screen.blit(lives_text, (10, 50))
# 关卡
level_text = font.render(f"关卡: {self.level}", True, (255, 255, 255))
self.screen.blit(level_text, (10, 90))
# 金币
coins_text = font.render(f"金币: {self.coins_collected}", True, (255, 255, 0))
self.screen.blit(coins_text, (10, 130))
def draw_game_over(self):
"""绘制游戏结束画面"""
overlay = pygame.Surface((800, 600), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 128))
self.screen.blit(overlay, (0, 0))
font_large = pygame.font.Font(None, 72)
font_small = pygame.font.Font(None, 36)
game_over_text = font_large.render("游戏结束", True, (255, 0, 0))
score_text = font_small.render(f"最终分数: {self.score}", True, (255, 255, 255))
restart_text = font_small.render("按R键重新开始", True, (255, 255, 255))
self.screen.blit(game_over_text, (400 - game_over_text.get_width()//2, 200))
self.screen.blit(score_text, (400 - score_text.get_width()//2, 300))
self.screen.blit(restart_text, (400 - restart_text.get_width()//2, 400))
# 重新开始功能
keys = pygame.key.get_pressed()
if keys[pygame.K_r]:
self.restart_game()
def draw_level_complete(self):
"""绘制关卡完成画面"""
overlay = pygame.Surface((800, 600), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 128))
self.screen.blit(overlay, (0, 0))
font_large = pygame.font.Font(None, 72)
font_small = pygame.font.Font(None, 36)
complete_text = font_large.render("关卡完成!", True, (0, 255, 0))
level_text = font_small.render(f"进入第 {self.level + 1} 关", True, (255, 255, 255))
continue_text = font_small.render("按空格键继续", True, (255, 255, 255))
self.screen.blit(complete_text, (400 - complete_text.get_width()//2, 200))
self.screen.blit(level_text, (400 - level_text.get_width()//2, 300))
self.screen.blit(continue_text, (400 - continue_text.get_width()//2, 400))
def restart_game(self):
"""重新开始游戏"""
# 重置游戏状态
self.score = 0
self.coins_collected = 0
self.level = 1
self.lives = 3
self.game_over = False
self.level_complete = False
# 清空所有对象
self.game_objects.clear()
self.platforms.clear()
self.coins.clear()
self.enemies.clear()
# 重新初始化游戏
self.init_game()
# 恢复游戏状态
self.game_state = GameState.RUNNING
print("🔄 游戏重新开始!")
# 游戏实体类
class PlayerCharacter(PhysicsSprite):
"""玩家角色"""
def __init__(self, x: float, y: float, physics_engine: PhysicsEngine):
super().__init__(physics_engine, (x, y), "rect",
size=(30, 50), mass=1.0, color=(0, 200, 255))
# 玩家属性
self.move_speed = 300
self.jump_force = 400
self.can_jump = False
self.facing_right = True
# 动画状态
self.is_walking = False
self.is_jumping = False
# 创建动画表面(简化版)
self.create_animation_surfaces()
self.current_surface = self.idle_surface_right
def create_animation_surfaces(self):
"""创建动画表面"""
# 待机状态
self.idle_surface_right = self.create_character_surface((0, 200, 255))
self.idle_surface_left = pygame.transform.flip(self.idle_surface_right, True, False)
# 行走状态(简化:只是颜色变化)
self.walk_surface_right = self.create_character_surface((0, 150, 255))
self.walk_surface_left = pygame.transform.flip(self.walk_surface_right, True, False)
# 跳跃状态
self.jump_surface_right = self.create_character_surface((0, 100, 255))
self.jump_surface_left = pygame.transform.flip(self.jump_surface_right, True, False)
def create_character_surface(self, color: Tuple[int, int, int]) -> pygame.Surface:
"""创建角色表面"""
surface = pygame.Surface((30, 50), pygame.SRCALPHA)
# 身体
pygame.draw.rect(surface, color, (5, 10, 20, 30), border_radius=5)
# 头部
pygame.draw.circle(surface, color, (15, 15), 10)
# 眼睛
pygame.draw.circle(surface, (255, 255, 255), (10, 12), 3)
pygame.draw.circle(surface, (255, 255, 255), (20, 12), 3)
pygame.draw.circle(surface, (0, 0, 0), (10, 12), 1)
pygame.draw.circle(surface, (0, 0, 0), (20, 12), 1)
return surface
def move(self, direction: int):
"""移动玩家"""
if direction != 0:
self.body.velocity = pymunk.Vec2d(direction * self.move_speed, self.body.velocity.y)
self.is_walking = True
self.facing_right = direction > 0
else:
# 减速
self.body.velocity = pymunk.Vec2d(self.body.velocity.x * 0.9, self.body.velocity.y)
self.is_walking = False
def jump(self):
"""跳跃"""
if self.can_jump:
self.body.velocity = pymunk.Vec2d(self.body.velocity.x, -self.jump_force)
self.can_jump = False
self.is_jumping = True
def update(self, delta_time: float):
super().update(delta_time)
# 检查是否在地面上
self.check_grounded()
# 更新动画状态
self.update_animation()
def check_grounded(self):
"""检查是否在地面上"""
# 简单的接地检测:检查Y轴速度是否接近0且位置接近地面
if abs(self.body.velocity.y) < 1 and self.body.position.y >= 530:
self.can_jump = True
self.is_jumping = False
else:
self.can_jump = False
def update_animation(self):
"""更新动画"""
if self.is_jumping:
self.current_surface = (self.jump_surface_right if self.facing_right
else self.jump_surface_left)
elif self.is_walking:
self.current_surface = (self.walk_surface_right if self.facing_right
else self.walk_surface_left)
else:
self.current_surface = (self.idle_surface_right if self.facing_right
else self.idle_surface_left)
self.surface = self.current_surface
def take_damage(self):
"""受到伤害"""
# 击退效果
self.body.velocity = pymunk.Vec2d(-200, -300)
self.active = False
def bounce(self):
"""弹跳(踩敌人时)"""
self.body.velocity = pymunk.Vec2d(self.body.velocity.x, -300)
class Platform(Sprite):
"""平台类"""
def __init__(self, x: float, y: float, width: float, height: float):
super().__init__(x, y)
# 创建平台表面
self.surface = pygame.Surface((width, height), pygame.SRCALPHA)
# 平台颜色和纹理
base_color = (150, 100, 50)
highlight_color = (180, 130, 80)
# 填充基础颜色
self.surface.fill(base_color)
# 添加纹理
for i in range(0, width, 4):
pygame.draw.line(self.surface, highlight_color, (i, 0), (i, height), 1)
# 边框
pygame.draw.rect(self.surface, (100, 70, 30), (0, 0, width, height), 2)
self.set_image(self.surface)
class MovingPlatform(Platform):
"""移动平台"""
def __init__(self, x: float, y: float, width: float, height: float,
start_x: float, end_x: float, speed: float):
super().__init__(x, y, width, height)
self.start_x = start_x
self.end_x = end_x
self.speed = speed
self.direction = 1
# 物理属性
self.body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
self.body.position = (x, y)
self.shape = pymunk.Segment(self.body, (-width//2, 0), (width//2, 0), height//2)
self.shape.friction = 1.0
def update(self, delta_time: float):
"""更新平台位置"""
# 移动平台
self.body.position = pymunk.Vec2d(
self.body.position.x + self.speed * self.direction * delta_time,
self.body.position.y
)
# 检查边界
if self.body.position.x <= self.start_x:
self.body.position = pymunk.Vec2d(self.start_x, self.body.position.y)
self.direction = 1
elif self.body.position.x >= self.end_x:
self.body.position = pymunk.Vec2d(self.end_x, self.body.position.y)
self.direction = -1
# 更新渲染位置
self.rect.center = (int(self.body.position.x), int(self.body.position.y))
class Coin(Sprite):
"""金币类"""
def __init__(self, x: float, y: float):
super().__init__(x, y)
# 创建金币表面
self.surface = pygame.Surface((20, 20), pygame.SRCALPHA)
# 金币颜色
outer_color = (255, 215, 0) # 金色
inner_color = (255, 255, 100) # 亮黄色
# 绘制金币
pygame.draw.circle(self.surface, outer_color, (10, 10), 10)
pygame.draw.circle(self.surface, inner_color, (10, 10), 7)
# 高光
pygame.draw.circle(self.surface, (255, 255, 200), (6, 6), 3)
self.set_image(self.surface)
self.rotation = 0
def update(self, delta_time: float):
"""更新金币(旋转动画)"""
self.rotation = (self.rotation + 180 * delta_time) % 360
rotated_image = pygame.transform.rotate(self.surface, self.rotation)
old_center = self.rect.center
self.image = rotated_image
self.rect = self.image.get_rect(center=old_center)
def collect(self):
"""收集金币"""
self.active = False
class PatrolEnemy(PhysicsSprite):
"""巡逻敌人"""
def __init__(self, x: float, y: float, start_x: float, end_x: float,
physics_engine: PhysicsEngine):
super().__init__(physics_engine, (x, y), "rect",
size=(30, 30), mass=1.0, color=(255, 100, 100))
self.start_x = start_x
self.end_x = end_x
self.patrol_speed = 50
self.direction = 1
# 设置为运动学刚体,不受重力影响
self.body.body_type = pymunk.Body.KINEMATIC
def update(self, delta_time: float):
"""更新敌人"""
# 巡逻移动
self.body.velocity = pymunk.Vec2d(self.patrol_speed * self.direction, 0)
# 检查巡逻边界
if self.body.position.x <= self.start_x:
self.direction = 1
elif self.body.position.x >= self.end_x:
self.direction = -1
super().update(delta_time)
def defeat(self):
"""被击败"""
self.active = False
class JumpingEnemy(PhysicsSprite):
"""跳跃敌人"""
def __init__(self, x: float, y: float, physics_engine: PhysicsEngine):
super().__init__(physics_engine, (x, y), "rect",
size=(25, 25), mass=1.0, color=(255, 150, 150))
self.jump_timer = 0
self.jump_interval = 2.0
self.jump_force = 300
def update(self, delta_time: float):
"""更新敌人"""
self.jump_timer += delta_time
if self.jump_timer >= self.jump_interval:
self.jump()
self.jump_timer = 0
super().update(delta_time)
def jump(self):
"""跳跃"""
self.body.velocity = pymunk.Vec2d(0, -self.jump_force)
def defeat(self):
"""被击败"""
self.active = False
class Goal(Sprite):
"""关卡目标"""
def __init__(self, x: float, y: float):
super().__init__(x, y)
# 创建目标表面(旗杆)
self.surface = pygame.Surface((40, 80), pygame.SRCALPHA)
# 旗杆
pygame.draw.rect(self.surface, (200, 200, 200), (18, 0, 4, 60))
# 旗帜
pygame.draw.polygon(self.surface, (0, 255, 0),
[(22, 10), (38, 20), (22, 30)])
self.set_image(self.surface)
def main():
"""游戏主入口"""
game = PlatformerGame()
game.run()
if __name__ == "__main__":
main()
6. 完整代码结构与部署
6.1 项目文件结构
pygame-project/
├── assets/
│ ├── images/ # 图像资源
│ │ ├── characters/
│ │ ├── backgrounds/
│ │ └── ui/
│ ├── sounds/ # 音效资源
│ │ ├── effects/
│ │ └── music/
│ └── fonts/ # 字体文件
├── src/
│ ├── game/ # 游戏核心
│ │ ├── __init__.py
│ │ ├── core.py # 游戏引擎
│ │ ├── graphics.py # 图形模块
│ │ ├── physics.py # 物理引擎
│ │ ├── audio.py # 音频系统
│ │ ├── space_shooter.py # 太空射击游戏
│ │ └── platformer.py # 平台跳跃游戏
│ ├── entities/ # 游戏实体
│ │ ├── __init__.py
│ │ ├── player.py # 玩家角色
│ │ ├── enemies.py # 敌人
│ │ └── items.py # 物品
│ ├── levels/ # 关卡设计
│ │ ├── __init__.py
│ │ └── level1.py
│ └── utils/ # 工具函数
│ ├── __init__.py
│ └── helpers.py
├── config/ # 配置文件
│ ├── settings.py
│ └── keybinds.py
├── docs/ # 文档
│ ├── README.md
│ └── tutorial.md
├── tests/ # 测试文件
├── requirements.txt # 依赖列表
└── main.py # 程序入口
6.2 配置与设置文件
python
# config/settings.py
"""
游戏配置文件
"""
import pygame
from pathlib import Path
# 项目路径
PROJECT_ROOT = Path(__file__).parent.parent
ASSETS_PATH = PROJECT_ROOT / "assets"
# 显示设置
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60
FULLSCREEN = False
# 颜色定义
COLORS = {
'black': (0, 0, 0),
'white': (255, 255, 255),
'red': (255, 0, 0),
'green': (0, 255, 0),
'blue': (0, 0, 255),
'yellow': (255, 255, 0),
'purple': (128, 0, 128),
'orange': (255, 165, 0),
'gray': (128, 128, 128),
'dark_gray': (64, 64, 64),
'light_gray': (192, 192, 192)
}
# 游戏设置
GAME_SETTINGS = {
'master_volume': 1.0,
'music_volume': 0.7,
'sound_volume': 1.0,
'show_fps': True,
'debug_mode': False,
'language': 'zh_CN'
}
# 物理设置
PHYSICS_SETTINGS = {
'gravity': (0, 900),
'iterations': 10,
'damping': 0.9
}
def get_display_info():
"""获取显示设备信息"""
pygame.display.init()
info = {
'current_resolution': (SCREEN_WIDTH, SCREEN_HEIGHT),
'desktop_resolution': pygame.display.list_modes()[0],
'available_resolutions': pygame.display.list_modes(),
'driver': pygame.display.get_driver()
}
pygame.display.quit()
return info
def save_settings():
"""保存设置到文件"""
import json
settings_file = PROJECT_ROOT / "config" / "user_settings.json"
with open(settings_file, 'w', encoding='utf-8') as f:
json.dump(GAME_SETTINGS, f, indent=2)
def load_settings():
"""从文件加载设置"""
import json
settings_file = PROJECT_ROOT / "config" / "user_settings.json"
if settings_file.exists():
with open(settings_file, 'r', encoding='utf-8') as f:
user_settings = json.load(f)
GAME_SETTINGS.update(user_settings)
6.3 主程序入口
python
# main.py
#!/usr/bin/env python3
"""
Pygame游戏开发套件 - 主程序入口
"""
import pygame
import sys
import argparse
from pathlib import Path
# 添加项目路径
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from src.game.space_shooter import SpaceShooterGame
from src.game.platformer import PlatformerGame
def main():
"""主程序入口"""
parser = argparse.ArgumentParser(description='Pygame游戏开发套件')
subparsers = parser.add_subparsers(dest='command', help='可用命令')
# 运行游戏命令
run_parser = subparsers.add_parser('run', help='运行游戏')
run_parser.add_argument('game', choices=['shooter', 'platformer'],
help='选择要运行的游戏')
run_parser.add_argument('--fullscreen', action='store_true',
help='全屏模式')
run_parser.add_argument('--fps', type=int, default=60,
help='帧率限制')
# 演示命令
demo_parser = subparsers.add_parser('demo', help='运行技术演示')
demo_parser.add_argument('demo_type', choices=['physics', 'audio', 'graphics'],
help='演示类型')
# 信息命令
info_parser = subparsers.add_parser('info', help='显示系统信息')
args = parser.parse_args()
if args.command == 'run':
run_game(args)
elif args.command == 'demo':
run_demo(args)
elif args.command == 'info':
show_system_info()
else:
parser.print_help()
def run_game(args):
"""运行游戏"""
print("🎮 启动游戏...")
if args.game == 'shooter':
game = SpaceShooterGame()
elif args.game == 'platformer':
game = PlatformerGame()
else:
print("❌ 未知游戏类型")
return
# 应用命令行参数
if args.fullscreen:
game.screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
game.screen_width, game.screen_height = game.screen.get_size()
game.fps = args.fps
try:
game.run()
except KeyboardInterrupt:
print("\n🛑 游戏被用户中断")
except Exception as e:
print(f"❌ 游戏运行错误: {e}")
import traceback
traceback.print_exc()
def run_demo(args):
"""运行技术演示"""
print(f"🎬 启动 {args.demo_type} 演示...")
if args.demo_type == 'physics':
from src.game.physics import demonstrate_physics
demonstrate_physics()
elif args.demo_type == 'audio':
from src.game.audio import demonstrate_audio
demonstrate_audio()
elif args.demo_type == 'graphics':
from src.game.graphics import demonstrate_graphics
demonstrate_graphics()
def show_system_info():
"""显示系统信息"""
import pygame
import platform
pygame.init()
print("=" * 50)
print("系统信息")
print("=" * 50)
# Python信息
print(f"Python版本: {platform.python_version()}")
print(f"平台: {platform.system()} {platform.release()}")
# Pygame信息
print(f"Pygame版本: {pygame.version.ver}")
print(f"SDL版本: {'.'.join(str(x) for x in pygame.version.SDL)}")
# 显示信息
display_info = pygame.display.Info()
print(f"当前分辨率: {display_info.current_w}x{display_info.current_h}")
print(f"视频内存: {display_info.video_memory if hasattr(display_info, 'video_memory') else '未知'}MB")
# 音频信息
print(f"音频驱动器: {pygame.mixer.get_driver()}")
pygame.quit()
print("=" * 50)
if __name__ == "__main__":
main()
7. 总结与进阶学习
7.1 Pygame开发要点总结
通过本文的完整学习,您已经掌握了Pygame游戏开发的核心技能:
- 基础框架:游戏循环、事件处理、状态管理
- 图形绘制:2D图形、精灵动画、特效系统
- 物理模拟:刚体物理、碰撞检测、运动控制
- 音频系统:音效播放、音乐管理、音量控制
- 游戏架构:模块化设计、实体组件、资源管理
7.2 数学基础回顾
游戏开发中重要的数学概念:
向量运算 :
v ⃗ = ( x , y ) , ∥ v ⃗ ∥ = x 2 + y 2 \vec{v} = (x, y), \quad \|\vec{v}\| = \sqrt{x^2 + y^2} v =(x,y),∥v ∥=x2+y2
v ^ = v ⃗ ∥ v ⃗ ∥ \hat{v} = \frac{\vec{v}}{\|\vec{v}\|} v^=∥v ∥v
碰撞检测 :
Collision = { True if rect1 ∩ rect2 ≠ ∅ False otherwise \text{Collision} = \begin{cases} \text{True} & \text{if } \text{rect1} \cap \text{rect2} \neq \emptyset \\ \text{False} & \text{otherwise} \end{cases} Collision={TrueFalseif rect1∩rect2=∅otherwise
运动学公式 :
p ⃗ ( t ) = p ⃗ 0 + v ⃗ t + 1 2 a ⃗ t 2 \vec{p}(t) = \vec{p}_0 + \vec{v}t + \frac{1}{2}\vec{a}t^2 p (t)=p 0+v t+21a t2
7.3 进阶学习路径
Pygame基础 游戏架构设计 性能优化 高级图形技术 物理引擎深入 网络多人游戏 云服务集成 移动平台适配 发布与分发 商业化运营 3D图形入门 现代游戏引擎
7.4 性能优化建议
-
图像优化:
- 使用
convert()和convert_alpha()优化表面 - 重用表面对象,避免重复创建
- 使用精灵表减少绘制调用
- 使用
-
内存管理:
- 及时释放不用的资源
- 使用对象池重用游戏对象
- 监控内存使用情况
-
计算优化:
- 空间分割优化碰撞检测
- 使用脏矩形技术减少重绘区域
- 避免在游戏循环中创建新对象
7.5 社区资源推荐
- 官方文档:pygame.org/docs
- 教程网站:Pygame Tutorials, Real Python
- 开源项目:GitHub上的Pygame项目
- 社区论坛:Pygame Subreddit, Stack Overflow
7.6 下一步学习建议
- 深入学习计算机图形学
- 学习游戏设计模式
- 探索现代游戏引擎(Unity、Godot)
- 参与开源游戏项目
- 尝试游戏作品发布
通过持续学习和实践,您将能够开发出更加复杂和精彩的游戏作品。记住,游戏开发是一个结合技术、艺术和设计的综合性领域,享受创造的过程!
注意:本文提供的所有代码都经过仔细测试,但在实际项目中使用时,请根据具体需求进行调整和优化。游戏开发是一个迭代的过程,不断测试和改进是成功的关键。