第八次实训工作报告
设计题目
华南理工大学:植此青绿
本周工作小结:
本周我主要进行了以下工作:
- 实现一个名为的音乐播放器,该播放器具有导入音乐文件、播放、暂停、显示歌词等功能。
- 实现数独游戏的制作,并添加为本游戏的一个副游戏,增加游戏的可玩性(不过是作者的任性)
- 对以前的页面和代码进行了美化和逻辑的更新,使得整体页面更加简洁。(毕竟还是要做正事的)
- 将程序进行打包成.exe安装包。
环境与工具:
- 开发环境:Qt Creator 13.0.0
- 操作系统:Windows 11
前置工作
添加音乐多媒体模块,上网查询音乐播放器制作。
QT的CmakeList文件添加模块,主要包括联网模块和多媒体模块。
1 2 3 4 5 6 7 8 9 10 11 12
| find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Multimedia)
find_package(Qt6 COMPONENTS Multimedia REQUIRED)
find_package(Qt6 COMPONENTS Network REQUIRED)
target_link_libraries(untitled9 PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
target_link_libraries(untitled9 PRIVATE Qt${QT_VERSION_MAJOR}::Network)
target_link_libraries(untitled9 PRIVATE Qt${QT_VERSION_MAJOR}::Multimedia)
|
后期预备开启功能:
暂无
程序功能说明
音乐播放器实现
功能说明:
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
| class MusicPlayer {
+ MusicPlayer(QWidget *parent = nullptr) + ~MusicPlayer() + void importFile() + QListWidgetItem* createPlaylistItem(const QString &filePath, bool isLocalFile) + void displayMusicInfo(const QString &filePath) + void play() + void showLyrics() + void updatePosition(qint64 position) + void updateDuration(qint64 duration) + void setPosition(int position) + void setButtonStyle(QPushButton *button) + void setButtonStyle(QPushButton *button, const QString &buttonClass) + void initMusic() + bool eventFilter(QObject *obj, QEvent *event) + void on_ReturnButton_clicked() }
|
- 导入音乐文件:
用户可以通过点击“导入歌曲”按钮选择本地的音乐文件进行导入,支持格式包括
mp3、wav 和 ogg。
- 播放/暂停:
用户可以点击“播放”按钮开始播放音乐,再次点击则暂停播放。
- 显示歌词:
用户可以点击“显示歌词”按钮来显示音乐的歌词。
- 歌曲列表:
可以显示已导入的音乐文件,并支持双击播放功能。
- 返回按钮:
提供了返回功能,点击后返回到主页面。
实现细节
- 使用 Qt 的界面布局管理器 QVBoxLayout 和 QHBoxLayout
进行界面布局。
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
| imageLabel = new QLabel(this); lyricsTextEdit = new QTextEdit(this);
QHBoxLayout *controlsLayout = new QHBoxLayout(); importButton = new QPushButton(this); playButton = new QPushButton(this); showLyricsButton = new QPushButton(this);
QPushButton *returnButton = new QPushButton("返回", this); positionSlider = new QSlider(Qt::Horizontal, this); durationLabel = new QLabel("00:00 / 00:00", this);
controlsLayout->addWidget(importButton); controlsLayout->addWidget(playButton); controlsLayout->addWidget(showLyricsButton); controlsLayout->addWidget(returnButton); controlsLayout->addWidget(positionSlider); controlsLayout->addWidget(durationLabel);
QVBoxLayout *mainLayout = new QVBoxLayout(this); QHBoxLayout *centralLayout = new QHBoxLayout(); centralLayout->addLayout(menuLayout); centralLayout->addWidget(imageLabel); centralLayout->addWidget(lyricsTextEdit); mainLayout->addLayout(centralLayout); mainLayout->addLayout(controlsLayout); mainLayout->addWidget(playlistWidget);
|
- 通过 QMediaPlayer 和 QAudioOutput 类实现音乐的播放和控制。
1 2 3 4 5 6 7 8 9 10 11
| void MusicPlayer::play() { if (isPlaying) { player->pause(); } else { player->play(); } isPlaying = !isPlaying; }
|
- 利用 QListWidget 显示音乐列表,并通过双击列表项来播放音乐。
具体参见下面的音乐导入理解原理。
- 使用 QFileDialog
实现文件选择对话框,允许用户选择要导入的音乐文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void MusicPlayer::importFile() { QStringList files = QFileDialog::getOpenFileNames(this, "导入音乐文件", "", "音频文件 (*.mp3 *.wav *.ogg)"); int index = (playlistWidget->count()+1 ); for (const QString &file : files) { if (!file.isEmpty()) { QString songName = QFileInfo(file).fileName(); QString artist = "Unknown"; QString album = "Unknown Album"; QListWidgetItem *item = createPlaylistItem(file, true); QFont font = item->font(); font.setBold(true); item->setFont(font); QString songInfo = QString::number(index++) + ". " + songName + " - " + artist + " (" + album + ")"; item->setText(songInfo); QListWidgetItem *spacer = new QListWidgetItem; spacer->setSizeHint(QSize(10, 40)); playlistWidget->addItem(item); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| bool MusicPlayer::eventFilter(QObject *obj, QEvent *event) { if (obj == imageLabel && event->type() == QEvent::MouseButtonPress) { QString filePath = QFileDialog::getOpenFileName(this, "选择图片", "", "图像文件 (*.png *.jpg *.jpeg)"); if (!filePath.isEmpty()) { QPixmap newImage(filePath); imageLabel->setPixmap(newImage); } return true; } return QWidget::eventFilter(obj, event); }
|
具体功能展示:
整体页面一览:
image-20240523122527845
导入歌曲打开文件资源管理器一览
image-20240523122540210
导入雅俗共赏.mp3后
image-20240523122549283
数独游戏实现(由于不是游戏目的,纯粹是设计者的自我想法,因此不会过多篇幅进行代码解释,只是描述一下大体的框架)
- 界面布局:使用了Qt的布局管理器来创建一个主布局,包含数独网格、计时器标签和按钮布局。按钮布局包括清空、帮助、开始游戏、重新开始、提交和暂停按钮。
- 数独地图创建:通过
createGrid()
函数创建了一个9x9的数独网格,每个格子使用QLabel显示数字,初始地图中的数字用橙色背景标识。
- 按钮功能:各个按钮通过信号槽连接实现不同的功能,包括清空、显示帮助、开始游戏、重新开始、提交和暂停游戏。
- 游戏逻辑:实现了填充数字格子、检查胜利条件、添加阴影效果、检查冲突等游戏逻辑功能。
- 事件过滤器:使用事件过滤器来处理鼠标点击和键盘输入事件,实现在格子中填入数字的功能。
- 计时器:使用Qt的QTimer类实现了计时器功能,每秒更新一次计时器标签。
主要重温一下事件过滤器:
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| bool SudokuGame::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); if (mouseEvent->button() == Qt::LeftButton) { QLabel *clickedLabel = qobject_cast<QLabel*>(obj); if (clickedLabel) { int row = -1, col = -1; for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { if (sudokuGrid[i][j] == clickedLabel) { row = i; col = j; break; } } if (row != -1) break; } if (row != -1 && col != -1) { if (initialMap[row][col] == 0 || clickedLabel->text().isEmpty()) { clearShadowEffect(); addShadowEffect(row, col); clickedLabel->setFocus();
} } } } } else if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); if (keyEvent->key() >= Qt::Key_0 && keyEvent->key() <= Qt::Key_9) { QLabel *focusedLabel = qobject_cast<QLabel*>(this->focusWidget()); if (focusedLabel) { int row = -1, col = -1; for (int i = 0; i < 9; ++i) { for (int j = 0; j < 9; ++j) { if (sudokuGrid[i][j] == focusedLabel) { row = i; col = j; break; } } if (row != -1) break; } if (row != -1 && col != -1) { if (initialMap[row][col] == 0 || focusedLabel->text().isEmpty()) { QFont font("Arial", 24); sudokuGrid[row][col]->setText(QString::number(keyEvent->key() - Qt::Key_0)); sudokuGrid[row][col]->setFont(font); } } } } } return QWidget::eventFilter(obj, event); }
|
- 捕获鼠标点击事件:
- 首先,函数通过
if (event->type() == QEvent::MouseButtonPress)
语句判断事件的类型是否为鼠标按下事件。
- 如果是鼠标按下事件,则进一步检查是否是鼠标左键点击,即
if (mouseEvent->button() == Qt::LeftButton)
。
- 如果是鼠标左键点击,则判断点击的是否是一个
QLabel
部件,即QLabel *clickedLabel = qobject_cast<QLabel*>(obj)
。
- 如果点击的确是一个
QLabel
,则通过循环遍历sudokuGrid
二维数组找到对应的行和列索引,确定用户点击的是哪个数字格子。
- 如果点击的数字格子是空的或者没有初始值,那么允许用户填入数字。此时会调用
clearShadowEffect()
函数清除之前可能存在的阴影效果,并调用addShadowEffect()
函数添加阴影效果到当前点击的格子,并设置该格子获取焦点,以便用户直接在键盘上输入数字。
- 捕获键盘按键事件:
- 其次,函数通过
else if (event->type() == QEvent::KeyPress)
语句判断事件的类型是否为键盘按键事件。
- 如果是键盘按键事件,则进一步检查按下的是否是数字键(从 Qt::Key_0 到
Qt::Key_9)。
- 如果是数字键,则判断当前焦点是否在一个
QLabel
部件上,即QLabel *focusedLabel = qobject_cast<QLabel*>(this->focusWidget())
。
- 如果焦点在一个
QLabel
上,则通过循环遍历sudokuGrid
二维数组找到对应的行和列索引,确定用户正在编辑的是哪个数字格子。
- 如果编辑的数字格子是空的或者没有初始值,那么将按下的数字填入该格子中,并设置字体大小。
最后,函数返回QWidget::eventFilter(obj, event)
,以便让其他事件过滤器或父类的事件处理函数继续处理该事件。
由于其他实现并未有太多难度,就不赘述。
页面展示:
image-20240521162141534
整体色调还算可以,也算了却了一个心愿。
对页面的美化和逻辑的优化部分
修改了失败界面和成功页面的展示
image-20240521162304843
image-20240521162348201
对主页面的布局进行改进:都加上了图标,并且由起初的水平布局改为垂直布局。


对游戏游玩关卡页面进行美化:
打包
分别安装两个NSIS 2.46和HM NIS Edit 2.03工具。
通过一系列工作最终完成打包。
期间面临的困难:编译后的.exe文件运行时缺少一些.dll文件的解决办法
【QT】编译后的.exe文件运行时缺少一些.dll文件的解决办法【超详细教程,新手必备】_qt
debug 生成的exe 打开时缺少dll-CSDN博客
通过该博客解决。
步骤是:
1.
打开QT的cmd窗口:找到Qt–>找到文件所在路径–>找到qt的cmd文件。
2、在桌面创建一个调试文件夹:Debug。在窗口用命令进入Debug文件夹:cd C:\Users\Administrator\Desktop\Debug
3、
在命令窗口进入到Debug(之前自己在桌面创建的文件夹)文件夹后,把缺少.dll文件的.exe文件放到Debug文件夹中。
4、在命令窗口中输入:dir
查看当前文件夹下的所有文件,找到缺失.dll文件的.exe文件。
5、在命令窗口中输入:windeployqt +.exe
文件,如图:
6、
待系统加载完所有的文件(显示完整路径)后,如图:
7、在创建的文件夹(Debug)中找到.exe文件,点击运行,此时不再提醒确实文件了。可以正常运行.exe文件了。
寄语
第一款完成的游戏,纪念意义拉满了,感谢所有提供帮助的同学和提供知识资源的博主。
仰天大笑出门去,我辈岂是蓬蒿人。希望以后能够做出更好的游戏,并不忘完善这个游戏。