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》游戏(如图)

屏幕截图 2026-05-19 220019

第一步:搭建游戏基础窗口

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

image

运行结果

image

一个黑色窗口,由于没有填充任何颜色,默认黑色窗口。

第二步:绘制游戏基本元素

创建加载方法,导入背景图和地面图片(一定要加上.convert()否则每一次格式转换会消耗大量内存)
游戏循环中调用flip方法,否则屏幕会根本不刷新(研究了好久,才发现要调用这玩意)

image

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

运行结果:

image

背景图成功铺展开来。

第三步:绘制游戏其他元素

整理代码,把背景图绘制放到一个专门的create_sprites函数中(严格来说,这些背景图不是精灵,后面改了过来)
用同样的方法绘制地面(tile)和小鸟(bird)

image

运行结果:

image

第四步:整理代码,让游戏动起来

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

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

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

Background.py整理后的代码
image

Bird.py整理后的代码
image

一目了然,简洁美观,而且体现了python的封装性、多态性,暂时还没有继承

然后让游戏动起来,逻辑是:因为运动具有相对性,鸟往前面飞就相当于鸟不动,背景向后面移动。于是对每个background,tile对象创建move方法和self.speed属性。
另外,当背景图向后推倒屏幕左边之外时,将其循环利用,移到最右边的背景图的右边

Main函数也要相应地import每个文件,并将每个对象实例化,然后逐一调用load init move等方法
image

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

image

第五步:实现小鸟的基本运动

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

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

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

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

另外把所有监听事件移入到check_event方法,然后在主循环中调用,这样可以保持游戏主循环简洁。

运行结果:现在小鸟可以下坠翻滚了,而且点击鼠标还可以跳跃

image

第六步:生成管道

添加管道(pipe)类,load,move之类的函数复用刚才的模块,重点讲一下管道的生成方法
设置set_pair函数,用于生成一对管道的信息(上一个,下一个,小鸟从中间缝隙(gap)飞过),其中使用到random模块,保证gap的高度每次不同,参数范围进行限制,防止gap生成到屏幕外边去了,upper_y是上管道的top值,lower_y是下管道的top值。

然后,在arrange方法中,调用四次set_pair()传入x参数,这样,每次游戏开始时,会预先生成管道在屏幕右侧。同样的,每次管道出了屏幕,移到最右边循环利用,防止管道反复销毁、重建
image

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

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

image

第七步:碰撞检测

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

image

image

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

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

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

第八步:游戏逻辑完整实现

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

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

image

第九步:

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

添加开始界面的ui
image

至此,一个基本的flappybird游戏复刻已经完成。

目标二:游戏玩法创新

添加几个游戏功能(过程复杂,代码过长,代码都在仓库里面)。

1. 长按鼠标,小鸟可以进行后空翻操作,并且蓄力一次,然后按下空格键,加速(如图)

image

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

image

2. 添加管道种类

(1) 上下移动型

(2) 左右跟随型

(3) 一面墙(需要飞到屏幕上面之外),增加难度

(4) 特别短的类型

根据难度选择不同,难度越高,加强版管道种类会增加。(如果游戏太难了,按下shift切入到英文输入法之后,按下o键可以开启无敌模式)

3. 添加各类游戏场景和特色

通过点击标签切换难度和场景

(1)夜晚(游戏重力、速度、帧率,会随机变化,而且可以触发进入恐怖隐藏关卡)

image

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

imageimage

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

image

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

image

(6)隐藏恐怖关卡,需要在夜晚模式掉进特定管道来触发进入。(也可以在英文输入法下,按下h键后,再按下k键自鲨后进入)

至此,完整的、玩法多样的、难度巨难、刺激版的游戏开发完成。

目标三:对游戏进行打包

对主文件进行打包
image
注意,必须修改游戏对依赖资源的路径修改,需要加入一下代码

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)

运行结果:
打包成功。
image
在根目录下dist文件夹内找到相应exe文件
image
使用tortoisegit托管代码到码云:
image
image

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

1.开通云服务器

屏幕截图 2026-05-25 194345

2.开放必要端口

image

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

屏幕截图 2026-05-25 194248

4.游戏通过网页进入

屏幕截图 2026-05-25 194146

四、遇到的问题:

  1. 问题:一旦窗口放大,游戏会变得异常卡顿
    解决方式:将每个pygame.load后面加上.convert(),可以防止每次转换格式消耗大量资源
  2. 问题:游戏每间隔一段时间就会卡一下
    解决方式:import进来gc模块,定期地清除程序缓存,防止垃圾堆积过多
  3. 问题:游戏碰撞检测函数失灵
    解决方式:Rect属性需要手动调整位置,用了graph模块画出碰撞箱后,要保证元素和碰撞箱位置一样
  4. 问题:游戏进行速度受到帧率的影响,运行时时快时慢,影响游戏体验
    解决方式:引入delta,每次位移时,加上 *delta/16.66(delta为每两帧之间的时间差,16.66每一帧的时间)
  5. 问题:背景图片覆盖了其他所有元素
    解决方式:Pygame无法设定depth层级,需要精准设置不同元素的绘制顺序,调整blit的调用顺序,保证优先级高的元素在上面。
  6. 问题:解决方式游戏运行到后面卡死
    精灵组的元素只是remove出精灵组,但是没有kill销毁,导致大量sprite积压,占用内存。所以要kill不要remove。
  7. 问题:游戏资源无法加载
    解决方式:在调用不同的资源时,要切换不同的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通话实验

课程感想

选这门课以前,我本来以为我有python基础可以不用仔细听,开课之后,我才发现有很多东西我之前没有触及到的。王老师并没有一开始上来就讲语法,而是先带着我们了解了python的地位、特性和适用场景,为我们建立目标思维,让我们先搞懂是学python这门课的意义。王老师不仅讲授python知识,还附加赠送了很多工程类其他必要的知识,比如git使用,创建gitee账号、托管代码等,为我们以后的团队合作奠定了基础。另外,让我颇感意外的是这门课需要打卡英语单词,但仔细仔细想想觉得很有道理,有了对单词精确的把握,才能更高效的读懂别人的代码,创建变量时也不需要抓破脑袋想用什么单词。最有意思的还是每堂课的签到画线,十分刺激。

然后正式将语法之后,我发现了python并不是我想的那样只是一个c语言简化版本,因为python是实现了c语言难以实现的很多功能,比如调用socket套接字来实现点对点的通信,让我第一次认识到了程序不一定局限在自己的电脑里,也可以与其他电脑相互连接。除此之外,还有pip的使用方法和pyinstaller的打包教程,刚好帮助了我在本次实验中把游戏代码进行了打包,让我体验到了Python相关各种工具的强大性。最让我感兴趣的就是网络爬虫,在选这门课以前,爬虫对于我来说就是一门高级的黑客技术,但是学习之后才发现,我发现这门技术确实高级,等这门课程结束之后在去了解把。

然后是王老师的讲课风格,听了之后才理解了学长们为何都极力推荐选择python课(给分高其中原因之一),因为课程内容全是干货,特别细致,尤其是序列部分字典、列表、集合、元组的详细区别,是我以前不知道的,我以前就把这些东西混着使用,上了课才终于分明白了。

总之,这门课给我带来了很多收获

posted @ 2026-05-20 22:01  20252223  阅读(69)  评论(0)    收藏  举报