Python游戏开发入门:Pygame实战

目录

  • 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),它持续运行直到游戏结束。每个循环周期包含三个主要步骤:

  1. 处理输入:检测玩家操作
  2. 更新游戏状态:根据输入和游戏逻辑更新对象
  3. 渲染输出:将游戏状态绘制到屏幕
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游戏开发的核心技能:

  1. 基础框架:游戏循环、事件处理、状态管理
  2. 图形绘制:2D图形、精灵动画、特效系统
  3. 物理模拟:刚体物理、碰撞检测、运动控制
  4. 音频系统:音效播放、音乐管理、音量控制
  5. 游戏架构:模块化设计、实体组件、资源管理

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 性能优化建议

  1. 图像优化

    • 使用convert()convert_alpha()优化表面
    • 重用表面对象,避免重复创建
    • 使用精灵表减少绘制调用
  2. 内存管理

    • 及时释放不用的资源
    • 使用对象池重用游戏对象
    • 监控内存使用情况
  3. 计算优化

    • 空间分割优化碰撞检测
    • 使用脏矩形技术减少重绘区域
    • 避免在游戏循环中创建新对象

7.5 社区资源推荐

  • 官方文档pygame.org/docs
  • 教程网站:Pygame Tutorials, Real Python
  • 开源项目:GitHub上的Pygame项目
  • 社区论坛:Pygame Subreddit, Stack Overflow

7.6 下一步学习建议

  1. 深入学习计算机图形学
  2. 学习游戏设计模式
  3. 探索现代游戏引擎(Unity、Godot)
  4. 参与开源游戏项目
  5. 尝试游戏作品发布

通过持续学习和实践,您将能够开发出更加复杂和精彩的游戏作品。记住,游戏开发是一个结合技术、艺术和设计的综合性领域,享受创造的过程!


注意:本文提供的所有代码都经过仔细测试,但在实际项目中使用时,请根据具体需求进行调整和优化。游戏开发是一个迭代的过程,不断测试和改进是成功的关键。

相关推荐
是苏浙3 小时前
零基础入门C语言之枚举和联合体
c语言·开发语言
报错小能手3 小时前
C++笔记(面向对象)静态联编和动态联编
开发语言·c++·算法
小肖爱笑不爱笑3 小时前
2025/11/5 IO流(字节流、字符流、字节缓冲流、字符缓冲流) 计算机存储规则(ASCII、GBK、Unicode)
java·开发语言·算法
手握风云-3 小时前
Java 数据结构第二十八期:反射、枚举以及 lambda 表达式
java·开发语言
ᐇ9593 小时前
Java Vector集合全面解析:线程安全的动态数组
java·开发语言
雍凉明月夜3 小时前
人工智能学习中深度学习之python基础之 类
python·学习
Geo_V4 小时前
OpenAI 大模型 API 使用示例
python·chatgpt·openai·大模型应用·llm 开发
AA陈超4 小时前
虚幻引擎5 GAS开发俯视角RPG游戏 P07-06 能力输入的回调
c++·游戏·ue5·游戏引擎·虚幻
Hello_WOAIAI4 小时前
2.4 python装饰器在 Web 框架和测试中的实战应用
开发语言·前端·python