九、关卡数据

来源:互联网 发布: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。

根据本游戏关卡的特征,我们抽象出了包括如下所示的一系列数据:

p1

把这些关卡数据写入plist文件后,第二步就可以设计一个类来解析读取数据了。要解析plist文件可以参考Cocos2d-x类库中的SpriteFrameCache类和ParticleSystem类,它们使用ValueMap类来对plist文件进行操作。下图是创建好的plist文件:

p2

说了那么多,接下来我们还是来看看代码吧。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
classLoadLevelinfo:publicRef
{
public:   
    ~LoadLevelinfo();
    staticLoadLevelinfo * createLoadLevelinfo(conststd::string& plistpath);
 
    boolinitPlist(conststd::string& plistpath);
    voidreadLevelInfo();
    voidclearAll();  
private:
    ValueMap resources;
    ValueMap levelInfo;
};

变量resources是关卡待加载的资源数据,levelInfo是关卡信息数据。initPlist方法根据plist文件路径加载并读取游戏相关数据,readLevelInfo则是读取并保存plist文件中所有属性的值。而这些值都被保存在GameManger中,如下就是GameManger中增加的属性,它们基本上都是用来存储从plist文件中解析的关卡数据的。

1
2
3
4
5
6
7
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的实现方法,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
boolLoadLevelinfo::initPlist(conststd::string& plistpath)
{
    boolbRet = 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);
    returnbRet;
}
 
voidLoadLevelinfo::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);
}
  1. plistpath是.plist文件的相对路径,这里通过FileUtils类获得给定文件名的完整路径,再把该文件中的内容(类型为Dictionary)加载到ValueMap的对象中保存。
  2. 放到Map中即可用Map的方法读取键为”id"的值是多少,分别读取dict对象中键为”resources",”levelInfo"的值,它们的类型依旧是Dictionary,所以依旧将其内容转换到ValueMap对象中保存。

p3

  1. 根据plist文件的属性和层次特征,一层一层的遍历获得相应类型的键值,再把它们存储到GameManager中。
  2. 根据从plist文件中获得的敌人信息创建一波敌人(groupEnemy),并把它插入groupVector向量统一管理。

判断游戏是否结束

判断是否过关

当最后一波敌人添加完后,变量isSuccessful将被置为true。这也说明了玩家游戏已经顺利过关,该跳转到下一个界面了。

在update函数体中添加如下的代码段,实现最终分数的评比和场景跳转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if(isSuccessful)
{
    isSuccessful = false;
    auto star = 0;
    auto playHp = this->getPlayHpPercentage();
 
    if( playHp > 0 && playHp <= 30){ star = 1;}
    elseif(playHp > 30 && playHp <= 60 ){ star = 2;}
    elseif(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()方法中加上如下的判断。

1
CC_SYNTHESIZE(bool, enemySuccessful, EnemySuccessful);
1
2
3
4
5
6
7
8
9
10
11
12
13
Node* EnemyBase::nextPoint()
{
    intmaxCount = this->pointsVector.size();
    pointCounter++;
    if(pointCounter < maxCount  ){
        auto node =this->pointsVector.at(pointCounter);
        returnnode;
    }
    else{
        setEnemySuccessful(true);
    }
    returnNULL;
}

每当有敌人攻克防守时,玩家的血量就会相应的减少,当玩家血量减少到0时,游戏失败,跳转到下一个界面。实现该方法的enemyIntoHouse函数我们依旧把它放在update函数体中,这样程序会逐帧检测游戏是否失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
voidPlayLayer::enemyIntoHouse()
{
    auto enemyVector = instance->enemyVector;
    for(inti = 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);
            }
        }
    }
}

添加工具栏

这里工具栏指游戏场景上方的图形化信息提示栏。为游戏添加工具栏可以更直观的观察到游戏的信息动态,如游戏金币数、当前波数、总波数等等信息。所以,它是很有必要的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
voidPlayLayer::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);   
    // 当前波数
    intgroupTotal = 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);
}
分享到:


0 0
原创粉丝点击