Qt版本-塔防遊戲實現三

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

工程代碼下載地址




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章