前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();
}
}
这里先判断下,看该URL是否存在该音乐文件,不然到时候若缺失文件,会导致关联音乐文件失败,而解析出错。
用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!