• 《入门级-Cocos2dx4.0 塔防游戏开发》---第九课:游戏难度选择


    一、开发环境

    操作系统:UOS1060专业版本。

    cocos2dx:版本4.0

    环境搭建教程:统信UOS下配置安装cocos2dx开发环境

    本课主要内容:

    1. 关卡难度选择
    2. xml文件读取
    3. 精灵的层级关系

    文章地址:https://arv000.blog.csdn.net/article/details/132489182 ​

    二、开发内容

    2.1 添加关卡跳转

            点击关卡地图上的关卡跳转到对应的关卡难度选择。在这个游戏中每个关卡上三种难度。场景的切换我们使用pushScene方式进行,这样可以方便我们回到原来的关卡选择界面,我们只需要popScene就能回到原来的关卡选择场景。

    在MapFlagSprite中,初始化touch监听

    1. void MapFlagSprite::initEvent()
    2. {
    3. auto listener = EventListenerTouchOneByOne::create();
    4. listener->onTouchBegan = CC_CALLBACK_2(MapFlagSprite::onTouchBeganFlag,this);
    5. listener->onTouchEnded = CC_CALLBACK_2(MapFlagSprite::onTouchEndedFlag,this);
    6. _eventDispatcher->addEventListenerWithSceneGraphPriority(listener,sprite_flag_);
    7. }

    在MapFlagSprite中,处理监听进行场景切换,关键代码Director::getInstance()->pushScene(ChooseDifficultyScene::createSceneWithLevel(getLevel()));

    1. bool MapFlagSprite::onTouchEndedFlag(Touch *touch, Event *event)
    2. {
    3. auto target = static_cast(event->getCurrentTarget());
    4. Point p = target->convertTouchToNodeSpace(touch);
    5. Rect rect= Rect(0,0,target->getContentSize().width,target->getContentSize().height);
    6. if(rect.containsPoint(p)){
    7. CCLOG(" MapFlagSprite::onTouchEndedFlag start level:%d",getLevel());
    8. Director::getInstance()->pushScene(ChooseDifficultyScene::createSceneWithLevel(getLevel()));
    9. }
    10. return false;
    11. }

    效果如下:

    2.2 第一层背景

           首先我们需要加载新的资源,还是在LoadingScene.cpp中添加

    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("map_spritesheet_16_a_3-hd.plist");

            第一层背景就是木板底层,然后因为他的宽度小于实际界面宽度,因此我们可以通过重复显示的方式铺满整个界面 

    1. // 第一层背景,木板背景
    2. for (int i = 0; i< 4; i++) {
    3. for(int j = 0 ; j < 5 ;j ++){
    4. auto sprite = Sprite::createWithSpriteFrameName("encyclopedia_bgTile.png");
    5. sprite->setPosition(Point(i*320,j*160));
    6. sprite->setAnchorPoint(Point(0,0));
    7. addChild(sprite);
    8. }
    9. }

    效果如下:

    2.3 第二层背景

    第二层背景就是纸张和地图

    1. // 第二层背景,纸张和地图
    2. auto sprite_bg2 = Sprite::createWithSpriteFrameName("LevelSelect_Bg.png");
    3. sprite_bg2->setPosition(Point(visible_size_.width/2,visible_size_.height/2));
    4. addChild(sprite_bg2,1);

    2.4 放大镜

    1. // 第三层背景放大镜
    2. auto sprite_loupe = Sprite::createWithSpriteFrameName("LevelSelect_loupe.png");
    3. sprite_loupe->setPosition(Point(visible_size_.width*0.21,390));
    4. addChild(sprite_loupe,2);

    2.5 关闭和进入战斗

            关闭按钮的操作就是popScene回退到上一次场景(关卡选择页面)

    1. // 关闭按钮
    2. auto sprite_close = MenuItemSprite::create(Sprite::createWithSpriteFrameName("LevelSelect_Back_0001.png"),
    3. Sprite::createWithSpriteFrameName("LevelSelect_Back_0002.png"),
    4. CC_CALLBACK_1(ChooseDifficultyScene::touchReturnMapSenec,this));
    5. Menu *menu_close = Menu::create(sprite_close,nullptr);
    6. sprite_close->setPosition(visible_size_.width*0.87,580);
    7. menu_close->setPosition(Vec2::ZERO);
    8. addChild(menu_close,2);
    9. // 进度战斗按钮
    10. auto sprite_start_mode = MenuItemSprite::create(Sprite::createWithSpriteFrameName("levelSelect_startMode_0001.png"),
    11. Sprite::createWithSpriteFrameName("levelSelect_startMode_0002.png"));
    12. Menu* menu_start_mode = Menu::create(sprite_start_mode,nullptr);
    13. sprite_start_mode->setPosition(Point(visible_size_.width*0.8,100));
    14. sprite_start_mode->setPosition(Vec2::ZERO);
    15. addChild(menu_start_mode,2);

    2.6 小星星

            小星星用于表示玩家游戏的得分情况,前三个是小星星后面两个一个是盾牌一个是拳头。

    1. // 第一颗 小星星
    2. sprite_star1 = Sprite::createWithSpriteFrameName("levelSelect_badges_0002.png");
    3. sprite_star1->setPosition(Point(170,580));
    4. sprite_star1->setRotation(-30.0f);
    5. addChild(sprite_star1,9);
    6. // 第一颗 小星星
    7. sprite_star2 = Sprite::createWithSpriteFrameName("levelSelect_badges_0002.png");
    8. sprite_star2->setPosition(Point(220,600));
    9. sprite_star2->setRotation(-15.0f);
    10. addChild(sprite_star2,9);
    11. // 第一颗 小星星
    12. sprite_star3 = Sprite::createWithSpriteFrameName("levelSelect_badges_0002.png");
    13. sprite_star3->setPosition(Point(270,605));
    14. addChild(sprite_star3,9);
    15. // 盾牌
    16. sprite_star4 = Sprite::createWithSpriteFrameName("levelSelect_badges_0004.png");
    17. sprite_star4->setPosition(Point(320,600));
    18. sprite_star3->setRotation(15.0f);
    19. addChild(sprite_star4,9);
    20. // 拳头
    21. sprite_star5 = Sprite::createWithSpriteFrameName("levelSelect_badges_0006.png");
    22. sprite_star5->setPosition(Point(370,580));
    23. sprite_star5->setRotation(30.0f);
    24. addChild(sprite_star5,9);

    2.7 难度选择

    1. ///=============================按钮1===============================
    2. auto menu_item_normal1 = MenuItemSprite::create(Sprite::createWithSpriteFrameName("levelSelect_butModes_0005.png"),
    3. Sprite::createWithSpriteFrameName("levelSelect_butModes_0001.png"));
    4. auto menu_ite_select1 = MenuItemSprite::create(Sprite::createWithSpriteFrameName("levelSelect_butModes_0001.png"),
    5. Sprite::createWithSpriteFrameName("levelSelect_butModes_0005.png"));
    6. toggle_item1_ = MenuItemToggle::createWithCallback(CC_CALLBACK_1(ChooseDifficultyScene::touchStarMenu1,this)
    7. ,menu_item_normal1
    8. ,menu_ite_select1
    9. ,nullptr);
    10. toggle_item1_->setTag(1);
    11. toggle_item1_->setPosition(Point(visible_size_.width*0.12,20));
    12. toggle_item1_->setSelectedIndex(0); // 初始化时设置0项为选中项
    13. toggle_item1_->setEnabled(true);
    14. ///=============================按钮2===============================
    15. auto menu_item_normal2 = MenuItemSprite::create(Sprite::createWithSpriteFrameName("levelSelect_butModes_0006.png"),
    16. Sprite::createWithSpriteFrameName("levelSelect_butModes_0006.png"));
    17. auto menu_ite_select2 = MenuItemSprite::create(Sprite::createWithSpriteFrameName("levelSelect_butModes_0002.png"),
    18. Sprite::createWithSpriteFrameName("levelSelect_butModes_0002.png"));
    19. auto menu_ite_disable2 = MenuItemSprite::create(Sprite::createWithSpriteFrameName("levelSelect_butModes_0004.png"),
    20. Sprite::createWithSpriteFrameName("levelSelect_butModes_0004.png"));
    21. toggle_item2_ = MenuItemToggle::createWithCallback(CC_CALLBACK_1(ChooseDifficultyScene::touchStarMenu2,this)
    22. ,menu_item_normal2
    23. ,menu_ite_select2
    24. ,menu_ite_disable2
    25. ,nullptr);
    26. toggle_item2_->setPosition(Point(visible_size_.width*0.24,20));
    27. toggle_item2_->setSelectedIndex(2); // 初始化时设置0项为选中项
    28. toggle_item2_->setEnabled(true);
    29. ///=============================按钮2===============================
    30. auto menu_item_normal3 = MenuItemSprite::create(Sprite::createWithSpriteFrameName("levelSelect_butModes_0007.png"),
    31. Sprite::createWithSpriteFrameName("levelSelect_butModes_0007.png"));
    32. auto menu_ite_select3 = MenuItemSprite::create(Sprite::createWithSpriteFrameName("levelSelect_butModes_0003.png"),
    33. Sprite::createWithSpriteFrameName("levelSelect_butModes_0003.png"));
    34. auto menu_ite_disable3 = MenuItemSprite::create(Sprite::createWithSpriteFrameName("levelSelect_butModes_0004.png"),
    35. Sprite::createWithSpriteFrameName("levelSelect_butModes_0004.png"));
    36. toggle_item3_ = MenuItemToggle::createWithCallback(CC_CALLBACK_1(ChooseDifficultyScene::touchStarMenu3,this)
    37. ,menu_item_normal3
    38. ,menu_ite_select3
    39. ,menu_ite_disable3
    40. ,nullptr);
    41. toggle_item3_->setPosition(Point(visible_size_.width*0.36,20));
    42. toggle_item3_->setSelectedIndex(2); // 初始化时设置0项为选中项
    43. toggle_item3_->setEnabled(true);
    44. auto toggle_menu = Menu::create(toggle_item1_,toggle_item2_,toggle_item3_,nullptr);
    45. toggle_menu->setPosition(Point(0,0));
    46. addChild(toggle_menu,2);

    2.8 Label文字

            文字的内容保存在xml文件内,因此我们需要根据当前的关卡获取对应的xml文件内容。

    1. ValueVector txt_description = FileUtils::getInstance()->getValueVectorFromFile(StringUtils::format("xml/level%d_description.xml",level));
    2. int i = 0;
    3. for(auto& e:txt_description){
    4. auto txt_map = txt_description.at(i).asValueMap();
    5. int id_init = txt_map.at("id").asInt();
    6. if(i == id_init){
    7. str_blue_top_ = txt_map.at("blueStrTop").asString();
    8. str_black_[0] = txt_map.at("blackStr1").asString();
    9. str_black_[1] = txt_map.at("blackStr2").asString();
    10. str_black_[2] = txt_map.at("blackStr3").asString();
    11. str_blue_[0] = txt_map.at("blueStr1").asString();
    12. str_blue_[1] = txt_map.at("blueStr2").asString();
    13. str_blue_[2] = txt_map.at("blueStr3").asString();
    14. }
    15. i++;
    16. }
    17. auto label1 = Label::createWithTTF(str_blue_top_,"fonts/Marker Felt.ttf",40);
    18. label1->setPosition(Point(visible_size_.width * 0.5,620));
    19. label1->setColor(Color3B(0,0,0));
    20. label1->setRotation(3.0f);
    21. label1->setAnchorPoint(Point(0,1));
    22. addChild(label1,1);
    23. label_blue_ = Label::createWithTTF(str_blue_[0],"fonts/Marker Felt.ttf",30);
    24. label_blue_->setPosition(Point(visible_size_.width * 0.5,570));
    25. label_blue_->setColor(Color3B(65,94,241));
    26. label_blue_->setRotation(3.0f);
    27. label_blue_->setAnchorPoint(Point(0,1));
    28. addChild(label_blue_,1);
    29. label_black_ = Label::createWithTTF(str_black_[0],"fonts/Marker Felt.ttf",21);
    30. label_black_->setPosition(Point(visible_size_.width * 0.5,525));
    31. label_black_->setColor(Color3B(0,0,0));
    32. label_black_->setRotation(3.0f);
    33. label_black_->setAnchorPoint(Point(0,1));
    34. addChild(label_black_,1);

    2.9 放大境内图像

            根据当前的关卡获取被放大的图片,让用户觉得当前的关卡被放大了。

    1. // 放大镜里面的东西。
    2. auto sprite_thumb = Sprite::createWithSpriteFrameName("thumb_stage2.png");
    3. sprite_thumb->setPosition(Point(260,400));
    4. addChild(sprite_thumb,1);

    三、演示效果

    四、知识点

    4.1 pushScene

    • pushScene 方法用于将一个新的场景推送到场景堆栈(Scene Stack)中,使其成为当前场景。
    • 当你调用 pushScene 时,当前场景将被暂停(不再更新和渲染),而新场景将变为活动场景。
    • 这意味着当前场景会保留在堆栈中,可以随时使用 popScene 返回到前一个场景。
    • 通常用于实现游戏中的场景切换,例如从主菜单场景切换到游戏关卡场景。

    4.2 replaceScene

    • replaceScene 方法用于替换当前的活动场景为一个新的场景,而不像 pushScene,它不会将当前场景保留在堆栈中。
    • 当你调用 replaceScene 时,当前场景将被销毁,而新场景将取代它成为活动场景。
    • 这意味着堆栈中只会有一个场景,即新场景。不能直接返回到前一个场景。
    • 通常用于实现游戏中的一些场景切换,如从游戏关卡场景切换到游戏结束场景,不需要保留前一个场景。

    4.3 pushScene和replaceScene优缺点对比

    `pushScene` 和 `replaceScene` 在使用上各自有优点和缺点,取决于你的游戏需求和设计目标。以下是它们的一些优点和缺点:

    4.3.1 pushScene 的优点

    1. 场景堆栈管理: 使用 `pushScene` 可以在场景堆栈中保留多个场景。这对于实现复杂的场景切换逻辑非常有用,例如从游戏中的任意位置返回到主菜单。

    2. 后退功能:因为前一个场景仍然存在于堆栈中,所以你可以轻松地使用 `popScene` 返回到前一个场景,实现后退功能。

    3. 场景切换流畅性:由于当前场景保留在堆栈中,切换回前一个场景通常比较流畅,因为前一个场景的状态仍然保留。

    4.3.2 pushScene 的缺点

    1. 内存消耗:保留多个场景可能会占用更多内存,特别是如果场景包含大量资源和对象。

    2. 场景堆栈管理复杂性:多个场景堆栈可能会增加游戏的复杂性,需要谨慎管理场景的生命周期和资源。

    4.3.3 replaceScene 的优点

    1. 内存效率:使用 `replaceScene` 只保留一个场景,因此在内存使用方面更加高效。

    2. 简单管理:场景堆栈始终保持简单,只有一个活动场景,这可能使代码和逻辑管理更加清晰。

    4.3.4 replaceScene 的缺点

    1. 无法后退:由于前一个场景被销毁,所以无法直接返回到前一个场景。如果需要后退功能,你需要自行实现。

    2. 场景切换可能有延迟:由于当前场景被销毁,加载新场景时可能会有短暂的延迟,这可能会在某些情况下影响用户体验。

    综上所述,选择使用 `pushScene` 还是 `replaceScene` 取决于你的游戏需求和设计目标。如果你需要复杂的场景切换逻辑和后退功能,可能更适合使用 `pushScene`。如果你关注内存效率和简化场景管理,那么 `replaceScene` 可能更合适。通常,在实际游戏开发中,你可能会根据不同的情况和场景选择合适的方法来实现场景切换。

    4.4 MenuItemToggle控件

    MenuItemToggle`是 Cocos2d-x 中的一个菜单项(`MenuItem`)控件,它允许你创建一个可以在多个选项之间切换的菜单项。当用户点击该菜单项时,它会循环切换到下一个选项。这个控件通常用于创建切换按钮或切换游戏中的不同选项或状态。

    以下是关于 `MenuItemToggle` 的一些使用知识点:

    1. 创建 `MenuItemToggle`: 要创建 `MenuItemToggle`,你需要为每个选项创建一个 `MenuItem`,然后将这些菜单项添加到 `MenuItemToggle` 中。你可以使用 `create` 方法来创建 `MenuItemToggle`。

    2. 设置选项切换的回调函数:你可以为 `MenuItemToggle` 设置一个回调函数,当用户点击菜单项时,该回调函数会被调用。在回调函数中,你可以执行与选项切换相关的操作。

    3. 添加到菜单:将 `MenuItemToggle` 添加到菜单中,以便它能够在菜单中显示并接收用户的点击操作。

    4. 切换选项:当用户点击 `MenuItemToggle` 时,它会自动循环切换到下一个选项。你可以使用 `setSelectedIndex` 方法来手动设置当前选项的索引。

    5. 设置选项的标签和文本: 每个选项都可以设置一个标签和文本,以便用户了解当前选中的选项是什么。

    6. 自定义外观: 你可以自定义 `MenuItemToggle` 的外观,包括背景、文本颜色、字体等,以使其符合你的应用或游戏的风格。

    以下是一个简单的示例,演示如何创建和使用 `MenuItemToggle`:

    1. #include "cocos2d.h"
    2. USING_NS_CC;
    3. void menuCallback(Ref* sender) {
    4.     // 处理菜单项点击事件
    5.     CCLOG("MenuItemToggle Clicked");
    6. }
    7. int main(int argc, char** argv) {
    8.     // 创建应用程序实例
    9.     Application app(argc, argv);
    10.     // 创建场景
    11.     auto scene = Scene::create();
    12.     auto layer = Layer::create();
    13.     scene->addChild(layer);
    14.     // 创建菜单项
    15.     auto item1 = MenuItemFont::create("Option 1", menuCallback);
    16.     auto item2 = MenuItemFont::create("Option 2", menuCallback);
    17.     auto item3 = MenuItemFont::create("Option 3", menuCallback);
    18.     // 创建 MenuItemToggle,并将选项添加到其中
    19.     auto toggleItem = MenuItemToggle::createWithCallback(menuCallback, item1, item2, item3);
    20.     
    21.     // 设置初始选中的选项
    22.     toggleItem->setSelectedIndex(0);
    23.     // 创建菜单并添加菜单项
    24.     auto menu = Menu::create(toggleItem, nullptr);
    25.     menu->setPosition(Vec2(240, 160));
    26.     layer->addChild(menu);
    27.     // 运行场景
    28.     Director::getInstance()->runWithScene(scene);
    29.     return app.run();
    30. }

    在这个示例中,我们创建了一个 `MenuItemToggle`,其中包含三个选项。当用户点击菜单项时,会调用 `menuCallback` 函数。你可以根据需要在回调函数中执行不同的操作来处理不同的选项。

    4.5 xml文件的读取

     在coco2dx中我们可以使用xml文件存储和读取内容,xml文件的格式有一定的要求,每一个dict都是一个数组,里面使用key和string交替表示key->value的对应关系。通过FileUtils::getInstance()->getValueVectorFromFile

    文件格式如下:

    1. "1.0" encoding="UTF-8"?>
    2. plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ">
    3. ">
    4. id
    5. 0
    6. info
    7. 风一般的男纸
    8. id
    9. 1
    10. info
    11. 注定是寂寞的

    读取

    ValueVector txt_vec = FileUtils::getInstance()->getValueVectorFromFile("label.xml");  

    可以看到他是一个vector,typedef std::vector ValueVector;  

    Value是一个Map,因此我们可以通过一下方式提取数据,第一步:先取列表内容

    auto txt_map = txt_vec.at(0).asValueMap();  

     第二步:通过关键词获取对应的数据内容

    int id_int = txt_map.at("id").asInt();  

    完整代码如下:

    1. ValueVector txt_vec = FileUtils::getInstance()->getValueVectorFromFile("label.xml");//读取xml文档,放入ValueVector中
    2. for( auto& e : txt_vec)
    3. {
    4. auto txt_map = e.asValueMap();//将键值转化成Map格式,放入txt_map中
    5. int id_int = txt_map.at("id").asInt();//获取id
    6. if(10 == id_int)
    7. {
    8. auto label_str = txt_map.at("info").asString();//获取info的值
    9. auto label1 = LabelTTF::create(label_str,"Arial",25);
    10. label1->setPosition(Point(160,425));
    11. this->addChild(label1,2);
    12. }
    13. else if(20 == id_int)
    14. {
    15. auto label_str = txt_map.at("info").asString();
    16. auto label1 = LabelTTF::create(label_str,"Arial",25);
    17. label1->setPosition(Point(160,400));
    18. this->addChild(label1,2);
    19. }
    20. }

    五、文章导航

    上一节:《入门级-Cocos2dx4.0 塔防游戏开发》---第八课:关卡地图开发

    下一节:待定

    总章:《入门级-Cocos2d 4.0塔防游戏开发》---实战

  • 相关阅读:
    技术干货 | 一文弄懂差分隐私原理!
    [山东科技大学OJ]1068 Problem H: 液晶显示
    RPA机器人的10大基础功能与2大类型
    如何用MATLAB对CSI数据进行预处理(卡尔曼滤波篇)
    Python - Numpy库的使用(简单易懂)
    《棒球裁判》:走近棒球运动·球赛规则
    allatori8.0文档翻译-第十步:增加过期日期
    [Hackthebox] Fawn (FTP)
    测试用例设计方法之等价类划分方法
    JimuReport积木报表 v1.6.5 版本发布—免费报表工具
  • 原文地址:https://blog.csdn.net/arv002/article/details/132606708