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!

工程代码下载地址




你可能感兴趣的:(Qt版本-塔防游戏实现三)