20252223 《Python程序设计》实验4报告
20252223 《Python程序设计》大实验报告
课程:《Python程序设计》
班级: 2522
姓名: 冉云天
学号:20252223
实验教师:王志强
实验日期:2026年4月27日
必修/选修: 公选课
运行环境
python:Python 3.13.13
pygame:2.61
一、整体分析
1. 游戏核心玩法
Flappy Bird 的本质是一个 2D 物理 + 碰撞检测 + 得分机制的游戏,核心游戏逻辑如下:
(1)玩家控制一只鸟,
(2)鸟受重力影响不断下落,
(3)点击 / 按键让鸟向上飞
(4)屏幕不断向右移动,产生“向前飞行”的感觉
(5)随机生成上下管道
(6)鸟撞到地面、天花板或管道 → 游戏结束
(7)每穿过一组管道 → 得分 +1
二、模块拆分
必选模块:
(1)可以把整个游戏拆成 7 个核心模块:
(2)窗口管理
(3)鸟(玩家操控)
(4)管道(障碍物)
(5)地面
(6)背景
(7)碰撞检测系统
(8)得分系统
选择性增加模块:
(1)声音控制系统
(2)游戏结束ui系统
(3)特效系统(死亡特效)
(4)天气模拟系统(包括雾的模拟,闪电的模拟)
(5)进度条、按钮等ui
三、逐步设计思路
....整体执行流程(主循环的伪流程)
....text
....初始化 pygame
....创建窗口
....进入主循环:
.........处理事件
. .......如果游戏未开始:
.............显示开始画面
. .......如果游戏中:
.............更新鸟的速度和位置
. .......... 更新管道位置
....................检测碰撞
....................检测是否得分
....................绘制背景、管道、鸟、地面、分数
..............如果游戏结束:
...................显示 Game Over
....................等待重新开始
.............刷新屏幕
目标一:复刻经典游戏飞翔的小鸟《flappy bird》游戏(如图)

第一步:搭建游戏基础窗口
- 定义game类,在构造函数中初始化pygame模块,创建(800*600)窗口
- 搭建游戏循环,捕捉event事件,防止关不掉游戏窗口
- 设置帧率为60
- Main函数中将这个类实例化

运行结果

一个黑色窗口,由于没有填充任何颜色,默认黑色窗口。
第二步:绘制游戏基本元素
创建加载方法,导入背景图和地面图片(一定要加上.convert()否则每一次格式转换会消耗大量内存)
游戏循环中调用flip方法,否则屏幕会根本不刷新(研究了好久,才发现要调用这玩意)

由于图片无法覆盖整个窗口,需要多个背景图片,于是创建了self.backgrounds,列表,用于存储每个背景的左上角坐标
用while循环将背景图片铺展开来
创建blit打印函数,专门用于绘制游戏画面

运行结果:

背景图成功铺展开来。
第三步:绘制游戏其他元素
整理代码,把背景图绘制放到一个专门的create_sprites函数中(严格来说,这些背景图不是精灵,后面改了过来)
用同样的方法绘制地面(tile)和小鸟(bird)

运行结果:

第四步:整理代码,让游戏动起来
为小鸟飞翔添加动画(一共就三帧,每帧画面出现五个游戏帧的时长),另外,游戏类添加一个self.cur_frame的属性,用于记录从游戏开始到现在一共进行了多少次循环。

由于此时对象过多(游戏,小鸟、地面、背景,以及后面加入的管道),所以单独开设三个文件(bird.py,tile.py,background.py)分别将三者搞成类,并统一添加blit打印方法,load加载图像方法以及arrange排布设置位置方法
Game.py整理后的代码

Tile.py(地面)整理后的代码

Background.py整理后的代码

Bird.py整理后的代码

一目了然,简洁美观,而且体现了python的封装性、多态性,暂时还没有继承
然后让游戏动起来,逻辑是:因为运动具有相对性,鸟往前面飞就相当于鸟不动,背景向后面移动。于是对每个background,tile对象创建move方法和self.speed属性。
另外,当背景图向后推倒屏幕左边之外时,将其循环利用,移到最右边的背景图的右边
Main函数也要相应地import每个文件,并将每个对象实例化,然后逐一调用load init move等方法

运行结果:(景物向后退去,鸟挥动翅膀)

第五步:实现小鸟的基本运动
为游戏添加重力,game类添加gravity 属性,为小鸟添加self.velocity_y, angle, rotate_speed属性。
根据重力加速度,每一帧游戏循环都把velocity_y增加,但是不超过13(防止速度太快)
根据当前角度,设置小鸟向下坠落时的角度,并更行angle。

绘制图形时,将当前小鸟的图像进行旋转操作,然后将新图片的中心点对齐小鸟的坐标。(如果不对齐坐标,那么图像的orgin会默认左上角而不是中心,旋转会很不自然。这是pygame很坑的一点,因为不可以手动设置orgin)

添加flap函数(强行将竖直速度设置为-15,用来模拟跳跃的效果),另外将坠落代码移到fall方法中,然后将fall 和 flap一并移入到move方法

Game类也需要修改,新增一个监听鼠标按下的事件,只要按下鼠标左键,调用flap方法。

另外把所有监听事件移入到check_event方法,然后在主循环中调用,这样可以保持游戏主循环简洁。
运行结果:现在小鸟可以下坠翻滚了,而且点击鼠标还可以跳跃

第六步:生成管道
添加管道(pipe)类,load,move之类的函数复用刚才的模块,重点讲一下管道的生成方法
设置set_pair函数,用于生成一对管道的信息(上一个,下一个,小鸟从中间缝隙(gap)飞过),其中使用到random模块,保证gap的高度每次不同,参数范围进行限制,防止gap生成到屏幕外边去了,upper_y是上管道的top值,lower_y是下管道的top值。
然后,在arrange方法中,调用四次set_pair()传入x参数,这样,每次游戏开始时,会预先生成管道在屏幕右侧。同样的,每次管道出了屏幕,移到最右边循环利用,防止管道反复销毁、重建

另外,上管道和下管道公用一张图片,只不过下管道进行了上下翻转的图像处理。

运行结果:(管道每间隔444像素向左移动,游戏基本框架已经形成,但是缺少游戏失败的判断逻辑)

第七步:碰撞检测
给bird类添加碰撞检测函数,如果矩形发生重叠,就返回true
给game类添加游戏结束检测,如果小鸟和管道的矩形发生重叠,将游戏状态self.running设置为false.


另外小鸟碰撞后,游戏屏幕白色闪一下(用一个rgb(255,255,255)的矩形来模拟),如图update_white_flash函数

同时,将游戏画面copy一下,然后坐标加上随机值,用于模拟撞墙后的抖动效果,写入到screen_shake方法中

然后,在添加游戏失败后的结算ui
Move函数可以让结算画面跳出来更丝滑,blit打印游戏结束菜单

第八步:游戏逻辑完整实现
为了让游戏失败可以多次重开,需要每次重新开始时把所有部件复位。因此把每个对象都添加reinit方法,用于清除所有精灵。复原参数,位置,flag等等。篇幅限制,这里只给了main文件的reinit()代码

运行结果:每次游戏结束后,再次点击,即可重新开始游玩。

第九步:
添加Number.py,用于显示分数。这里需要用到pygame的container,把得分拆成一个个数字后,组合到container容器中,这样可以把多位数的每个数字看作一个整体,整体移动和打印可以防止数字错位偏移


添加开始界面的ui

至此,一个基本的flappybird游戏复刻已经完成。
目标二:游戏玩法创新
添加几个游戏功能(过程复杂,代码过长,代码都在仓库里面)。
1. 长按鼠标,小鸟可以进行后空翻操作,并且蓄力一次,然后按下空格键,加速(如图)

2.添加游戏音乐和音效(全都是网上其他游戏扒下的),可自主选择音量

2. 添加管道种类
(1) 上下移动型
(2) 左右跟随型
(3) 一面墙(需要飞到屏幕上面之外),增加难度
(4) 特别短的类型
根据难度选择不同,难度越高,加强版管道种类会增加。(如果游戏太难了,按下shift切入到英文输入法之后,按下o键可以开启无敌模式)
3. 添加各类游戏场景和特色
通过点击标签切换难度和场景
(1)夜晚(游戏重力、速度、帧率,会随机变化,而且可以触发进入恐怖隐藏关卡)

(2)雷雨天气(闪电自动追踪小鸟,可以劈死,小鸟需要空翻蓄力后按下space加速操作来躲避闪电。非闪电时刻,整个屏幕黑暗增加难度)


(3)雾天(对向会有雾气,视线受阻。另外,需要小鸟需要空翻后加速操作来摆脱后面雾气)

(5) 晴天(对向会有其他鸟,需要躲避)

(6)隐藏恐怖关卡,需要在夜晚模式掉进特定管道来触发进入。(也可以在英文输入法下,按下h键后,再按下k键自鲨后进入)
至此,完整的、玩法多样的、难度巨难、刺激版的游戏开发完成。
目标三:对游戏进行打包
对主文件进行打包

注意,必须修改游戏对依赖资源的路径修改,需要加入一下代码
import os
import sys
def resource_path(relative_path):
"""获取打包后的资源路径"""
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
运行结果:
打包成功。

在根目录下dist文件夹内找到相应exe文件

使用tortoisegit托管代码到码云:


目标四:将游戏部署到云服务器(用javascript语言更加方便)
1.开通云服务器

2.开放必要端口

3.通过ssh服务将代码转移到云服务器的usr/share/nginx/html文件夹

4.游戏通过网页进入

四、遇到的问题:
- 问题:一旦窗口放大,游戏会变得异常卡顿
解决方式:将每个pygame.load后面加上.convert(),可以防止每次转换格式消耗大量资源 - 问题:游戏每间隔一段时间就会卡一下
解决方式:import进来gc模块,定期地清除程序缓存,防止垃圾堆积过多 - 问题:游戏碰撞检测函数失灵
解决方式:Rect属性需要手动调整位置,用了graph模块画出碰撞箱后,要保证元素和碰撞箱位置一样 - 问题:游戏进行速度受到帧率的影响,运行时时快时慢,影响游戏体验
解决方式:引入delta,每次位移时,加上 *delta/16.66(delta为每两帧之间的时间差,16.66每一帧的时间) - 问题:背景图片覆盖了其他所有元素
解决方式:Pygame无法设定depth层级,需要精准设置不同元素的绘制顺序,调整blit的调用顺序,保证优先级高的元素在上面。 - 问题:解决方式游戏运行到后面卡死
精灵组的元素只是remove出精灵组,但是没有kill销毁,导致大量sprite积压,占用内存。所以要kill不要remove。 - 问题:游戏资源无法加载
解决方式:在调用不同的资源时,要切换不同的basic_path,而且最好使用相对路径,以免找不到资源
五、代码仓库:
python版游戏代码:https://gitee.com/rytll/flappy-bird-python
javascript版游戏代码:https://gitee.com/rytll/flappy-bird
javascript版游戏网址:besti-mc.xyz(备用服务器 http://123.249.38.56/ )
javascript版的游戏介绍视频(游戏玩法基本与python版一致): https://pan.baidu.com/s/1NgHQpl5kEHWDxIvg79TlpQ?pwd=2223 提取码: 2223
六、本实验感想:
这个游戏用的少量素材为网上爬取的,很多事经过漫长的ps修改(尤其是恐怖隐藏关卡的素材)得到的,所以做这个游戏还得会画画(后面我才意识到可以用ai 来做)。
这些代码的基础版本其实是在寒假完成的,为了这次作业做了很多功能的补充,并且重新把主要的逻辑框架重写了一遍。对pygame库的感想就是,pygame是在是太难用了。虽然我的游戏玩法丰富刺激,但是其实看起来很粗糙,比如难看的ui 动画,没有暂停功能,这完完全全就是Pygame支持的方法太少造成的。很多逻辑,比如tween的补间动画、元素绘制顺序、场景切换,都没有现成的方法,需要用其他框架(如phaser)五倍至十倍的工作量去完成,非常难做。另外,pygame的性能优化也是很差,动不动就卡,还需要手动清理垃圾,精灵一旦多起来,极其卡顿。而且没有办法将pygame项目部署到网页或者微信小程序上面,你唯一可以做的就是用pyinstaller打包成一个巨大的exe文件,传给别人用,所以Pygame完全就是为了教学而存在的,难以想象现实有谁会去用这个库。相比之下,javascript语言的phaser框架就会好太多,有现成的物理系统、粒子系统、补间动画、scene切换可以快速调用。而且从来也没有卡顿。
基于Pygame这么难用,我选择转向javascript的phaser框架,诞生了网页版游戏flappy bird 2.0,代码量有四千六百行,实现了更好的、更加丝滑、动画效果,也有了pygame难以实现暂停功能。其中大量素材经过ai绘制精美了许多,很多pygame无法实现的功能均可以在phaser上面实现。网址为besti-mc.xyz(如果服务器停摆,也可以访问我的码仓库下载源代码,需要用到的框架也包含在lib内,无需自己安装,入口文件为index.html,无敌模式依然为按下o键)。
课程知识点总结
0.gitee使用
1.初识python
(1)python的特点:高级的、解释性语言的、面向对象的、通用的、动态类型的
(2)python地位:tiboe排名
(3)应用:大模型、大数据
2.基本语法:
print imput(数字要int转换)
各种运算符
动态类型变量
保留字和标识符
注释、缩进
3.流程控制语句
if elif else 结构
for in :结构
while结构
pass continue break 使用
计算器设计实验
4.序列
列表、字典、元组、集合长什么样子
特性对比(有无顺序、可否更改)
访问、切片
列表推导式
5.函数
形参、实参是什么
函数如何创建调用
什么是局部变量、全局变量
6.字符串
如何切片、转化大小写、切割
正则表达式
7.面向对象
三要素:继承、多态、封装
创建类
将类的实例化
属性(共有、私有)
方法
book图书管理项目实践
8. 模块与异常处理
import使用
random模块 math等
解释if name==main的意思
try except finally 处理错误 raise抛出错误
断点调试程序
pyinstaller打包文件
9.文件与目录操作
open close
模式选择(r,w+,a之类)
file.write写入
os.path模块介绍
10.爬虫
urllib
request处理
header调用
urlpopen使用
read+decode
11.socket套接字
socket定义(ip+端口)
原理(服务器、客户端)
socket常用函数使用
与队友进行socket通话实验
浙公网安备 33010602011771号