用Python复刻Chrome断网小恐龙游戏:从零开始的Pygame实战教程
你是否曾在网络连接中断时,对着Chrome浏览器里那只跳跃奔跑的小恐龙会心一笑?这个看似简单的游戏,实际上是一个绝佳的编程学习项目。它麻雀虽小,五脏俱全,涵盖了游戏开发中状态管理、碰撞检测、动画循环、事件处理等核心概念。对于Python初学者而言,通过复刻这个经典游戏来入门Pygame,远比从枯燥的理论学起要有趣得多。今天,我们就抛开现成的代码模板,从一张白纸开始,一步步构建属于我们自己的“小恐龙快跑”。我会带你深入每个模块的内部逻辑,分享我在实现过程中踩过的坑和总结的技巧,让你不仅“知其然”,更“知其所以然”。
1. 项目初始化与环境搭建
在动手写第一行游戏代码之前,我们需要一个稳固的“地基”。这个地基就是Pygame库以及清晰的项目结构。很多教程会直接让你pip install pygame然后开始编码,但我建议先花几分钟规划一下,这能避免后续的混乱。
首先,确保你的Python环境是3.6或更高版本。打开终端或命令提示符,使用pip安装Pygame:
pip install pygame
安装完成后,我习惯创建一个独立的项目文件夹,比如dino_game,并在其中建立清晰的子目录来存放资源。这是我们的项目结构蓝图:
dino_game/
├── main.py # 游戏主入口文件
├── assets/ # 资源文件夹
│ ├── dino/ # 小恐龙精灵图
│ ├── cactus/ # 仙人掌障碍物图片
│ ├── bird/ # 飞鸟障碍物图片
│ └── other/ # 背景、云朵等图片
└── utils.py # 工具函数(可选,如高分记录)
注意:你可以从开源资源网站(如OpenGameArt)寻找免费的像素风素材,或者自己用简单的绘图工具绘制。关键在于图片背景最好是透明的(PNG格式),这样在游戏中融合会更自然。
接下来,在main.py中,我们进行最基础的Pygame初始化。这不仅仅是两行代码,理解每个参数的意义很重要。
import pygame
import sys
import os
# 初始化Pygame的所有模块
pygame.init()
# 定义游戏窗口的常量
SCREEN_WIDTH = 1100
SCREEN_HEIGHT = 600
FPS = 60 # 帧率,控制游戏刷新速度
# 创建游戏窗口
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("我的小恐龙快跑")
clock = pygame.time.Clock() # 用于控制游戏循环的时钟
# 定义颜色(RGB格式),方便后续使用
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (240, 240, 240)
这里有几个关键点:
pygame.init():必须调用,它启动了Pygame的字体、声音等所有子系统。FPS(Frames Per Second):设置为60,意味着游戏每秒尝试更新和绘制60次。这个值直接影响游戏的手感和流畅度。clock.tick(FPS):我们会在游戏主循环中用到它,它确保循环每秒最多执行FPS次,从而稳定游戏速度,避免在不同性能的电脑上运行速度不一致。
2. 游戏核心类设计与实现
面向对象编程(OOP)是组织游戏代码的利器。我们将游戏中的主要“演员”——恐龙、障碍物、背景元素——都设计成类。这能让代码逻辑清晰,易于维护和扩展。
2.1 恐龙类:状态机的经典案例
小恐龙有三种状态:奔跑、跳跃、下蹲。用一堆布尔标志(is_running, is_jumping...)来管理会很快变得混乱。更好的方法是使用一个明确的状态变量。我们先来定义这个类的基本骨架。
class Dinosaur:
def __init__(self):
# 恐龙在屏幕上的初始位置
self.x_pos = 80
self.y_pos = 310 # 奔跑和跳跃时的Y坐标
self.y_pos_duck = 340 # 下蹲时身体更低,Y坐标更大
# 加载图像资源
self.run_imgs = [...] # 奔跑动画帧列表
self.jump_img = ... # 跳跃静态图
self.duck_imgs = [...] # 下蹲动画帧列表
# 核心状态变量:'RUN', 'JUMP', 'DUCK'
self.state = 'RUN'
# 动画帧索引,用于在不同动画帧间切换
self.step_index = 0
# 跳跃物理参数
self.jump_velocity = 8.5
self.current_jump_vel = self.jump_velocity
# 获取图像矩形区域,用于碰撞检测
self.rect = self.run_imgs[0].get_rect()
self.rect.x = self.x_pos
self.rect.y = self.y_pos
def update(self, user_input):
# 根据状态和用户输入更新恐龙
pass
def draw(self, surface):
# 将恐龙绘制到指定的surface(通常是屏幕)上
pass
update方法是这个类的灵魂。它需要处理用户输入(键盘事件),并根据当前状态进行切换。这里有一个状态转换逻辑需要仔细处理:
| 当前状态 | 按下UP键 | 按下DOWN键 | 无相关按键 |
|---|---|---|---|
| 奔跑 (RUN) | 切换到JUMP | 切换到DUCK | 保持RUN |
| 跳跃 (JUMP) | 无效(已在跳) | 无效(空中不能下蹲) | 受重力影响,落地后切回RUN |
| 下蹲 (DUCK) | 无效(下蹲中) | 保持DUCK | 释放按键后切回RUN |
跳跃的物理模拟是一个小亮点。我们用一个简单的匀减速运动来模拟:
def _jump(self):
"""处理跳跃的物理更新"""
if self.state == 'JUMP':
# 向上移动:减去速度值
self.rect.y -= self.current_jump_vel * 4
# 速度递减,模拟重力
self.current_jump_vel -= 0.8
# 当速度反向并达到初始大小时,认为跳跃结束
if self.current_jump_vel < -self.jump_velocity:
self.state = 'RUN'
self.current_jump_vel = self.jump_velocity # 重置速度
self.rect.y = self.y_pos # 重置Y坐标
2.2 障碍物系统:工厂模式与继承的应用
障碍物(仙人掌、飞鸟)有很多共性:它们都在屏幕右侧生成,向左移动,需要被绘制和更新,并且能与恐龙发生碰撞。我们可以设计一个基类Obstacle,然后让具体的障碍物类型继承它。
首先看基类:
class Obstacle:
def __init__(self, image, obstacle_type):
self.image = image # 可能是一个图片列表
self.type = obstacle_type # 用于选择图片列表中的哪一个
self.rect = self.image[self.type].get_rect()
self.rect.x = SCREEN_WIDTH # 从屏幕最右侧开始出现
def update(self, game_speed):
self.rect.x -= game_speed
# 如果完全移出屏幕左侧,返回True,通知主程序可以将其移除
if self.rect.x < -self.rect.width:
return True
return False
def draw(self, surface):
surface.blit(self.image[self.type], self.rect)
然后,我们创建具体的障碍物类。注意它们如何通过调用super().__init__来复用基类的初始化逻辑。
class SmallCactus(Obstacle):
def __init__(self, image_list):
# 随机选择0,1,2中的一种仙人掌形态
cactus_type = random.randint(0, 2)
super().__init__(image_list, cactus_type)
self.rect.y = 325 # 小型仙人掌的落地高度
class Bird(Obstacle):
def __init__(self, image_list):
super().__init__(image_list, 0) # 鸟通常只有一种类型,但图片列表里是动画帧
self.rect.y = 250 # 鸟在空中飞行的高度
self.animation_index = 0
self.animation_speed = 0.2
def draw(self, surface):
# 重写draw方法,实现扇动翅膀的动画
img_index = int(self.animation_index) % len(self.image)
surface.blit(self.image[img_index], self.rect)
self.animation_index += self.animation_speed
为了管理屏幕上不断生成和消失的障碍物,我们需要一个障碍物管理器。它的职责是:
- 随机决定何时生成新障碍物。
- 随机选择生成哪种类型的障碍物(小仙人掌、大仙人掌或鸟)。
- 更新所有现存障碍物的位置。
- 移除已经移出屏幕的障碍物,以节省内存。
这个管理器可以是一个函数,也可以封装成一个类。我更喜欢用类,逻辑更集中。
class ObstacleManager:
def __init__(self):
self.obstacles = []
self.next_obstacle_time = 0 # 用于控制生成频率
def update(self, game_speed, current_score):
# 1. 更新现有障碍物
for obstacle in self.obstacles[:]: # 使用切片创建副本进行遍历,安全删除
if obstacle.update(game_speed):
self.obstacles.remove(obstacle)
# 2. 决定是否生成新障碍物
# 基础生成间隔,分数越高,间隔可能越短,游戏越难
base_interval = max(20, 40 - current_score // 100)
if len(self.obstacles) == 0 and random.randint(0, 200) < 5:
# 随机选择障碍物类型
obstacle_choice = random.random()
if obstacle_choice < 0.5:
self.obstacles.append(SmallCactus(SMALL_CACTUS_IMGS))
elif obstacle_choice < 0.8:
self.obstacles.append(LargeCactus(LARGE_CACTUS_IMGS))
else:
self.obstacles.append(Bird(BIRD_IMGS))
def draw(self, surface):
for obstacle in self.obstacles:
obstacle.draw(surface)
def get_obstacles(self):
return self.obstacles
def reset(self):
self.obstacles.clear()
3. 构建游戏主循环与核心逻辑
游戏主循环是游戏的心脏,它是一个不断运行的while循环,每一帧都执行以下步骤:
- 处理事件(如退出、按键)。
- 更新游戏状态(恐龙位置、障碍物位置、分数等)。
- 绘制一切(背景、角色、UI)。
- 控制帧率。
让我们构建一个清晰的主循环:
def main_game_loop():
running = True
game_over = False
score = 0
game_speed = 10 # 初始游戏速度(背景和障碍物左移的速度)
player = Dinosaur()
obstacle_manager = ObstacleManager()
cloud = Cloud() # 背景云朵,也是一个可移动的精灵
while running:
# --- 1. 事件处理 ---
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 游戏结束时,按任意键重启
if game_over and event.type == pygame.KEYDOWN:
# 重置游戏状态
game_over = False
score = 0
game_speed = 10
player = Dinosaur()
obstacle_manager.reset()
if not game_over:
# --- 2. 获取用户持续按键 ---
user_input = pygame.key.get_pressed()
# --- 3. 更新游戏对象 ---
player.update(user_input)
obstacle_manager.update(game_speed, score)
cloud.update(game_speed)
# --- 4. 碰撞检测 ---
for obstacle in obstacle_manager.get_obstacles():
# 使用pygame.Rect的colliderect方法进行矩形碰撞检测
# 这里做了一个小优化:只检测恐龙右侧与障碍物左侧的碰撞,避免“擦肩而过”时误判
if player.rect.colliderect(obstacle.rect) and player.rect.right >= obstacle.rect.left:
game_over = True
# 可以在这里触发一个简单的震动效果或播放音效
break
# --- 5. 更新分数和游戏速度 ---
score += 1
# 每100分,游戏速度增加,难度提升
if score % 100 == 0:
game_speed += 0.5
# --- 6. 绘制 ---
screen.fill(GRAY) # 填充背景色
draw_background(game_speed) # 绘制滚动的地面
cloud.draw(screen)
obstacle_manager.draw(screen)
player.draw(screen)
draw_score(score, screen) # 绘制分数UI
if game_over:
draw_game_over_screen(score, screen) # 绘制游戏结束界面
# --- 7. 刷新屏幕并控制帧率 ---
pygame.display.update()
clock.tick(FPS)
pygame.quit()
sys.exit()
碰撞检测是游戏逻辑的关键。我们使用的是最简单的**轴对齐边界框(AABB)**检测,即pygame.Rect.colliderect()。这对于矩形精灵来说高效且足够。但在实际项目中,你可能会遇到需要更精确检测的情况(如像素完美检测或圆形检测),Pygame也提供了相应的方法。
4. 打磨游戏体验:动画、难度与持久化
一个完整的游戏不仅要有核心玩法,还需要打磨细节,提升用户体验。
4.1 平滑的动画系统
恐龙的奔跑和下蹲是两帧动画循环。我们通过一个step_index和一个计时器来实现:
# 在Dinosaur类的update方法中
if self.state in ['RUN', 'DUCK']:
self.step_index += 1
if self.step_index >= 10: # 控制动画速度
self.step_index = 0
# 根据状态选择图像列表和帧
if self.state == 'RUN':
image_list = self.run_imgs
frame = self.step_index // 5 # 0-4用第一帧,5-9用第二帧
self.image = image_list[frame]
elif self.state == 'DUCK':
image_list = self.duck_imgs
frame = self.step_index // 5
self.image = image_list[frame]
4.2 动态难度与分数系统
为了让游戏可玩性更高,我们需要一个动态的难度系统。通常,随着分数增加,游戏速度会加快,障碍物生成频率也会提高。我们在主循环和ObstacleManager中已经实现了基础部分。
一个有趣的扩展是引入**“连跳”惩罚或“完美躲避”奖励**。例如,连续成功跳过多个障碍物后,下一个障碍物生成间隔缩短,或者获得额外分数加成。这需要我们在碰撞检测之外,再增加一个对“成功躲避”的判断逻辑。
4.3 游戏状态管理与数据持久化
游戏应该有开始菜单、游戏进行中、结束界面等状态。我们用一个简单的状态变量(如game_state,取值'MENU', 'PLAYING', 'GAME_OVER')来管理。
最高分记录是让玩家有重复挑战欲望的重要功能。我们需要将最高分保存到本地文件。
import os
HIGH_SCORE_FILE = 'high_score.txt'
def load_high_score():
"""从文件加载最高分,如果文件不存在则返回0"""
if os.path.exists(HIGH_SCORE_FILE):
try:
with open(HIGH_SCORE_FILE, 'r') as f:
return int(f.read().strip())
except (ValueError, IOError):
return 0
return 0
def save_high_score(score):
"""将新的最高分保存到文件"""
try:
with open(HIGH_SCORE_FILE, 'w') as f:
f.write(str(score))
except IOError:
print("无法保存最高分文件")
在游戏结束时,比较当前分数和最高分,如果更高,则调用save_high_score并更新显示。
4.4 音效与视觉反馈(进阶)
虽然原版Chrome小恐龙游戏没有音效,但为自己的版本添加一些简单的反馈能极大提升质感。Pygame的mixer模块可以轻松加载和播放WAV或MP3文件。
# 在初始化部分
pygame.mixer.init()
jump_sound = pygame.mixer.Sound('assets/sound/jump.wav')
crash_sound = pygame.mixer.Sound('assets/sound/crash.wav')
# 在恐龙跳跃时
if user_input[pygame.K_UP] and self.state == 'RUN':
self.state = 'JUMP'
jump_sound.play() # 播放跳跃音效
# 在碰撞发生时
if collision_detected:
crash_sound.play()
game_over = True
视觉反馈方面,可以在碰撞时让屏幕短暂闪烁红色,或者让恐龙有一个“受伤”的动画状态。这些细节能让游戏感觉更生动。
5. 项目扩展与优化思路
当你完成了基础版本后,可以尝试以下扩展,这能让你更深入地理解游戏开发:
- 添加更多障碍物类型:比如会移动的石头、突然出现的地洞。这需要你设计新的类,并修改障碍物生成逻辑。
- 引入道具系统:例如,偶尔飞过的“加速翅膀”道具,拾取后短时间内游戏速度变慢(相当于恐龙变快)。这涉及到新的精灵类、碰撞检测(恐龙与道具)和临时状态管理。
- 实现关卡系统:将游戏划分为不同的“场景”,比如白天、夜晚、沙漠、雪地,每个场景有不同的背景和障碍物主题。这需要你管理一组资源,并在特定分数切换。
- 优化性能:当游戏对象很多时,绘制和更新可能成为瓶颈。可以研究一下脏矩形更新技术,即只重绘屏幕上发生变化的部分,而不是每一帧都重绘整个屏幕。
- 打包成可执行文件:使用
PyInstaller或cx_Freeze将你的Python脚本打包成.exe(Windows)或.app(Mac)文件,这样你就可以分享给没有安装Python的朋友玩了。
# 使用PyInstaller打包的示例命令
pip install pyinstaller
pyinstaller --onefile --windowed --add-data "assets;assets" main.py
这个命令会创建一个独立的可执行文件,并包含你的assets资源文件夹。
从零开始复刻一个经典游戏,最大的收获不是最终那几百行代码,而是在解决一个个具体问题(“如何让跳起来更自然?”、“怎么随机生成障碍物才不显得突兀?”)的过程中,建立起对编程逻辑和游戏架构的直观理解。我最初实现时,就被碰撞检测的精度问题困扰了很久,后来通过调整恐龙的碰撞矩形(rect)大小,并加入一个小的触发缓冲区域,才让手感变得舒适。当你看到自己编写的小恐龙在屏幕上灵巧地越过一个个障碍时,那种成就感是无可替代的。不妨现在就打开编辑器,从加载第一张图片开始,亲手创造这个属于你的小小世界吧。

527

被折叠的 条评论
为什么被折叠?



