九、关卡数据
来源:互联网 发布:java中console 编辑:程序博客网 时间:2024/05/01 15:58
转载至:http://cn.cocos2d-x.org/tutorial/show?id=1300
读取游戏关卡数据
关卡设计对于塔防游戏是必须的,通过关卡设计玩家可以尝试各种不同风格和难度的游戏。它是游戏的重要组成部分,游戏的节奏、难度等级等方面很大程度上要依靠关卡来控制。而在各关中,敌人波数、敌人个数、地图、金币等等信息都是不同的。如果每个关卡都循规蹈矩的重构代码,那它的重用率将会很高,程序的藕合度也将很低,因此,这里我们很有必要把这些数据信息收集起来统一管理。这样,在游戏场景中我们就可以在不同的关卡中重用相同的一套逻辑了。
但是,现在另一个问题又出现了,我们该如何储存和处理这些数据啦?
我们可以把游戏中的数据分为静态数据和动态数据两种:
- 动态数据是指游戏运行和运营过程中不断变动的数据,这些数据会随着玩家在游戏世界中执行各种行为的不同而发生改变,如本游戏中的分数。一般简单的数据可以使用Cocos2d-x中的UserDefault来进行动态数据的存储,大型的数据则会更倾向于用SQLite来进行存储。所以,在开发过程中应该根据需求来选择数据存储方案。
- 静态数据则是程序中的只读数据,如资源名,敌人起始血量,起始金币数等等。然而,为了达到最佳的游戏效果或方便测试,这些数据在开发过程中可能是经常变动的。所以为了便于修改,一般会把这些数据放到外部文件中进行保存,杜绝硬编码。
现在回到游戏,我们第一步需要做的是抽象出一组有关关卡信息的静态数据,然后把它们写到文件中,便于读取。
如果是简单数据的读取,我们除了使用常用的格式之外,我们还可以用Cocos2d-x最常用的plist来读取。plist是基于XML的纯文本格式,随便找个文本编辑器就可以编辑。当然,如果你使用的是OS X系统,那在XCode中可以直接创建和编辑plist文件。下面我们就来和大家共同学习一下plist。
根据本游戏关卡的特征,我们抽象出了包括如下所示的一系列数据:
把这些关卡数据写入plist文件后,第二步就可以设计一个类来解析读取数据了。要解析plist文件可以参考Cocos2d-x类库中的SpriteFrameCache类和ParticleSystem类,它们使用ValueMap类来对plist文件进行操作。下图是创建好的plist文件:
说了那么多,接下来我们还是来看看代码吧。如下所示:
class
LoadLevelinfo:
public
Ref
{
public
:
~LoadLevelinfo();
static
LoadLevelinfo * createLoadLevelinfo(
const
std::string& plistpath);
bool
initPlist(
const
std::string& plistpath);
void
readLevelInfo();
void
clearAll();
private
:
ValueMap resources;
ValueMap levelInfo;
};
变量resources是关卡待加载的资源数据,levelInfo是关卡信息数据。initPlist方法根据plist文件路径加载并读取游戏相关数据,readLevelInfo则是读取并保存plist文件中所有属性的值。而这些值都被保存在GameManger中,如下就是GameManger中增加的属性,它们基本上都是用来存储从plist文件中解析的关卡数据的。
CC_SYNTHESIZE(
int
, money, Money);
CC_SYNTHESIZE(
int
, groupNum, GroupNum);
CC_SYNTHESIZE(std::string, curMapName, CurMapName);
CC_SYNTHESIZE(std::string, currLevelFile, CurrLevelFile);
CC_SYNTHESIZE(std::string, nextLevelFile, NextLevelFile);
CC_SYNTHESIZE(
bool
, isFinishedAddGroup, IsFinishedAddGroup);
CC_SYNTHESIZE(std::string, curBgName, CurBgName);
接下来是initPlist和readLevelInfo的实现方法,如下所示:
bool
LoadLevelinfo::initPlist(
const
std::string& plistpath)
{
bool
bRet =
false
;
do
{
// 1
std::string fullPath = FileUtils::getInstance()->fullPathForFilename(plistpath);
ValueMap dict = FileUtils::getInstance()->getValueMapFromFile(fullPath);
// 2
resources = dict[
"resources"
].asValueMap();
levelInfo = dict[
"levelInfo"
].asValueMap();
bRet =
true
;
}
while
(0);
return
bRet;
}
void
LoadLevelinfo::readLevelInfo()
{
GameManager *instance = GameManager::getInstance();
// 3
auto money = levelInfo[
"money"
].asFloat();
instance->setMoney(money);
auto currlevel = levelInfo[
"currlevel"
].asString();
instance->setCurrLevelFile(currlevel);
auto nextlevel = levelInfo[
"nextlevel"
].asString();
instance->setNextLevelFile(nextlevel);
ValueMap& groupDict = levelInfo[
"group"
].asValueMap();
auto groupTotle = groupDict.size();
instance->setGroupNum(groupTotle);
for
(auto iter = groupDict.begin(); iter != groupDict.end(); ++iter)
{
ValueMap& group = iter->second.asValueMap();
std::string spriteFrameName = iter->first;
auto type1Num = group[
"type1Num"
].asInt();
auto type2Num = group[
"type2Num"
].asInt();
auto type3Num = group[
"type3Num"
].asInt();
auto type1Hp = group[
"type1Hp"
].asInt();
auto type2Hp = group[
"type2Hp"
].asInt();
auto type3Hp = group[
"type3Hp"
].asInt();
GroupEnemy* groupEnemy = GroupEnemy::create()->initGroupEnemy(type1Num, type1Hp, type2Num, type2Hp, type3Num, type3Hp);
instance->groupVector.pushBack(groupEnemy);
}
auto curMapName = resources[
"map"
].asString();
instance->setCurMapName(curMapName);
auto curBgName = resources[
"image"
].asString();
instance->setCurBgName(curBgName);
}
- plistpath是.plist文件的相对路径,这里通过FileUtils类获得给定文件名的完整路径,再把该文件中的内容(类型为Dictionary)加载到ValueMap的对象中保存。
- 放到Map中即可用Map的方法读取键为”id"的值是多少,分别读取dict对象中键为”resources",”levelInfo"的值,它们的类型依旧是Dictionary,所以依旧将其内容转换到ValueMap对象中保存。
- 根据plist文件的属性和层次特征,一层一层的遍历获得相应类型的键值,再把它们存储到GameManager中。
- 根据从plist文件中获得的敌人信息创建一波敌人(groupEnemy),并把它插入groupVector向量统一管理。
判断游戏是否结束
判断是否过关
当最后一波敌人添加完后,变量isSuccessful将被置为true。这也说明了玩家游戏已经顺利过关,该跳转到下一个界面了。
在update函数体中添加如下的代码段,实现最终分数的评比和场景跳转。
if
(isSuccessful)
{
isSuccessful =
false
;
auto star = 0;
auto playHp =
this
->getPlayHpPercentage();
if
( playHp > 0 && playHp <= 30){ star = 1;}
else
if
(playHp > 30 && playHp <= 60 ){ star = 2;}
else
if
(playHp > 60 && playHp <= 100 ){ star = 3;}
if
( star > UserDefault::getInstance()->getIntegerForKey(instance->getCurrLevelFile().c_str()))
{
UserDefault::getInstance()->setIntegerForKey(instance->getCurrLevelFile().c_str(), star);
}
instance->clear();
// 应该跳转到成功界面,这里暂时显示如下文字
Size winSize = Director::getInstance()->getWinSize();
auto putOutLabel = Label::createWithBMFont(
"fonts/boundsTestFont.fnt"
,
"Congratulations!"
);
putOutLabel->setPosition(Point(winSize.width / 2, winSize.height / 2 ));
putOutLabel->setScale(4);
this
->addChild(putOutLabel);
}
该段代码将根据玩家剩余血量来评定分数,当血量在60到100之间时,玩家将得到三颗星;当在30到60之间时,则为两颗星;在0到30之间时就只会得到一颗星了。
正如前面所说,像游戏分数这样简单的动态数据我们使用UserDefault来进行存储即可,所以,我们把过关后的分数用UserDefault存储起来。它的键名是从plist中读取的,为了能清楚地分便,所以设为了该关数据的文件名。
判断是否失败
当敌人移动到最后一个路径点的时候,这也意味着该敌人成功的攻克了玩家的防守,它取得了胜利。所以我们需要为每个敌人都添加一条是否成功进入玩家阵地的属性,并在敌人的nextPoint()方法中加上如下的判断。
CC_SYNTHESIZE(
bool
, enemySuccessful, EnemySuccessful);
Node* EnemyBase::nextPoint()
{
int
maxCount =
this
->pointsVector.size();
pointCounter++;
if
(pointCounter < maxCount ){
auto node =
this
->pointsVector.at(pointCounter);
return
node;
}
else
{
setEnemySuccessful(
true
);
}
return
NULL;
}
每当有敌人攻克防守时,玩家的血量就会相应的减少,当玩家血量减少到0时,游戏失败,跳转到下一个界面。实现该方法的enemyIntoHouse函数我们依旧把它放在update函数体中,这样程序会逐帧检测游戏是否失败。
void
PlayLayer::enemyIntoHouse()
{
auto enemyVector = instance->enemyVector;
for
(
int
i = 0; i < enemyVector.size(); i++)
{
auto enemy = enemyVector.at(i);
if
( enemy->getEnemySuccessful())
{
instance->enemyVector.eraseObject(enemy);
enemy->removeFromParent();
auto playHp = getPlayHpPercentage() - 10;
if
(playHp > 0){
setPlayHpPercentage(playHp);
playHpBar->setPercentage(playHp);
}
else
{
instance->clear();
// 应该跳转到失败界面
this
->removeAllChildren();
Size winSize = Director::getInstance()->getWinSize();
auto putOutLabel = Label::createWithBMFont(
"fonts/boundsTestFont.fnt"
,
"Game Over"
);
putOutLabel->setPosition(Point(winSize.width / 2, winSize.height / 2 ));
putOutLabel->setScale(4);
this
->addChild(putOutLabel);
}
}
}
}
添加工具栏
这里工具栏指游戏场景上方的图形化信息提示栏。为游戏添加工具栏可以更直观的观察到游戏的信息动态,如游戏金币数、当前波数、总波数等等信息。所以,它是很有必要的。
void
PlayLayer::initToolLayer()
{
auto size = Director::getInstance()->getWinSize();
toolLayer = Layer::create();
addChild(toolLayer);
// 工具栏背景图片
auto spritetool = Sprite::createWithSpriteFrameName(
"toolbg.png"
);
spritetool->setAnchorPoint(Point(0.5f, 1));
spritetool->setPosition (Point(size.width / 2, size.height));
toolLayer->addChild(spritetool);
// 金币数
money = instance->getMoney();
moneyLabel = Label::createWithBMFont(
"fonts/bitmapFontChinese.fnt"
,
" "
);
moneyLabel->setPosition(Point(spritetool->getContentSize().width / 8, spritetool->getContentSize().height / 2));
moneyLabel->setAnchorPoint(Point(0, 0.5f));
auto moneyText = std::to_string(money);
moneyLabel->setString(moneyText);
spritetool->addChild(moneyLabel);
// 玩家血量条
playHpBar = ProgressTimer::create(Sprite::createWithSpriteFrameName(
"playhp.png"
));
playHpBar->setType(ProgressTimer::Type::BAR);
playHpBar->setMidpoint(Point(0, 0.4f));
playHpBar->setBarChangeRate(Point(1, 0));
playHpBar->setPercentage(playHpPercentage);
playHpBar->setPosition(Point(spritetool->getContentSize().width / 5 *4 , spritetool->getContentSize().height / 2));
spritetool->addChild(playHpBar);
// 玩家得分标尺
auto star = Sprite::createWithSpriteFrameName(
"playstar.png"
);
star->setPosition(Point(spritetool->getContentSize().width / 5 *4 , spritetool->getContentSize().height / 2));
spritetool->addChild(star);
// 当前波数
int
groupTotal = instance->getGroupNum();
groupLabel = Label::createWithBMFont(
"fonts/bitmapFontChinese.fnt"
,
" "
);
groupLabel->setPosition(Point(spritetool->getContentSize().width / 8 * 3, spritetool->getContentSize().height / 2 ));
groupLabel->setAnchorPoint(Point(0.5f , 0.5f));
auto groupInfoText = std::to_string(groupCounter + 1);
groupLabel->setString(groupInfoText);
spritetool->addChild(groupLabel);
// 总波数
groupTotalLabel = Label::createWithBMFont(
"fonts/bitmapFontChinese.fnt"
,
" "
);
groupTotalLabel->setPosition(Point(spritetool->getContentSize().width / 2 , spritetool->getContentSize().height / 2 ));
groupTotalLabel->setAnchorPoint(Point(0.5f , 0.5f));
auto groupTotalText = std::to_string(groupTotal);
groupTotalLabel->setString(groupTotalText);
spritetool->addChild(groupTotalLabel);
}
- 九、关卡数据
- 关卡
- Replica Island 学习笔记 05 - 关卡数据
- 华容道05--关卡数据的显示
- quick-cocos2d-x 关卡数据的解析与更新
- 华容道03---关卡类的设计和数据读取
- [unreal4入门系列之九] UE4创建空白关卡并添加碰撞体
- 关卡设计
- 关卡流
- 关卡重载
- 数据挖掘九律
- 九、数据验证机制
- 数据挖掘九律
- 大数据(九) - Hive
- 数据实验九 检索
- Kotlin解析数据(九)
- 大数据-九
- Hibernate数据更新(九)
- Qt stylesheet使用
- MPI Maelstrom
- 马士兵struts2视频教程第三十一集
- 在Ubuntu下编译FFMPEG
- Lucene学习总结之二:Lucene的总体架构
- 九、关卡数据
- 点在哪
- Apache Lucene 简介
- Callable和CompletionService的使用,多任务返回值。
- 【二分匹配】HDU1068-Girls and Boys
- 【140115】网络五子棋,VC++游戏源码
- POJ 2349————最小生成树
- 稀疏矩阵的十字链表存储
- JDK 1.7 Integer.parseInt 源码解析