20242225 《Python程序设计》实验四报告
20242225 《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2422
姓名:
学号:20242225
实验教师:王志强
实验日期:2025年5月14日
必修/选修: 公选课
一、实验名称
桌面宠物程序设计和实现
二、实验分析(需求与可行性)
核心功能:
一个置顶的窗口,可以显示宠物动画,可以用鼠标拖拽移动宠物,循环播放图片,连起来形成动作动画(如行走、挥手、躲猫猫)。通过配置文件动态加载不同宠物的动作序列和图片资源。
三、实验设计(架构与流程)
1.大概描述
使用PyQt5创建透明窗口、托盘图标,通过鼠标的行为(比如按下/移动/释放)实现一些互动;实现宠物动画的显示(比如随机动作、逐帧播放)、窗口位置随机生成
然后通过cfg.py管理宠物动作配置和资源路径
2.程序启动流程
初始化,加载配置文件cfg.py,验证PET_ACTIONS_MAP和ROOT_DIR是否存在
随机选择宠物并加载对应图片资源,初始化托盘图标和定时器
显示窗口并随机定位到桌面合适位置
3.动画播放流程
启动定时器,随机选择一个动作序列,通过runFrame逐帧更新显示图片,到最后一张图片时标记动作完成并等待下次触发播放
四、实现过程(代码与调试)
1.先引入一些必要的系统模块和 PyQt5 库
import os
#用于文件和目录的操作
import cfg
#自定义的配置模块,存储宠物相关配置
import sys
#提供对Python解释器的访问,用来退出程序
import random
#用来生成随机数和随机选择
from PyQt5.QtGui import *
#导入PyQt5的GUI模块
from PyQt5.QtCore import *
#导入PyQt5的核心模块
from PyQt5.QtWidgets import *
#导入PyQt5的窗口部件模块
from PyQt5 import QtWidgets, QtGui
#再次导入PyQt5的相关模块
2.调试部分
这部分代码用于调试,是因为程序没有成功运行后加的
try:
print("cfg.PET_ACTIONS_MAP:", cfg.PET_ACTIONS_MAP)
print("cfg.ROOT_DIR:", cfg.ROOT_DIR)
except AttributeError:
print("没加载配置文件")
3.设置桌面宠物的窗口
使窗口没有标题栏、边框和窗口控制按钮(最小化、最大化、关闭)
让窗口始终置顶,显示在其他窗口的上方
将窗口作为子窗口处理,不显示在任务栏中
禁用自动填充背景,防止窗口使用默认背景色填充
设置窗口背景为透明
class DesktopPet(QWidget):
def __init__(self, parent=None, **kwargs):
super(DesktopPet, self).__init__(parent)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.SubWindow)
self.setAutoFillBackground(False)
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.repaint()
4.加载宠物动画资源和托盘图标
创建一个包含 "退出" 选项的右键菜单,在系统托盘中显示图标,并关联右键菜单,可以点击托盘图标右键选择 "退出" 来关闭程序
self.pet_images, iconpath = self.randomLoadPetImages()
quit_action = QAction('退出', self, triggered=self.quit)
quit_action.setIcon(QIcon(iconpath))
self.tray_icon_menu = QMenu(self)
self.tray_icon_menu.addAction(quit_action)
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(QIcon(iconpath))
self.tray_icon.setContextMenu(self.tray_icon_menu)
self.tray_icon.show()
5.设置显示宠物图片的标签,拖拽,窗口大小,随机放置宠物并显示窗口。
创建图像控件并显示宠物的第一帧动画。
设置拖拽交互所需的状态变量。
调整窗口大小,随机出现在桌面某处,然后显示窗口
self.image = QLabel(self)
self.setImage(self.pet_images[0][0])
#是否跟随鼠标
self.is_follow_mouse = False
#初始设置为不跟随,宠物拖拽时避免鼠标直接跳到左上角
self.mouse_drag_pos = self.pos()
#记录偏移量什么的
self.resize(128, 128)
self.randomPosition()
self.show()
print("Window is shown.")
6.设置了一个定时器,每500毫秒触发一次随机动作,让宠物能够执行动作
self.is_running_action = False
self.action_images = []
self.action_pointer = 0
self.action_max_len = 0
# 每隔一段时间做个动作
self.timer = QTimer()
self.timer.timeout.connect(self.randomAct)
self.timer.start(500)
7.实现宠物的动画效果
randomAct随机选择一个动作序列,runFrame逐帧显示动作序列中的图片
按顺序显示选中动作的所有帧,形成连续动画
通过is_running_action防止动作冲突,确保一个动作完整执行后再开始下一个
def randomAct(self):
if not self.is_running_action:
self.is_running_action = True
self.action_images = random.choice(self.pet_images)
self.action_max_len = len(self.action_images)
self.action_pointer = 0
self.runFrame()
def runFrame(self):
if self.action_pointer == self.action_max_len:
self.is_running_action = False
self.action_pointer = 0
self.action_max_len = 0
self.setImage(self.action_images[self.action_pointer])
self.action_pointer += 1
8.加载宠物图像
setImage用于在标签上显示图片
randomLoadPetImages从配置中随机选择一个宠物,并加载其所有动作的图片,这样就可以有很多个宠物,但是我只有一个宠物素材,所以实际上没有实现。。
def setImage(self, image):
self.image.setPixmap(QPixmap.fromImage(image))
def randomLoadPetImages(self):
pet_name = random.choice(list(cfg.PET_ACTIONS_MAP.keys()))
actions = cfg.PET_ACTIONS_MAP[pet_name]
pet_images = []
for action in actions:
pet_images.append([self.loadImage(os.path.join(cfg.ROOT_DIR, pet_name, 'shime' + item + '.png')) for item in action])
iconpath = os.path.join(cfg.ROOT_DIR, pet_name, 'shime1.png')
return pet_images, iconpath
9.实现拖拽功能
按下左键:记录鼠标与窗口的相对位置,开始拖拽状态
移动鼠标:根据鼠标位置实时更新窗口位置
释放左键:结束拖拽状态
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.is_follow_mouse = True
self.mouse_drag_pos = event.globalPos() - self.pos()
event.accept()
self.setCursor(QCursor(Qt.OpenHandCursor))
def mouseMoveEvent(self, event):
if Qt.LeftButton and self.is_follow_mouse:
self.move(event.globalPos() - self.mouse_drag_pos)
event.accept()
def mouseReleaseEvent(self, event):
self.is_follow_mouse = False
self.setCursor(QCursor(Qt.ArrowCursor))
10.图像加载、随机位置生成和程序退出
def loadImage(self, imagepath):
image = QImage()
if not image.load(imagepath):
print(f"Failed to load image: {imagepath}")
return image
def randomPosition(self):
screen_geo = QDesktopWidget().screenGeometry()
pet_geo = self.geometry()
width = int((screen_geo.width() - pet_geo.width()) * random.random())
height = int((screen_geo.height() - pet_geo.height()) * random.random())
self.move(width, height)
def quit(self):
self.close()
sys.exit()
11.初始化和运行桌面宠物应用
启动 PyQt5 应用程序
创建并显示桌面宠物窗口
保持程序运行,处理用户交互和动画逻辑
提供错误处理机制,确保程序崩溃时能给出提示
if __name__ == '__main__':
try:
app = QApplication(sys.argv)
pet = DesktopPet()
sys.exit(app.exec_())
except Exception as e:
print(f"An error occurred: {e}")
except KeyboardInterrupt:
print("程序被手动中断。")
12.配置文件中的内容
ROOT_DIR = 'C:\\Users\\M\\Desktop\\chongwu\\pet'
ACTION_DISTRIBUTION = [['1', '2', '3'],
['4', '5', '6', '7', '8', '9', '10', '11'],
['12', '13', '14'],
['15', '16', '17'],
['18', '19'],
['20', '21'],
['22'],
['23', '24', '25'],
['26', '27', '28', '29'],
['30', '31', '32', '33'],
['34', '35', '36', '37'],
['38', '39', '40', '41'],
['42', '43', '44', '45', '46']]
PET_ACTIONS_MAP = {'pet': ACTION_DISTRIBUTION}
五.问题和解决方案
1.运行这个文件需要保证安装了PyQt5库,而最开始我电脑上没有安装这个库,所以程序没有成功运行
错误信息:Traceback (most recent call last):
File "C:\Users\M\Desktop\chongwu\DesktopPet.py", line 12, in
from PyQt5.QtGui import *
ModuleNotFoundError: No module named 'PyQt5'
解决方案:使用pip install PyQt5语句在终端安装了PyQt5库
但是安装了这个库之后仍然显示未安装,所以没有办法运行
排除安装不完整或者损坏的问题之后,发现是因为我之前安装了好几个python。。。而 pip 关联的是D:\python\python.exe这个Python3.13环境。但是我运行测试代码时,可能用的是其他Python环境,从而导致找不到已安装的PyQt5库。
所以换了一种命令:D:\python\python.exe C:\Users\M\Desktop\chongwu\DesktopPet.py
过程中顺带解决了width和height是float类型和self.move()不一致的问题
修改后:
width = int((screen_geo.width() - pet_geo.width()) * random.random())
height = int((screen_geo.height() - pet_geo.height()) * random.random())
2.运行程序之后发现没有显示任何图像和内容,所以添加了一些排查问题的代码
解决方案1:
try:
print("cfg.PET_ACTIONS_MAP:", cfg.PET_ACTIONS_MAP)
print("cfg.ROOT_DIR:", cfg.ROOT_DIR)
except AttributeError:
print("没加载配置文件")
用来检查cfg模块是否正确加载
解决方案2:
def loadImage(self, imagepath):
image = QImage()
if not image.load(imagepath):
print(f"Failed to load image: {imagepath}")
return image
用来检查图片资源是不是缺失
解决方案3:
def __init__(self, parent=None, **kwargs):
super(DesktopPet, self).__init__(parent)
self.setWindowFlags(Qt.FramelessWindowHint|Qt.WindowStaysOnTopHint|Qt.SubWindow)
self.setAutoFillBackground(False)
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.repaint()
self.show()
print("Window is shown.")
可能是窗口没有正常显示,试图排除问题
添加了上述方案之后显示了下面这一批乱码,根据提示信息可以看出来是因为图片没有加载出来
后来我发现是图片存放的位置有问题,然后我重新修改了图片位置,存放在pet的pet文件里之后,再尝试了几次,程序成功运行
上传到码云
六、感悟思考
做这个项目是因为感觉游戏,比如说贪吃蛇和扫雷什么的有很多人会做,然后其他的太难了又不会做,所以选了这个看起来应该简单一点然后又很可爱的项目()
虽然感觉功能挺少的(),比如说又什么对话互动的应该也可以加上吧,找了很多教程和网上的视频,感觉别人的功能很丰富然后动画也很流畅,但是我就只能找到这一点不多的图片素材了,所以说现实很骨感(),只能努力到这一步了QAQ
而且感觉使用起来不是很方便QAQ
但是能做出来已经很不错了QAQ
七、课程总结
1.总结
在本学期的课程中,志强老师不仅教会了我们很多基础知识(比如变量、数据类型、保留字到流程控制语句,后面还有字典爬虫什么的),还通过各种项目,将课堂上学的知识点转化为可运行的程序,让我们深刻理解了 “学以致用” 的意义,还提升了我的思维能力和实践能力。虽然课程的时间很长,每天都有三节课,而且我刚好周三全是课QAQ感觉很累但是又收获了很多东西。但是由于本人不是很聪明(?)虽然在课程刚开始的时候很好奇很感兴趣也还能跟上,但是在后面就有点听不懂了QAQ,写实验报告的时候也越来越困难(感到无力),相对于其他课程的作业也很多,但是最后做出来的时候会感到很兴奋。
还有就是感觉课程内容对于没有学过的同学来说会有一点吃力(比如我QAQ),但是对于有些很厉害的同学来说就很简单,只是给他们一段时间可以敲代码并且加学分这样子(?),其实可以分分层次()
然后就是感觉志强老师在努力试图让这个课程变得很有趣,用很多猥琐的方式(划掉),比如说签到的时候用诡异的图案(),输出的时候用亿点搞笑的话代替等等()其实有时候确实能够吸引同学们抬起头(感觉老师已经很努力了(加油!!!))
2.建议
可以引入真实的实践案例,讲解如何综合运用多种知识
可以增加互动环节,比如说课堂中随机出现小型代码挑战(如用 10 行代码实现随机数生成器),顺便加加分()
可以提供个性化指导,针对基础薄弱学生,增加课后答疑或分层次作业;对学有余力的学生,我也不知道,毕竟我没有余力()
对于工具与资源补充,比如可以详细讲讲PyCharm的什么隐藏功能,提升学习效率;
可以建立一个共享资源库,整理优质学习资料,方便学生课后复习或者是大家共享资源
可以组建一个什么小组,以小组为单位共同编写代码,互相审查代码,学习不同的设计思路,感觉更有效率
(其实感觉讲得有点快,可能是因为课时比较少吧。。。
参考资料
《零基础学Python》(2018 明日科技编著)
《Python从入门到实战》
...