第八次实训工作报告

设计题目

华南理工大学:植此青绿

本周工作小结:

本周我主要进行了以下工作:

  1. 实现一个名为的音乐播放器,该播放器具有导入音乐文件、播放、暂停、显示歌词等功能。
  2. 实现数独游戏的制作,并添加为本游戏的一个副游戏,增加游戏的可玩性(不过是作者的任性)
  3. 对以前的页面和代码进行了美化和逻辑的更新,使得整体页面更加简洁。(毕竟还是要做正事的)
  4. 将程序进行打包成.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 {
//- QMediaPlayer *player // 媒体播放器对象
//- QAudioOutput *audioOutput // 音频输出对象
//- bool isPlaying // 标志,指示音乐是否正在播放
//- QLabel *imageLabel // 用于显示图像的标签
//- QTextEdit *lyricsTextEdit // 用于显示歌词的文本编辑器
//- QPushButton *importButton // 导入音乐文件的按钮
//- QPushButton *playButton // 播放或暂停音乐的按钮
//- QPushButton *showLyricsButton // 显示歌词的按钮
//- QSlider *positionSlider // 指示和控制音乐位置的滑块
//- QLabel *durationLabel // 显示当前位置和总时长的标签
//- QListWidget *playlistWidget // 显示播放列表的窗口部件
//- QPixmap defaultAlbumArt // 默认专辑封面

// 构造函数
+ 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"; // 默认歌手为Unknown
QString album = "Unknown 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

数独游戏实现(由于不是游戏目的,纯粹是设计者的自我想法,因此不会过多篇幅进行代码解释,只是描述一下大体的框架)

  1. 界面布局:使用了Qt的布局管理器来创建一个主布局,包含数独网格、计时器标签和按钮布局。按钮布局包括清空、帮助、开始游戏、重新开始、提交和暂停按钮。
  2. 数独地图创建:通过 createGrid() 函数创建了一个9x9的数独网格,每个格子使用QLabel显示数字,初始地图中的数字用橙色背景标识。
  3. 按钮功能:各个按钮通过信号槽连接实现不同的功能,包括清空、显示帮助、开始游戏、重新开始、提交和暂停游戏。
  4. 游戏逻辑:实现了填充数字格子、检查胜利条件、添加阴影效果、检查冲突等游戏逻辑功能。
  5. 事件过滤器:使用事件过滤器来处理鼠标点击和键盘输入事件,实现在格子中填入数字的功能。
  6. 计时器:使用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
QLabel *clickedLabel = qobject_cast<QLabel*>(obj);
if (clickedLabel)
{
int row = -1, col = -1;
// 在sudokuGrid中查找点击的QLabel的位置
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)
{
// 如果找到了QLabel的位置
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);
// 检查键盘按下的是否为数字键(从 Qt::Key_0 到 Qt::Key_9)
if (keyEvent->key() >= Qt::Key_0 && keyEvent->key() <= Qt::Key_9)
{
// 如果点击的是 QLabel,则将按下的数字填入标签中
QLabel *focusedLabel = qobject_cast<QLabel*>(this->focusWidget());
if (focusedLabel)
{
// 获取当前标签的行和列
int row = -1, col = -1;
// 在sudokuGrid中查找点击的QLabel的位置
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)
{
// 如果找到了QLabel的位置
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);
}
  1. 捕获鼠标点击事件:
    • 首先,函数通过if (event->type() == QEvent::MouseButtonPress)语句判断事件的类型是否为鼠标按下事件。
    • 如果是鼠标按下事件,则进一步检查是否是鼠标左键点击,即if (mouseEvent->button() == Qt::LeftButton)
    • 如果是鼠标左键点击,则判断点击的是否是一个QLabel部件,即QLabel *clickedLabel = qobject_cast<QLabel*>(obj)
    • 如果点击的确是一个QLabel,则通过循环遍历sudokuGrid二维数组找到对应的行和列索引,确定用户点击的是哪个数字格子。
    • 如果点击的数字格子是空的或者没有初始值,那么允许用户填入数字。此时会调用clearShadowEffect()函数清除之前可能存在的阴影效果,并调用addShadowEffect()函数添加阴影效果到当前点击的格子,并设置该格子获取焦点,以便用户直接在键盘上输入数字。
  2. 捕获键盘按键事件:
    • 其次,函数通过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

对主页面的布局进行改进:都加上了图标,并且由起初的水平布局改为垂直布局。

image-20240521162540535image-20240521162411721

对游戏游玩关卡页面进行美化:image-20240521162546025

打包

分别安装两个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文件了。

寄语

第一款完成的游戏,纪念意义拉满了,感谢所有提供帮助的同学和提供知识资源的博主。

仰天大笑出门去,我辈岂是蓬蒿人。希望以后能够做出更好的游戏,并不忘完善这个游戏。