本篇博客为飞机大战添加一下分数标签,同时保存用户的数据到xml文件中,我们可以做一个分数榜,来显示一下玩家的得分情况,本篇博客实现UserData类,然后添加到游戏主场景中显示玩家的当前得分情况,同时我们还将根据玩家的得分情况来设置敌机出现的数量,最后玩家退出游戏的时候保存分数到文件中,分数榜留作以后博客完成。下面看一下UserData类的写法。

#ifndef _SAVE_DATA_H_
#define _SAVE_DATA_H_
#include "cocos2d.h"

USING_NS_CC;

class SaveData : public Ref
{
public:
	SaveData(void);
	~SaveData(void);
public:
	bool init();
	CREATE_FUNC(SaveData);
private:
	//用户数据操作的成员变量
	UserDefault * m_userDefault;
	//该集合中保存的是分数的记录,因为分数是基本数据类型,放到vector中的内容必须是Ref的子类,所以要用Value
	//封装一下,但封装完毕却不是使用vector来存放,而是用ValueVector
	ValueVector m_vector;
	//记录玩家的当前分数
	CC_SYNTHESIZE(int,m_score,Score);
public:
	void save();
};

#endif
#include "SaveData.h"

SaveData::SaveData(void)
{
}

SaveData::~SaveData(void)
{
}

bool SaveData::init()
{
	//先对成员变量进行初始化
	m_userDefault = UserDefault::getInstance();
	//初始化vector集合
	m_vector = ValueVector();
	//初始化本次游戏玩家得分
	m_score = 0;

//每玩一次游戏,分数的记录条数就会加一
	m_userDefault->setIntegerForKey("count",(m_userDefault->getIntegerForKey("count",0))+1);

	//首先判断XML文件是否存在,如果不存在的话就会执行if中的语句
	if(m_userDefault->getBoolForKey("isExit",false) == false)
	{
		//玩家初次玩游戏会执行这里
		m_userDefault->setBoolForKey("isExit",true);
	}
	else
	{
			//将分数记录保存在vector集合中
			for(int i=0;i<m_userDefault->getIntegerForKey("count")-1;i++)
			{
				__String * index = String::createWithFormat("%d",i);
				//将要放的数据使用Value包装一下
				m_vector.push_back(Value(m_userDefault->getIntegerForKey(index->getCString())));
			}
	}

	return true;
}

void SaveData::save()
{
	/*本函数的整体思路是先对vector中保存的玩家数据进行排序,然后重新写入到xml文件中*/

	//将玩家的分数保存到set集合中,以便排序
	m_vector.push_back(Value(m_score));

	//自定义排序函数,对m_vector中的内容进行排序,方便以后对数据的操作
	auto sortData = [](Value value1,Value value2)
	{
		return value1.asInt()>value2.asInt();
	};
	//调用c++模板中的sort函数进行排序,前俩个参数是数组的地址,最后一个参数是使用的排序函数
	std::sort(m_vector.begin(),m_vector.end(),sortData);

	//将玩家的得分保存在文件中
	for(int i=0;i<m_vector.size();i++)
	{
		auto value = m_vector.at(i);
		auto index = __String::createWithFormat("%d",i);
		m_userDefault->setIntegerForKey(index->getCString(),value.asInt());
	}
	//刷新
	m_userDefault->flush();
}

在这个类中需要注意的是玩家分数会存放到一个vector集合中,为什么要这么做呢,因为我们要利用这个vector对玩家分数进行排序,方便以后做一个分数榜出来。玩家分数是int类型,vector中只能存放Ref的子类,所以int需要包装一下,也就是用到了Value类,关于这些容器的使用可以查看我前边的博客。最后我们使用的是ValueVector这个容器来存放数据,如果你使用vector的话是会报错的。接下来是对XML文件是否存在的一个判断,然后将XML文件中的内容读取到vector集合中,以便和本次的游戏得分进行比较,save函数比较重要,在save函数中演示了对vector排序的方法,我们需要在适当的时候调用这个函数,保存游戏数据。接下来就是什么时候用这个类了,我们需要显示玩家的得分情况,所以需要在UILayer中使用,以下是UILayer中init函数新增加的代码。

//添加显示分数的标签
	m_saveData = SaveData::create();
	//这里一定要retain一下saveData,在析构函数中release一下
	m_saveData->retain();
	auto str = __String::createWithFormat("%d",m_saveData->getScore());
	m_score = Label::createWithBMFont("font/font.fnt",str->getCString());
	m_score->setPosition(Point(score_label->getContentSize().width+m_score->getContentSize().width/2+30,
		size.height-score_label->getContentSize().height/2));
	this->addChild(m_score);
	//记得更新分数的显示
	this->scheduleUpdate();
void UILayer::update(float tm)
{
	auto str = __String::createWithFormat("%d",m_saveData->getScore());
	//更新分数和坐标
	m_score->setString(str->getCString());
	m_score->setPositionX(score_label->getContentSize().width+m_score->getContentSize().width/2+30);
}

需要注意的是SaveData类继承自Ref,这里作为UILayer的成员变量的时候记着要retain,否则就释放掉了。好了,现在运行程序是不是添加了分数,但是我们消灭飞机分数是不变的,没错,因为我们还没有改变分数啊。改变的地方当然是敌机和子弹碰撞的时候了,下面就是代码。

//生命值为0敌机爆炸,如果敌机已经死亡就不要和剩下的子弹进行判断了,所以要break
				if(enemyVector.at(i)->getHp() == 0)
				{
					enemyVector.at(i)->blowUp();
					//改变分数
					auto saveData = m_ui->getSaveData();
					//加上分数,这个分数是敌机的生命值,函数getOriginalHp是新加的
					saveData->setScore(saveData->getScore()+enemyVector.at(i)->getOriginalHp());
					break;
				}
//获得敌机原始的血量值
	int getOriginalHp(){return (m_frameName[5]-'0')*5;};

在游戏主场景中我将ui作为了成员变量,而ui中有一个成员变量是m_saveData,这样的话SaveData就不用写成单例的形式了,通过这种方式我们来改变玩家的分数。下面我们完成根据玩家的分数来改变敌机出现的频率,修改主场景中的函数addEnemy。

//添加敌机
void MainGame::addEnemy(float tm)
{
	//这里是根据分数来改变敌机出现的快慢
	int score = m_ui->getSaveData()->getScore();
	//更新schedule只更新一次,使用level来控制
	if(score<50 && m_level == 0)
	{
		m_level = 1;
	}
	else if(score>50 && score<=200 && m_level == 1)
	{
		//重新调用schedule会更新时间的
		this->schedule(SEL_SCHEDULE(&MainGame::addEnemy),0.8f);
		m_level = 2;
	}
	else if(score>200 && score<=400 && m_level == 2)
	{
		this->schedule(SEL_SCHEDULE(&MainGame::addEnemy),0.5f);
		m_level = 3;
	}
	else if(score>400 && score<=800 && m_level == 3)
	{
		this->schedule(SEL_SCHEDULE(&MainGame::addEnemy),0.4f);
		m_level = 4;
	}
	else if(score>800 && m_level == 4)
	{
		this->schedule(SEL_SCHEDULE(&MainGame::addEnemy),0.3f);
		m_level = 5;
	}

	/*到此处为止,代码就没有修改了,上边是修改过的代码*/

	auto enemy = EnemyBase::create();
	//根据不同的概率来添加不同种类的飞机
	int enemy_x = CCRANDOM_0_1()*9+1;
	int count = 0;
	if(enemy_x > 0 && enemy_x <7)
	{
		enemy_x = 1;
		count = 4;
	}
	else if(enemy_x >= 7 && enemy_x < 9)
	{
		enemy_x = 2;
		count = 4;
	}
	else
	{
		//敌机三用到的背景图片不太一样,这里单独的建立下
		count = 6;
		enemy->initEnemy("enemy3_n1",count);
		//创建敌机三的动画
		Vector<SpriteFrame *> vector;
		vector.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_n1.png"));
		vector.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_n2.png"));
		auto animation = Animation::createWithSpriteFrames(vector,0.2f,-1);
		auto animate = Animate::create(animation);
		enemy->runAction(animate);
		//添加到当前的层中
		this->addChild(enemy);
		//将敌机添加到管理器中
		auto & enemyVector = Manager::getInstance()->getEnemyVector();
		enemyVector.pushBack(enemy);
		//直接返回,不再执行下面的语句
		return;
	}
	//以下的这句话一定要调用
	auto str = __String::createWithFormat("enemy%d",enemy_x);
	enemy->initEnemy(str->getCString(),count);
	this->addChild(enemy);
	//将敌机添加到管理器中
	auto & vector = Manager::getInstance()->getEnemyVector();
	vector.pushBack(enemy);
}

如果只修改上边的代码你会发现如果我们不暂停场景游戏是没有任何问题的,但是如果暂停了场景再开始游戏敌机出现的频率一下子就慢了下来。这是因为我们暂停场景的时候是用的入栈的方式,出栈以后会执行onEnterTransitionDidFinish函数,在这个函数中更新了敌机出现的频率为1,所以对代码做如下的改动。

//场景切换完毕调用
void MainGame::onEnterTransitionDidFinish()
{
	//必须先调用父类的函数
	Layer::onEnterTransitionDidFinish();
	//添加敌机,每秒添加一个
	float tm = 1.0f;
	if(m_level == 1)
		tm = 0.8f;
	else if(m_level == 2)
		tm = 0.5f;
	else if(m_level == 3)
		tm = 0.4f;
	else if(m_level == 4)
		tm = 0.3f;
	this->schedule(SEL_SCHEDULE(&MainGame::addEnemy),tm);
	//添加子弹
	this->schedule(SEL_SCHEDULE(&MainGame::addBullet),0.1f);
	//添加UFO和炸弹,每十秒出现一个UFO或者是炸弹
	this->schedule(SEL_SCHEDULE(&MainGame::addUfo),10.0f);
	//碰撞检测
	this->schedule(SEL_SCHEDULE(&MainGame::isHitEnemy));
}

最后玩家退出游戏的时候需要调用一下Sava函数,这个位置就不用我说了吧。
用3.0实现飞机大战——用户数据的操作