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
五、最佳实践
- 性能优化技巧
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)
- 资源管理
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
- 状态管理
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()
六、调试技巧
- 显示调试信息
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
- 性能分析
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游戏。