前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!