前2篇文章基本上完成了游戏的主体
这一部分主要讲解2个问题
1、音效部分
2、xml文件读取配置文件
一、音效问题
Qt5移除了phonon模块,改为使用QMultiMedia,需要使用此模块,需要在pro文件中添加Qt += multimedia
这里也就使用了QMultiMedia中的高层实现QMediaPlayer,此类可以直接关联播放mp3,wav格式的音乐,这两个格式也是我们游戏的主要音乐格式。
先看下如何使用QMediaPlayer
player = new QMediaPlayer; player->setMedia(QUrl::fromLocalFile("/Users/me/Music/coolsong.mp3")); player->setVolume(50); player->play();这里其实主要就是要关联一个QMediaContent,QMediaContent可以又QUrl购置而来,这里就可以用上述方法引用本地音乐文件,并播放。
查看我们的声明和定义:
enum SoundType { TowerPlaceSound, // 放塔时的声音 LifeLoseSound, // 基地费血时的声音 LaserShootSound, // 打中敌人时的生意 EnemyDestorySound // 敌人升天时的声音 }; class AudioPlayer : public QObject { public: explicit AudioPlayer(QObject *parent = 0); void startBGM(); void playSound(SoundType soundType); private: QMediaPlayer *m_backgroundMusic; // 只用来播放背景音乐 };
// 为了解决mac下声音无法输出,总之发现使用绝对路径可以完成目标,蛋疼,因此以此种方式暂时处理 static const QString s_curDir = QDir::currentPath() + "/"; AudioPlayer::AudioPlayer(QObject *parent) : QObject(parent) , m_backgroundMusic(NULL) { // 创建一直播放的背景音乐 QUrl backgroundMusicUrl = QUrl::fromLocalFile(s_curDir + "music/8bitDungeonLevel.mp3"); if (QFile::exists(backgroundMusicUrl.toLocalFile())) { m_backgroundMusic = new QMediaPlayer(this); QMediaPlaylist *backgroundMusicList = new QMediaPlaylist(); QMediaContent media(backgroundMusicUrl); backgroundMusicList->addMedia(media); backgroundMusicList->setCurrentIndex(0); // 设置背景音乐循环播放 backgroundMusicList->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop); m_backgroundMusic->setPlaylist(backgroundMusicList); } } void AudioPlayer::startBGM() { if (m_backgroundMusic) m_backgroundMusic->play(); } void AudioPlayer::playSound(SoundType soundType) { static const QUrl mediasUrls[] = { QUrl::fromLocalFile(s_curDir + "music/tower_place.wav"), QUrl::fromLocalFile(s_curDir + "music/life_lose.wav"), QUrl::fromLocalFile(s_curDir + "music/laser_shoot.wav"), QUrl::fromLocalFile(s_curDir + "music/enemy_destroy.wav") }; static QMediaPlayer player; if (QFile::exists(mediasUrls[soundType].toLocalFile())) { player.setMedia(mediasUrls[soundType]); player.play(); } }
用QMediaPlayer关联一个播放链表,里面循环播放背景音乐。
在MainWindow中如下引用就可以
m_audioPlayer->playSound(LifeLoseSound);其他地方也类似使用,即可,具体的代码,见下面的链接就好了,嘿嘿
二、读取XML配置文件
Qt提供多种方式读取XML文件,其中QXmlStreamReader的方法已经由QtXml移入QtCore,这个可见一般啦,我是自然采用这个啦,通过查看Qt的demo样例,有如下小封装。
在看声明和实现前,先看下我们的配置文件啥样
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <array> <dict> <key>x</key> <integer>65</integer> <key>y</key> <integer>220</integer> </dict> <dict> <key>x</key> <integer>155</integer> <key>y</key> <integer>220</integer> </dict> <dict> <key>x</key> <integer>245</integer> <key>y</key> <integer>220</integer> </dict> <dict> <key>x</key> <integer>335</integer> <key>y</key> <integer>220</integer> </dict> <dict> <key>x</key> <integer>100</integer> <key>y</key> <integer>125</integer> </dict> <dict> <key>x</key> <integer>195</integer> <key>y</key> <integer>125</integer> </dict> <dict> <key>x</key> <integer>280</integer> <key>y</key> <integer>125</integer> </dict> <dict> <key>x</key> <integer>370</integer> <key>y</key> <integer>125</integer> </dict> <dict> <key>x</key> <integer>80</integer> <key>y</key> <integer>35</integer> </dict> <dict> <key>x</key> <integer>170</integer> <key>y</key> <integer>35</integer> </dict> <dict> <key>x</key> <integer>260</integer> <key>y</key> <integer>35</integer> </dict> <dict> <key>x</key> <integer>350</integer> <key>y</key> <integer>35</integer> </dict> </array> </plist>这里是TowerPosition的xml配置信息
再看下Waves的xml配置信息
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <array> <array> <dict> <key>data</key> <string></string> <key>spawnTime</key> <integer>1000</integer> </dict> <dict> <key>data</key> <string></string> <key>spawnTime</key> <integer>2000</integer> </dict> <dict> <key>data</key> <string></string> <key>spawnTime</key> <integer>3000</integer> </dict> </array> </array> </plist>这里只取了波数中的一部分
分下一下:
TowerPosition很简单,x和y就是坐标
Waves中实际有用的只有spwanTime,用来确定每个敌人的出场时间
而Plist的xml的格式有这么几个标签需要处理
array-------dict----------------key
|-------array |-------integer
|-------string
|-------real
也就是一个数组下可以管理数组或字典映射
而一个字典映射由一对键值对组成,即key-(integer/string/real)这里实际有用的就是integer
对应Qt中的数据结构存储就是如下
array------QList<QVariant>
dict--------QMap<QString, QVariant>
integer----int
这里先看下MainWindow中如何使用
void MainWindow::loadTowerPositions() { QFile file(":/config/TowersPosition.plist"); if (!file.open(QFile::ReadOnly | QFile::Text)) { QMessageBox::warning(this, "TowerDefense", "Cannot Open TowersPosition.plist"); return; } PListReader reader; reader.read(&file); QList<QVariant> data = reader.data(); for (int i = 0; i < data.size(); ++i) { QMap<QString, QVariant> dict = data[i].toMap(); int x = dict.value("x").toInt(); int y = dict.value("y").toInt(); m_towerPositionsList.push_back(QPoint(x, y)); } file.close(); }
再看下如何读取波数,针对波数信息,采用先预读一次,存储起来,然后每次直接读取缓存中的数据就可以了
void MainWindow::preLoadWavesInfo() { QFile file(":/config/Waves.plist"); if (!file.open(QFile::ReadOnly | QFile::Text)) { QMessageBox::warning(this, "TowerDefense", "Cannot Open TowersPosition.plist"); return; } PListReader reader; reader.read(&file); // 获取波数信息 m_wavesInfo = reader.data(); file.close(); } bool MainWindow::loadWave() { if (m_waves >= m_wavesInfo.size()) return false; WayPoint *startWayPoint = m_wayPointsList.back(); QList<QVariant> curWavesInfo = m_wavesInfo[m_waves].toList(); for (int i = 0; i < curWavesInfo.size(); ++i) { QMap<QString, QVariant> dict = curWavesInfo[i].toMap(); int spawnTime = dict.value("spawnTime").toInt(); Enemy *enemy = new Enemy(startWayPoint, this); m_enemyList.push_back(enemy); QTimer::singleShot(spawnTime, enemy, SLOT(doActivate())); } return true; }这里知道用法了,赶快看下PListReader的声明和实现吧!
class PListReader { public: PListReader(); bool read(QIODevice *device); const QList<QVariant> data() const; QString errorString() const; private: void readPList(); void readArray(QList<QVariant> &array); void readDict(QList<QVariant> &array); void readKey(QMap<QString, QVariant> &dict); private: QXmlStreamReader m_xmlReader; QList<QVariant> m_data; };声明很简单,将读取的数据最终保存到m_data,通过data()函数拿到
PListReader::PListReader() { } bool PListReader::read(QIODevice *device) { m_xmlReader.setDevice(device); if (m_xmlReader.readNextStartElement()) { if (m_xmlReader.name() == "plist" && m_xmlReader.attributes().value("version") == "1.0") readPList(); else m_xmlReader.raiseError("The file is not an PList version 1.0 file."); } return m_xmlReader.error(); } const QList<QVariant> PListReader::data() const { return m_data; } QString PListReader::errorString() const { return QString("%1\nLine %2, column %3") .arg(m_xmlReader.errorString()) .arg(m_xmlReader.lineNumber()) .arg(m_xmlReader.columnNumber()); } void PListReader::readPList() { Q_ASSERT(m_xmlReader.isStartElement() && m_xmlReader.name() == "plist"); while (m_xmlReader.readNextStartElement()) { if (m_xmlReader.name() == "array") readArray(m_data); else if (m_xmlReader.name() == "dict") readDict(m_data); else m_xmlReader.skipCurrentElement(); } } void PListReader::readArray(QList<QVariant> &array) { Q_ASSERT(m_xmlReader.isStartElement() && m_xmlReader.name() == "array"); while (m_xmlReader.readNextStartElement()) { if (m_xmlReader.name() == "array") { QList<QVariant> subArray; readArray(subArray); array.push_back(subArray); } else if (m_xmlReader.name() == "dict") { readDict(array); } else { m_xmlReader.skipCurrentElement(); } } } void PListReader::readDict(QList<QVariant> &array) { Q_ASSERT(m_xmlReader.isStartElement() && m_xmlReader.name() == "dict"); QMap<QString, QVariant> dict; while (m_xmlReader.readNextStartElement()) { // 这里只处理key,在readKey中,一次默认读取一对键值 if (m_xmlReader.name() == "key") readKey(dict); else m_xmlReader.skipCurrentElement(); } array.push_back(dict); } void PListReader::readKey(QMap<QString, QVariant> &dict) { Q_ASSERT(m_xmlReader.isStartElement() && m_xmlReader.name() == "key"); // 这里一次读取一个键值对 QString key = m_xmlReader.readElementText(); Q_ASSERT(m_xmlReader.readNextStartElement()); QString value = m_xmlReader.readElementText(); dict.insertMulti(key, value); }这里针对不同情况,每读取到一个标签,根据类型进行相应的处理。
现在所有的基本代码已经完成了,下面附上整个工程的下载路径,嘿嘿,Nice!
工程代码下载地址