Pyglet开发游戏流程详解

Pyglet 开发游戏详细流程

一、Pyglet vs Pygame 对比

特性 Pyglet Pygame
架构 基于OpenGL,现代 基于SDL,传统
性能 高性能,支持硬件加速 中等性能,软件渲染
3D支持 原生支持3D 需要第三方库
安装大小 较小,无依赖 较大,依赖SDL
学习曲线 较陡峭 较平缓
音频支持 优秀(OpenAL) 良好
多窗口 支持 有限支持
移动平台 支持 不支持

二、Pyglet 核心架构

复制代码
Application (应用层)
    ├── Window (窗口)
    ├── Event Loop (事件循环)
    ├── Batch (批处理)
    ├── Sprite (精灵)
    └── Resource Management (资源管理)

三、完整开发流程详解

1. 基础框架搭建

python 复制代码
import pyglet
from pyglet.window import key
import random

class GameWindow(pyglet.window.Window):
    def __init__(self):
        """初始化游戏窗口"""
        # 创建窗口(设置大小、标题、垂直同步)
        super().__init__(
            width=800,
            height=600,
            caption="Pyglet游戏",
            resizable=False,
            vsync=True  # 启用垂直同步,防止画面撕裂
        )
        
        # 设置窗口居中
        self.set_location(
            self.screen.width // 2 - self.width // 2,
            self.screen.height // 2 - self.height // 2
        )
        
        # 游戏状态
        self.game_state = "MENU"  # MENU, PLAYING, PAUSED, GAME_OVER
        self.score = 0
        self.lives = 3
        self.level = 1
        
        # 帧率和时间管理
        self.fps_display = pyglet.window.FPSDisplay(self)
        self.game_time = 0.0
        self.dt = 1/60.0  # 固定时间步长
        
        # 批次渲染(提高性能)
        self.main_batch = pyglet.graphics.Batch()
        self.background_batch = pyglet.graphics.Batch()
        
        # 初始化游戏对象
        self.init_game_objects()
        
        # 初始化UI
        self.init_ui()
        
        # 加载资源
        self.load_resources()
        
        # 设置键盘状态
        self.keys = key.KeyStateHandler()
        self.push_handlers(self.keys)
    
    def load_resources(self):
        """加载游戏资源"""
        # 设置资源路径
        pyglet.resource.path = ['assets/images', 'assets/sounds']
        pyglet.resource.reindex()
        
        # 加载图像(带透明度)
        try:
            self.player_image = pyglet.resource.image("player.png")
            self.player_image.anchor_x = self.player_image.width // 2
            self.player_image.anchor_y = self.player_image.height // 2
            
            self.enemy_image = pyglet.resource.image("enemy.png")
            self.bullet_image = pyglet.resource.image("bullet.png")
        except:
            # 如果图片不存在,创建占位符
            self.create_placeholder_images()
        
        # 加载音效
        try:
            self.shoot_sound = pyglet.resource.media("shoot.wav", streaming=False)
            self.explosion_sound = pyglet.resource.media("explosion.wav", streaming=False)
        except:
            self.shoot_sound = None
            self.explosion_sound = None
        
        # 加载背景音乐
        try:
            self.bg_music = pyglet.resource.media("bg_music.mp3", streaming=True)
            self.bg_player = pyglet.media.Player()
            self.bg_player.queue(self.bg_music)
            self.bg_player.loop = True
            self.bg_player.play()
        except:
            self.bg_player = None
    
    def create_placeholder_images(self):
        """创建占位图像"""
        # 创建玩家图像
        self.player_image = pyglet.image.SolidColorImagePattern(
            (0, 120, 255, 255)).create_image(40, 40)
        self.player_image.anchor_x = 20
        self.player_image.anchor_y = 20
        
        # 创建敌人图像
        self.enemy_image = pyglet.image.SolidColorImagePattern(
            (255, 50, 50, 255)).create_image(30, 30)
        self.enemy_image.anchor_x = 15
        self.enemy_image.anchor_y = 15
        
        # 创建子弹图像
        self.bullet_image = pyglet.image.SolidColorImagePattern(
            (255, 255, 0, 255)).create_image(10, 20)
        self.bullet_image.anchor_x = 5
        self.bullet_image.anchor_y = 10

2. 游戏对象系统

python 复制代码
class GameObject:
    """游戏对象基类"""
    def __init__(self, x, y, image=None, batch=None):
        self.x = x
        self.y = y
        self.vx = 0.0
        self.vy = 0.0
        self.rotation = 0.0
        self.scale = 1.0
        self.visible = True
        
        if image and batch:
            self.sprite = pyglet.sprite.Sprite(
                img=image,
                x=x,
                y=y,
                batch=batch
            )
            self.width = self.sprite.width
            self.height = self.sprite.height
        else:
            self.sprite = None
            self.width = 0
            self.height = 0
    
    def update(self, dt):
        """更新对象状态"""
        self.x += self.vx * dt
        self.y += self.vy * dt
        
        if self.sprite:
            self.sprite.x = self.x
            self.sprite.y = self.y
            self.sprite.rotation = self.rotation
            self.sprite.scale = self.scale
            self.sprite.visible = self.visible
    
    def check_bounds(self, screen_width, screen_height):
        """检查边界"""
        if self.x < 0:
            self.x = 0
            self.vx = abs(self.vx) * 0.5  # 反弹
        elif self.x > screen_width:
            self.x = screen_width
            self.vx = -abs(self.vx) * 0.5
        
        if self.y < 0:
            self.y = 0
            self.vy = abs(self.vy) * 0.5
        elif self.y > screen_height:
            self.y = screen_height
            self.vy = -abs(self.vy) * 0.5
    
    def collides_with(self, other):
        """简单的矩形碰撞检测"""
        if not self.visible or not other.visible:
            return False
            
        return (abs(self.x - other.x) * 2 < (self.width + other.width) and
                abs(self.y - other.y) * 2 < (self.height + other.height))


class Player(GameObject):
    """玩家类"""
    def __init__(self, x, y, image, batch):
        super().__init__(x, y, image, batch)
        self.speed = 300.0
        self.fire_rate = 0.2  # 射击间隔(秒)
        self.fire_timer = 0.0
        self.health = 100
        self.invincible_timer = 0.0
    
    def update(self, dt, keys, bullets, game_window):
        # 移动控制
        self.vx = 0
        self.vy = 0
        
        if keys[key.LEFT]:
            self.vx = -self.speed
        if keys[key.RIGHT]:
            self.vx = self.speed
        if keys[key.UP]:
            self.vy = self.speed
        if keys[key.DOWN]:
            self.vy = -self.speed
        
        # 对角线移动速度修正
        if self.vx != 0 and self.vy != 0:
            self.vx *= 0.7071
            self.vy *= 0.7071
        
        super().update(dt)
        
        # 边界检查
        self.check_bounds(game_window.width, game_window.height)
        
        # 射击控制
        self.fire_timer -= dt
        if keys[key.SPACE] and self.fire_timer <= 0:
            self.shoot(bullets, game_window)
            self.fire_timer = self.fire_rate
        
        # 无敌时间
        if self.invincible_timer > 0:
            self.invincible_timer -= dt
            # 闪烁效果
            self.visible = (int(self.invincible_timer * 10) % 2) == 0
        else:
            self.visible = True
    
    def shoot(self, bullets, game_window):
        """发射子弹"""
        bullet = Bullet(
            self.x, 
            self.y + self.height // 2,
            game_window.bullet_image,
            game_window.main_batch
        )
        bullet.vy = 500.0
        bullets.append(bullet)
        
        # 播放音效
        if game_window.shoot_sound:
            game_window.shoot_sound.play()
    
    def take_damage(self, amount):
        """受到伤害"""
        if self.invincible_timer <= 0:
            self.health -= amount
            self.invincible_timer = 1.0  # 1秒无敌时间
            return True
        return False


class Enemy(GameObject):
    """敌人类"""
    def __init__(self, x, y, image, batch, enemy_type=1):
        super().__init__(x, y, image, batch)
        self.type = enemy_type
        self.health = enemy_type * 10
        self.points = enemy_type * 100
        
        # 根据类型设置不同属性
        if enemy_type == 1:  # 普通敌人
            self.speed = 150.0
            self.color = (255, 100, 100)
        elif enemy_type == 2:  # 快速敌人
            self.speed = 250.0
            self.color = (255, 150, 50)
        else:  # 重型敌人
            self.speed = 80.0
            self.color = (255, 50, 50)
    
    def update(self, dt, player, game_window):
        """AI: 追踪玩家"""
        dx = player.x - self.x
        dy = player.y - self.y
        distance = (dx**2 + dy**2)**0.5
        
        if distance > 0:
            self.vx = (dx / distance) * self.speed
            self.vy = (dy / distance) * self.speed
        
        super().update(dt)
        
        # 旋转朝向玩家
        self.rotation = -math.degrees(math.atan2(dx, dy)) + 90
        
        # 边界检查
        self.check_bounds(game_window.width, game_window.height)


class Bullet(GameObject):
    """子弹类"""
    def __init__(self, x, y, image, batch):
        super().__init__(x, y, image, batch)
        self.lifetime = 2.0  # 子弹存在时间(秒)
    
    def update(self, dt):
        super().update(dt)
        self.lifetime -= dt
        return self.lifetime > 0  # 返回子弹是否还存在

3. 游戏主循环和事件处理

python 复制代码
class GameWindow(pyglet.window.Window):
    # ... 之前的代码 ...
    
    def init_game_objects(self):
        """初始化游戏对象"""
        # 玩家
        self.player = Player(
            self.width // 2,
            100,
            self.player_image,
            self.main_batch
        )
        
        # 游戏对象列表
        self.bullets = []
        self.enemies = []
        self.particles = []
        self.powerups = []
        
        # 敌人生成计时器
        self.enemy_spawn_timer = 0.0
        self.enemy_spawn_interval = 1.0  # 初始生成间隔
    
    def init_ui(self):
        """初始化UI元素"""
        # 分数显示
        self.score_label = pyglet.text.Label(
            '分数: 0',
            font_name='Arial',
            font_size=24,
            x=10,
            y=self.height - 30,
            color=(255, 255, 255, 255),
            batch=self.main_batch
        )
        
        # 生命显示
        self.lives_label = pyglet.text.Label(
            '生命: 3',
            font_name='Arial',
            font_size=24,
            x=self.width - 100,
            y=self.height - 30,
            color=(255, 255, 255, 255),
            batch=self.main_batch
        )
        
        # 游戏状态文字
        self.state_label = pyglet.text.Label(
            '',
            font_name='Arial',
            font_size=36,
            x=self.width // 2,
            y=self.height // 2,
            anchor_x='center',
            anchor_y='center',
            color=(255, 255, 255, 255),
            batch=self.main_batch
        )
    
    def on_draw(self):
        """绘制游戏画面"""
        self.clear()
        
        # 绘制背景(如果有)
        if hasattr(self, 'background'):
            self.background.blit(0, 0)
        
        # 根据游戏状态绘制不同内容
        if self.game_state == "PLAYING":
            # 绘制游戏对象
            self.main_batch.draw()
            
            # 绘制UI
            self.score_label.text = f'分数: {self.score}'
            self.lives_label.text = f'生命: {self.lives}'
            
            # 显示帧率
            self.fps_display.draw()
            
            # 绘制玩家生命条
            self.draw_health_bar()
            
        elif self.game_state == "MENU":
            self.draw_menu()
            
        elif self.game_state == "GAME_OVER":
            self.draw_game_over()
    
    def draw_health_bar(self):
        """绘制生命条"""
        bar_width = 200
        bar_height = 20
        x = self.width // 2 - bar_width // 2
        y = 10
        
        # 背景
        pyglet.graphics.draw(
            4, pyglet.gl.GL_QUADS,
            ('v2f', [x, y, x+bar_width, y, 
                     x+bar_width, y+bar_height, x, y+bar_height]),
            ('c3B', [50, 50, 50] * 4)
        )
        
        # 生命值
        health_width = bar_width * (self.player.health / 100)
        pyglet.graphics.draw(
            4, pyglet.gl.GL_QUADS,
            ('v2f', [x, y, x+health_width, y, 
                     x+health_width, y+bar_height, x, y+bar_height]),
            ('c3B', [0, 255, 0] * 4)
        )
    
    def draw_menu(self):
        """绘制主菜单"""
        # 标题
        title = pyglet.text.Label(
            '太空射击游戏',
            font_name='Arial',
            font_size=48,
            x=self.width // 2,
            y=self.height * 0.7,
            anchor_x='center',
            anchor_y='center',
            color=(255, 255, 0, 255)
        )
        title.draw()
        
        # 开始按钮
        start_text = pyglet.text.Label(
            '按 SPACE 开始游戏',
            font_name='Arial',
            font_size=32,
            x=self.width // 2,
            y=self.height * 0.5,
            anchor_x='center',
            anchor_y='center',
            color=(255, 255, 255, 255)
        )
        start_text.draw()
        
        # 说明
        instructions = pyglet.text.Label(
            '方向键移动,SPACE射击',
            font_name='Arial',
            font_size=20,
            x=self.width // 2,
            y=self.height * 0.3,
            anchor_x='center',
            anchor_y='center',
            color=(200, 200, 200, 255)
        )
        instructions.draw()
    
    def update(self, dt):
        """更新游戏逻辑"""
        self.game_time += dt
        
        if self.game_state == "PLAYING":
            self.update_gameplay(dt)
        elif self.game_state == "MENU":
            self.update_menu(dt)
    
    def update_gameplay(self, dt):
        """更新游戏玩法"""
        # 更新玩家
        self.player.update(dt, self.keys, self.bullets, self)
        
        # 更新子弹
        self.update_bullets(dt)
        
        # 更新敌人
        self.update_enemies(dt)
        
        # 生成敌人
        self.spawn_enemies(dt)
        
        # 检测碰撞
        self.check_collisions()
        
        # 检查游戏结束条件
        if self.player.health <= 0:
            self.lives -= 1
            if self.lives > 0:
                self.reset_player()
            else:
                self.game_state = "GAME_OVER"
        
        # 升级关卡
        if self.score > self.level * 1000:
            self.level_up()
    
    def update_bullets(self, dt):
        """更新子弹"""
        # 更新玩家子弹
        for bullet in self.bullets[:]:
            if not bullet.update(dt):
                self.bullets.remove(bullet)
                continue
            
            # 移除屏幕外的子弹
            if (bullet.y > self.height or bullet.y < 0 or 
                bullet.x > self.width or bullet.x < 0):
                self.bullets.remove(bullet)
    
    def spawn_enemies(self, dt):
        """生成敌人"""
        self.enemy_spawn_timer -= dt
        if self.enemy_spawn_timer <= 0:
            # 根据等级调整生成数量和类型
            enemy_type = random.randint(1, min(3, self.level))
            enemy_count = random.randint(1, min(3, self.level // 2 + 1))
            
            for _ in range(enemy_count):
                x = random.randint(50, self.width - 50)
                y = self.height + 50
                enemy = Enemy(x, y, self.enemy_image, self.main_batch, enemy_type)
                self.enemies.append(enemy)
            
            # 重置计时器
            self.enemy_spawn_timer = self.enemy_spawn_interval * random.uniform(0.8, 1.2)
    
    def check_collisions(self):
        """检测碰撞"""
        # 子弹与敌人碰撞
        for bullet in self.bullets[:]:
            for enemy in self.enemies[:]:
                if bullet.collides_with(enemy):
                    enemy.health -= 25
                    
                    if enemy.health <= 0:
                        self.score += enemy.points
                        self.enemies.remove(enemy)
                        
                        # 播放爆炸音效
                        if self.explosion_sound:
                            self.explosion_sound.play()
                        
                        # 生成爆炸粒子
                        self.create_explosion(enemy.x, enemy.y)
                    
                    if bullet in self.bullets:
                        self.bullets.remove(bullet)
                    break
        
        # 玩家与敌人碰撞
        for enemy in self.enemies[:]:
            if self.player.collides_with(enemy) and self.player.take_damage(20):
                self.enemies.remove(enemy)
                self.create_explosion(enemy.x, enemy.y)
    
    def create_explosion(self, x, y):
        """创建爆炸粒子效果"""
        for _ in range(20):
            particle = {
                'x': x,
                'y': y,
                'vx': random.uniform(-100, 100),
                'vy': random.uniform(-100, 100),
                'life': random.uniform(0.5, 1.5),
                'size': random.uniform(2, 6),
                'color': random.choice([
                    (255, 255, 0), (255, 165, 0), (255, 0, 0)
                ])
            }
            self.particles.append(particle)
    
    def on_key_press(self, symbol, modifiers):
        """键盘按下事件"""
        if self.game_state == "MENU":
            if symbol == key.SPACE:
                self.start_game()
        
        elif self.game_state == "PLAYING":
            if symbol == key.ESCAPE:
                self.game_state = "MENU"
            elif symbol == key.P:
                self.game_state = "PAUSED"
        
        elif self.game_state == "GAME_OVER":
            if symbol == key.R:
                self.restart_game()
        
        elif self.game_state == "PAUSED":
            if symbol == key.P:
                self.game_state = "PLAYING"
    
    def start_game(self):
        """开始游戏"""
        self.game_state = "PLAYING"
        self.score = 0
        self.lives = 3
        self.level = 1
        self.player.health = 100
        
        # 清空所有游戏对象
        self.bullets.clear()
        self.enemies.clear()
        self.particles.clear()
        
        # 重置玩家位置
        self.player.x = self.width // 2
        self.player.y = 100
        
        # 重置生成计时器
        self.enemy_spawn_timer = 1.0

4. 完整示例:太空射击游戏

python 复制代码
import pyglet
from pyglet.window import key, mouse
import math
import random

class SpaceShooter:
    """完整的太空射击游戏"""
    def __init__(self):
        # 创建窗口
        self.window = GameWindow()
        
        # 设置更新函数
        pyglet.clock.schedule_interval(self.window.update, 1/60.0)
    
    def run(self):
        """运行游戏"""
        pyglet.app.run()


class GameWindow(pyglet.window.Window):
    def __init__(self):
        super().__init__(800, 600, "太空射击", resizable=False)
        
        # 游戏初始化
        self.init_game()
    
    def init_game(self):
        """初始化游戏"""
        # 游戏状态
        self.state = "MENU"
        self.score = 0
        self.level = 1
        
        # 批次渲染
        self.batch = pyglet.graphics.Batch()
        self.background_batch = pyglet.graphics.Batch()
        
        # 创建背景
        self.create_background()
        
        # 创建玩家
        self.create_player()
        
        # 对象列表
        self.bullets = []
        self.enemies = []
        self.stars = []
        
        # 创建星星背景
        self.create_stars()
        
        # UI元素
        self.create_ui()
        
        # 计时器
        self.enemy_timer = 0
        self.star_timer = 0
        
        # 键盘状态
        self.keys = key.KeyStateHandler()
        self.push_handlers(self.keys)
    
    def create_background(self):
        """创建渐变背景"""
        # 创建渐变色背景
        self.bg = pyglet.graphics.OrderedGroup(0)
        
        # 从上到下的渐变(深空色)
        colors = [
            (10, 10, 40, 255),    # 顶部:深蓝色
            (5, 5, 20, 255),      # 中部:更深蓝
            (0, 0, 10, 255)       # 底部:近黑色
        ]
        
        # 创建渐变矩形
        for i in range(3):
            height = self.height // 3
            y = i * height
            color = colors[i]
            
            pyglet.graphics.draw(
                4, pyglet.gl.GL_QUADS,
                ('v2f', [0, y, self.width, y, 
                        self.width, y+height, 0, y+height]),
                ('c3B', color[:3] * 4)
            )
    
    def create_stars(self):
        """创建星星"""
        for _ in range(100):
            star = {
                'x': random.randint(0, self.width),
                'y': random.randint(0, self.height),
                'size': random.uniform(0.5, 2.0),
                'speed': random.uniform(0.5, 2.0),
                'brightness': random.uniform(0.5, 1.0)
            }
            self.stars.append(star)
    
    def create_player(self):
        """创建玩家飞船"""
        # 创建三角形飞船
        self.player = {
            'x': self.width // 2,
            'y': 50,
            'width': 30,
            'height': 40,
            'speed': 5,
            'color': (0, 200, 255),
            'fire_rate': 0.2,
            'fire_timer': 0,
            'health': 100
        }
    
    def create_ui(self):
        """创建UI元素"""
        self.score_label = pyglet.text.Label(
            '分数: 0',
            font_name='Arial',
            font_size=20,
            x=10,
            y=self.height - 30,
            color=(255, 255, 255, 255),
            batch=self.batch
        )
        
        self.health_label = pyglet.text.Label(
            '生命: 100',
            font_name='Arial',
            font_size=20,
            x=10,
            y=self.height - 60,
            color=(255, 255, 255, 255),
            batch=self.batch
        )
        
        self.level_label = pyglet.text.Label(
            '关卡: 1',
            font_name='Arial',
            font_size=20,
            x=self.width - 100,
            y=self.height - 30,
            color=(255, 255, 255, 255),
            batch=self.batch
        )
    
    def update_stars(self, dt):
        """更新星星位置"""
        for star in self.stars:
            star['y'] -= star['speed']
            if star['y'] < 0:
                star['y'] = self.height
                star['x'] = random.randint(0, self.width)
    
    def update_player(self, dt):
        """更新玩家"""
        # 移动
        if self.keys[key.LEFT] and self.player['x'] > self.player['width']//2:
            self.player['x'] -= self.player['speed']
        if self.keys[key.RIGHT] and self.player['x'] < self.width - self.player['width']//2:
            self.player['x'] += self.player['speed']
        if self.keys[key.UP] and self.player['y'] < self.height - self.player['height']//2:
            self.player['y'] += self.player['speed']
        if self.keys[key.DOWN] and self.player['y'] > self.player['height']//2:
            self.player['y'] -= self.player['speed']
        
        # 射击
        self.player['fire_timer'] -= dt
        if self.keys[key.SPACE] and self.player['fire_timer'] <= 0:
            self.create_bullet()
            self.player['fire_timer'] = self.player['fire_rate']
    
    def create_bullet(self):
        """创建子弹"""
        bullet = {
            'x': self.player['x'],
            'y': self.player['y'] + self.player['height']//2,
            'width': 4,
            'height': 20,
            'speed': 10,
            'color': (255, 255, 0)
        }
        self.bullets.append(bullet)
    
    def update_bullets(self):
        """更新子弹"""
        for bullet in self.bullets[:]:
            bullet['y'] += bullet['speed']
            
            # 移除屏幕外的子弹
            if bullet['y'] > self.height:
                self.bullets.remove(bullet)
    
    def create_enemy(self):
        """创建敌人"""
        enemy_type = random.randint(1, min(3, self.level))
        
        if enemy_type == 1:  # 普通敌人
            color = (255, 100, 100)
            speed = 2
            health = 30
        elif enemy_type == 2:  # 快速敌人
            color = (255, 200, 100)
            speed = 4
            health = 20
        else:  # 重型敌人
            color = (255, 50, 50)
            speed = 1
            health = 50
        
        enemy = {
            'x': random.randint(30, self.width - 30),
            'y': self.height + 30,
            'width': 30,
            'height': 30,
            'speed': speed,
            'color': color,
            'health': health,
            'type': enemy_type
        }
        self.enemies.append(enemy)
    
    def update_enemies(self):
        """更新敌人"""
        for enemy in self.enemies[:]:
            enemy['y'] -= enemy['speed']
            
            # 移除屏幕外的敌人
            if enemy['y'] < -30:
                self.enemies.remove(enemy)
    
    def check_collisions(self):
        """检测碰撞"""
        # 子弹与敌人碰撞
        for bullet in self.bullets[:]:
            for enemy in self.enemies[:]:
                if (abs(bullet['x'] - enemy['x']) < (bullet['width'] + enemy['width']) / 2 and
                    abs(bullet['y'] - enemy['y']) < (bullet['height'] + enemy['height']) / 2):
                    
                    enemy['health'] -= 25
                    
                    if enemy['health'] <= 0:
                        self.score += enemy['type'] * 100
                        self.enemies.remove(enemy)
                    
                    if bullet in self.bullets:
                        self.bullets.remove(bullet)
                    break
        
        # 玩家与敌人碰撞
        for enemy in self.enemies[:]:
            if (abs(self.player['x'] - enemy['x']) < (self.player['width'] + enemy['width']) / 2 and
                abs(self.player['y'] - enemy['y']) < (self.player['height'] + enemy['height']) / 2):
                
                self.player['health'] -= 10
                self.enemies.remove(enemy)
                
                if self.player['health'] <= 0:
                    self.state = "GAME_OVER"
    
    def update_game(self, dt):
        """更新游戏逻辑"""
        self.update_stars(dt)
        self.update_player(dt)
        self.update_bullets()
        self.update_enemies()
        self.check_collisions()
        
        # 更新UI
        self.score_label.text = f'分数: {self.score}'
        self.health_label.text = f'生命: {self.player["health"]}'
        self.level_label.text = f'关卡: {self.level}'
        
        # 生成敌人
        self.enemy_timer += dt
        spawn_rate = max(0.5, 2.0 - self.level * 0.1)
        
        if self.enemy_timer >= spawn_rate:
            self.create_enemy()
            self.enemy_timer = 0
        
        # 升级
        if self.score >= self.level * 1000:
            self.level += 1
            self.player['health'] = min(100, self.player['health'] + 20)
    
    def draw_player(self):
        """绘制玩家飞船"""
        x, y = self.player['x'], self.player['y']
        width, height = self.player['width'], self.player['height']
        color = self.player['color']
        
        # 绘制三角形飞船
        vertices = [
            x, y + height//2,          # 顶部
            x - width//2, y - height//2,  # 左下
            x + width//2, y - height//2   # 右下
        ]
        
        pyglet.graphics.draw(
            3, pyglet.gl.GL_TRIANGLES,
            ('v2f', vertices),
            ('c3B', color * 3)
        )
    
    def draw_bullets(self):
        """绘制子弹"""
        for bullet in self.bullets:
            x, y = bullet['x'], bullet['y']
            width, height = bullet['width'], bullet['height']
            color = bullet['color']
            
            pyglet.graphics.draw(
                4, pyglet.gl.GL_QUADS,
                ('v2f', [
                    x - width//2, y - height//2,
                    x + width//2, y - height//2,
                    x + width//2, y + height//2,
                    x - width//2, y + height//2
                ]),
                ('c3B', color * 4)
            )
    
    def draw_enemies(self):
        """绘制敌人"""
        for enemy in self.enemies:
            x, y = enemy['x'], enemy['y']
            width, height = enemy['width'], enemy['height']
            color = enemy['color']
            
            # 绘制方形敌人
            pyglet.graphics.draw(
                4, pyglet.gl.GL_QUADS,
                ('v2f', [
                    x - width//2, y - height//2,
                    x + width//2, y - height//2,
                    x + width//2, y + height//2,
                    x - width//2, y + height//2
                ]),
                ('c3B', color * 4)
            )
            
            # 绘制生命条
            health_width = width * (enemy['health'] / 50)
            pyglet.graphics.draw(
                4, pyglet.gl.GL_QUADS,
                ('v2f', [
                    x - width//2, y + height//2 + 5,
                    x - width//2 + health_width, y + height//2 + 5,
                    x - width//2 + health_width, y + height//2 + 8,
                    x - width//2, y + height//2 + 8
                ]),
                ('c3B', (0, 255, 0) * 4)
            )
    
    def draw_stars(self):
        """绘制星星"""
        for star in self.stars:
            size = star['size']
            brightness = int(255 * star['brightness'])
            
            pyglet.graphics.draw(
                4, pyglet.gl.GL_QUADS,
                ('v2f', [
                    star['x'] - size, star['y'] - size,
                    star['x'] + size, star['y'] - size,
                    star['x'] + size, star['y'] + size,
                    star['x'] - size, star['y'] + size
                ]),
                ('c3B', (brightness, brightness, brightness) * 4)
            )
    
    def draw_health_bar(self):
        """绘制玩家生命条"""
        x = self.width // 2 - 100
        y = 20
        width = 200
        height = 15
        
        # 背景
        pyglet.graphics.draw(
            4, pyglet.gl.GL_QUADS,
            ('v2f', [x, y, x+width, y, x+width, y+height, x, y+height]),
            ('c3B', (50, 50, 50) * 4)
        )
        
        # 生命值
        health_width = width * (self.player['health'] / 100)
        health_color = (
            int(255 * (1 - self.player['health'] / 100)),
            int(255 * (self.player['health'] / 100)),
            0
        )
        
        pyglet.graphics.draw(
            4, pyglet.gl.GL_QUADS,
            ('v2f', [x, y, x+health_width, y, 
                     x+health_width, y+height, x, y+height]),
            ('c3B', health_color * 4)
        )
    
    def draw_menu(self):
        """绘制菜单"""
        # 标题
        title = pyglet.text.Label(
            '太空射击游戏',
            font_name='Arial',
            font_size=48,
            x=self.width // 2,
            y=self.height * 0.7,
            anchor_x='center',
            anchor_y='center',
            color=(255, 255, 0, 255)
        )
        title.draw()
        
        # 开始按钮
        start = pyglet.text.Label(
            '按 SPACE 开始游戏',
            font_name='Arial',
            font_size=32,
            x=self.width // 2,
            y=self.height * 0.5,
            anchor_x='center',
            anchor_y='center',
            color=(255, 255, 255, 255)
        )
        start.draw()
        
        # 控制说明
        controls = pyglet.text.Label(
            '方向键移动,SPACE射击',
            font_name='Arial',
            font_size=20,
            x=self.width // 2,
            y=self.height * 0.3,
            anchor_x='center',
            anchor_y='center',
            color=(200, 200, 200, 255)
        )
        controls.draw()
    
    def draw_game_over(self):
        """绘制游戏结束画面"""
        # 半透明覆盖层
        overlay = pyglet.shapes.Rectangle(
            0, 0, self.width, self.height,
            color=(0, 0, 0, 200)
        )
        overlay.draw()
        
        # 游戏结束文字
        game_over = pyglet.text.Label(
            '游戏结束!',
            font_name='Arial',
            font_size=48,
            x=self.width // 2,
            y=self.height * 0.6,
            anchor_x='center',
            anchor_y='center',
            color=(255, 0, 0, 255)
        )
        game_over.draw()
        
        # 最终分数
        score_text = pyglet.text.Label(
            f'最终分数: {self.score}',
            font_name='Arial',
            font_size=36,
            x=self.width // 2,
            y=self.height * 0.45,
            anchor_x='center',
            anchor_y='center',
            color=(255, 255, 255, 255)
        )
        score_text.draw()
        
        # 重新开始提示
        restart = pyglet.text.Label(
            '按 R 重新开始',
            font_name='Arial',
            font_size=24,
            x=self.width // 2,
            y=self.height * 0.3,
            anchor_x='center',
            anchor_y='center',
            color=(200, 200, 255, 255)
        )
        restart.draw()
    
    def on_draw(self):
        """绘制"""
        self.clear()
        
        # 绘制星星背景
        self.draw_stars()
        
        if self.state == "MENU":
            self.draw_menu()
        
        elif self.state == "PLAYING":
            # 绘制游戏对象
            self.draw_bullets()
            self.draw_enemies()
            self.draw_player()
            
            # 绘制UI
            self.batch.draw()
            self.draw_health_bar()
        
        elif self.state == "GAME_OVER":
            self.draw_game_over()
    
    def on_key_press(self, symbol, modifiers):
        """键盘按下事件"""
        if self.state == "MENU":
            if symbol == key.SPACE:
                self.state = "PLAYING"
        
        elif self.state == "PLAYING":
            if symbol == key.ESCAPE:
                self.state = "MENU"
        
        elif self.state == "GAME_OVER":
            if symbol == key.R:
                self.init_game()
    
    def update(self, dt):
        """更新"""
        if self.state == "PLAYING":
            self.update_game(dt)


if __name__ == "__main__":
    game = SpaceShooter()
    game.run()

四、Pyglet 高级特性

1. 3D 游戏开发

python 复制代码
import pyglet
from pyglet.gl import *
import math

class Simple3DGame(pyglet.window.Window):
    def __init__(self):
        super().__init__(800, 600, "3D游戏", resizable=True)
        
        # 设置3D渲染
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_LIGHTING)
        glEnable(GL_LIGHT0)
        
        # 设置光照
        glLightfv(GL_LIGHT0, GL_POSITION, (GLfloat * 4)(1, 1, 1, 0))
        glLightfv(GL_LIGHT0, GL_AMBIENT, (GLfloat * 4)(0.2, 0.2, 0.2, 1))
        glLightfv(GL_LIGHT0, GL_DIFFUSE, (GLfloat * 4)(1, 1, 1, 1))
        
        # 设置材质
        glMaterialfv(GL_FRONT, GL_AMBIENT, (GLfloat * 4)(0.2, 0.2, 0.2, 1))
        glMaterialfv(GL_FRONT, GL_DIFFUSE, (GLfloat * 4)(0.8, 0.8, 0.8, 1))
        
        # 相机参数
        self.camera_distance = 5.0
        self.camera_angle_x = 45.0
        self.camera_angle_y = 45.0
        
        # 立方体旋转
        self.cube_rotation = 0.0
        
        pyglet.clock.schedule_interval(self.update, 1/60.0)
    
    def update(self, dt):
        self.cube_rotation += 90.0 * dt
    
    def on_draw(self):
        self.clear()
        
        # 设置投影矩阵
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(45, self.width/self.height, 0.1, 100.0)
        
        # 设置模型视图矩阵
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        
        # 设置相机位置
        glTranslatef(0, 0, -self.camera_distance)
        glRotatef(self.camera_angle_x, 1, 0, 0)
        glRotatef(self.camera_angle_y, 0, 1, 0)
        
        # 绘制立方体
        glPushMatrix()
        glRotatef(self.cube_rotation, 1, 1, 1)
        self.draw_cube()
        glPopMatrix()
    
    def draw_cube(self):
        """绘制彩色立方体"""
        vertices = [
            # 前面
            [-1, -1,  1], [ 1, -1,  1], [ 1,  1,  1], [-1,  1,  1],
            # 后面
            [-1, -1, -1], [-1,  1, -1], [ 1,  1, -1], [ 1, -1, -1],
            # 上面
            [-1,  1, -1], [-1,  1,  1], [ 1,  1,  1], [ 1,  1, -1],
            # 下面
            [-1, -1, -1], [ 1, -1, -1], [ 1, -1,  1], [-1, -1,  1],
            # 右面
            [ 1, -1, -1], [ 1,  1, -1], [ 1,  1,  1], [ 1, -1,  1],
            # 左面
            [-1, -1, -1], [-1, -1,  1], [-1,  1,  1], [-1,  1, -1]
        ]
        
        colors = [
            [1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0],
            [1, 0, 1], [0, 1, 1], [0.5, 0.5, 0.5], [1, 0.5, 0],
            [0.5, 1, 0], [0, 0.5, 1], [1, 0, 0.5], [0.5, 0, 1],
            [1, 1, 0.5], [0.5, 1, 1], [1, 0.5, 1], [0.5, 0.5, 1],
            [1, 0.5, 0.5], [0.5, 1, 0.5], [0.5, 0.5, 0], [0, 0.5, 0.5],
            [0.5, 0, 0.5], [1, 1, 1], [0.2, 0.2, 0.2], [0.8, 0.8, 0.8]
        ]
        
        glBegin(GL_QUADS)
        for i, vertex in enumerate(vertices):
            glColor3f(*colors[i % len(colors)])
            glVertex3f(*vertex)
        glEnd()

2. 粒子系统优化

python 复制代码
class ParticleSystem:
    """高性能粒子系统"""
    def __init__(self, batch, group=None):
        self.batch = batch
        self.group = group
        
        # 使用顶点列表提高性能
        self.vertices = []
        self.colors = []
        self.vertex_list = None
        
        # 粒子数据
        self.particles = []
        self.max_particles = 1000
    
    def add_particle(self, x, y, vx, vy, lifetime, color, size):
        """添加粒子"""
        if len(self.particles) >= self.max_particles:
            return
            
        particle = {
            'x': x, 'y': y,
            'vx': vx, 'vy': vy,
            'life': lifetime,
            'max_life': lifetime,
            'color': color,
            'size': size
        }
        self.particles.append(particle)
    
    def update(self, dt):
        """更新所有粒子"""
        self.vertices = []
        self.colors = []
        
        for particle in self.particles[:]:
            # 更新位置
            particle['x'] += particle['vx'] * dt
            particle['y'] += particle['vy'] * dt
            
            # 更新生命周期
            particle['life'] -= dt
            
            # 如果粒子还活着,添加到渲染列表
            if particle['life'] > 0:
                size = particle['size'] * (particle['life'] / particle['max_life'])
                alpha = int(255 * (particle['life'] / particle['max_life']))
                
                # 添加正方形粒子
                x, y = particle['x'], particle['y']
                half_size = size / 2
                
                # 四个顶点
                self.vertices.extend([
                    x - half_size, y - half_size,
                    x + half_size, y - half_size,
                    x + half_size, y + half_size,
                    x - half_size, y + half_size
                ])
                
                # 颜色(带透明度)
                r, g, b = particle['color']
                self.colors.extend([r, g, b, alpha] * 4)
            else:
                # 移除死亡粒子
                self.particles.remove(particle)
        
        # 更新顶点列表
        if self.vertices:
            if self.vertex_list is None:
                self.vertex_list = self.batch.add(
                    len(self.particles) * 4,
                    pyglet.gl.GL_QUADS,
                    self.group,
                    ('v2f/stream', self.vertices),
                    ('c4B/stream', self.colors)
                )
            else:
                self.vertex_list.vertices = self.vertices
                self.vertex_list.colors = self.colors
        elif self.vertex_list:
            self.vertex_list.delete()
            self.vertex_list = None

五、最佳实践

  1. 性能优化技巧
python 复制代码
# 1. 使用批处理渲染
batch = pyglet.graphics.Batch()
sprites = [pyglet.sprite.Sprite(img, batch=batch) for _ in range(100)]
# 一次性绘制所有精灵
batch.draw()

# 2. 使用顶点列表而不是单独绘制
vertex_list = batch.add(4, GL_QUADS, None,
    ('v2f', vertices),
    ('c3B', colors)
)

# 3. 纹理图集(减少状态切换)
texture_atlas = pyglet.image.atlas.TextureAtlas(1024, 1024)

# 4. 对象池(减少内存分配)
class ObjectPool:
    def __init__(self, create_func, max_size=100):
        self.pool = []
        self.create_func = create_func
        self.max_size = max_size
    
    def get(self):
        if self.pool:
            return self.pool.pop()
        return self.create_func()
    
    def recycle(self, obj):
        if len(self.pool) < self.max_size:
            self.pool.append(obj)
  1. 资源管理
python 复制代码
class ResourceManager:
    def __init__(self):
        self.images = {}
        self.sounds = {}
        self.fonts = {}
    
    def load_image(self, name, path):
        if name not in self.images:
            self.images[name] = pyglet.image.load(path)
        return self.images[name]
    
    def load_sound(self, name, path):
        if name not in self.sounds:
            self.sounds[name] = pyglet.media.load(path, streaming=False)
        return self.sounds[name]
    
    def unload_unused(self):
        # 实现LRU缓存策略
        pass
  1. 状态管理
python 复制代码
class StateMachine:
    def __init__(self, initial_state):
        self.states = {}
        self.current_state = initial_state
    
    def add_state(self, name, state):
        self.states[name] = state
    
    def change_state(self, name):
        if self.current_state:
            self.current_state.exit()
        
        self.current_state = self.states[name]
        self.current_state.enter()
    
    def update(self, dt):
        if self.current_state:
            self.current_state.update(dt)
    
    def draw(self):
        if self.current_state:
            self.current_state.draw()

六、调试技巧

  1. 显示调试信息
python 复制代码
class DebugInfo:
    def __init__(self, window):
        self.window = window
        self.labels = []
        self.info = {}
    
    def set_info(self, key, value):
        self.info[key] = value
    
    def draw(self):
        y = self.window.height - 30
        for key, value in self.info.items():
            label = pyglet.text.Label(
                f"{key}: {value}",
                x=10, y=y,
                font_size=12,
                color=(255, 255, 255, 255)
            )
            label.draw()
            y -= 20
  1. 性能分析
python 复制代码
import time

class Profiler:
    def __init__(self):
        self.sections = {}
    
    def start(self, name):
        self.sections[name] = time.time()
    
    def end(self, name):
        if name in self.sections:
            elapsed = time.time() - self.sections[name]
            print(f"{name}: {elapsed*1000:.2f}ms")

七、项目结构建议

复制代码
pyglet_game/
├── main.py              # 程序入口
├── game.py              # 游戏主类
├── resources.py         # 资源管理
├── states/              # 游戏状态
│   ├── __init__.py
│   ├── menu_state.py
│   ├── game_state.py
│   └── game_over_state.py
├── entities/            # 游戏实体
│   ├── player.py
│   ├── enemy.py
│   ├── bullet.py
│   └── particle.py
├── systems/             # 游戏系统
│   ├── collision_system.py
│   ├── spawn_system.py
│   └── particle_system.py
├── utils/               # 工具函数
│   ├── math_utils.py
│   ├── debug.py
│   └── constants.py
├── assets/              # 游戏资源
│   ├── images/
│   ├── sounds/
│   └── fonts/
└── config.py            # 配置文件

Pyglet是一个强大而灵活的游戏开发库,特别适合需要高性能和现代图形特性的项目。通过合理的架构设计和优化,你可以用它创建出色的2D和3D游戏。

相关推荐
HDO清风2 小时前
CASIA-HWDB2.x 数据集DGRL文件解析(python)
开发语言·人工智能·pytorch·python·目标检测·计算机视觉·restful
weixin_499771552 小时前
Python上下文管理器(with语句)的原理与实践
jvm·数据库·python
weixin_452159552 小时前
高级爬虫技巧:处理JavaScript渲染(Selenium)
jvm·数据库·python
多米Domi0112 小时前
0x3f 第48天 面向实习的八股背诵第五天 + 堆一题 背了JUC的题,java.util.Concurrency
开发语言·数据结构·python·算法·leetcode·面试
深蓝海拓2 小时前
PySide6从0开始学习的笔记(二十六) 重写Qt窗口对象的事件(QEvent)处理方法
笔记·python·qt·学习·pyqt
纠结哥_Shrek2 小时前
外贸选品工程师的工作流程和方法论
python·机器学习
小汤圆不甜不要钱2 小时前
「Datawhale」RAG技术全栈指南 Task 5
python·llm·rag
A懿轩A3 小时前
【Java 基础编程】Java 变量与八大基本数据类型详解:从声明到类型转换,零基础也能看懂
java·开发语言·python
Tansmjs3 小时前
使用Python自动收发邮件
jvm·数据库·python