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从入门到实战》

...

posted @ 2025-06-02 14:35  。。。shshhshs  阅读(60)  评论(0)    收藏  举报