zoukankan      html  css  js  c++  java
  • Cocos2d-x 3.2 学习笔记(十二)TimberMan!疯狂伐木工!

      学习cocos2dx有一段时间了,试着做了2048游戏,最近又发现个经典游戏,啥也不说果断开工做自己的游戏——TimberMan

      首先说明:游戏资源摘自同类游戏,感谢这些游戏的资源让我完成自己的开发。

    一、TimberMan玩法--根本停不下来!

        这款游戏的玩法比较简单,通过手指点击左右屏幕来决定砍树站立的方向,不能让树枝碰触Hero,同时有时间限制(时间通过砍树增加),如果停止砍树时间结束=游戏结束。

      让我们看看成品的效果吧!(ps:录像失帧,看到的不直观,可以下载已打包好的apk 在最下方)

    二、代码结构

      HelloWorldScene 主场景

      TreeModel  树木(由树节点组合而成)

      Timber 伐木Hero

      TreeNode 树木节点

      GameScore 场景生命和得分

      GameOver 游戏结束

     

      当然由小到大,一颗大树是由无数的树节点组成的,因此先写树结类,然后才是大树类,最后才是场景。这样拆分之后就很简单的做出了demo

    三、树节点TreeNode 

      树节点是一颗树的基础,包括树枝。当然,有没有树枝、树枝的方向是由 大树控制的。因此有如下枚举贯穿游戏

      

    enum TreeBranchDirection
    {
        DEFINE,//无节点
        LEFT,//
        RIGHT//
    };

      节点的基本功能:1、设置树枝2、获取树枝类型(返回TreeBranchDirection)

    void TreeNode::setBranch(TreeBranchDirection enums)
    {
        enumBranch = enums;
        auto branch = this->getChildByName("branch");
        auto body = this->getChildByName("body");
        branch->setVisible(enums!=DEFINE);
        if(enums==DEFINE)return;
        if(enums == RIGHT)
        {
            branch->setScaleX(1);
            branch->setPositionX(body->getContentSize().width);
        }
        else
        {
            branch->setScaleX(-1);
            branch->setPositionX(-body->getContentSize().width);
        }
    }
    
    TreeBranchDirection TreeNode::getHasBranch()
    {
        return enumBranch;
    }

    四、大树TreeModel

      大树是树节点的集合,由一个一个的节点依次排列组成。最基本的功能如下

      TreeNode* getTreeHeadNode();获得头节点

      TreeNode* deleteTreeHeadNode();删除头节点

      void initTree();初始化

      TreeBranchDirection getFirstBranch();获得头节点的树枝方向

      void onReset();重置整个树

      Vector<TreeNode*> treeQueue;树节点列表

      Vector<TreeNode*> treeCache;树节点缓存列表

      优化:这个游戏一直在变化的是树节点,如果不停的删除和new节点 将会使程序不健康!为此除了要有树列表treeQueue外要有一个缓存队列treeCache,缓存队列的工作就是避免重复的new节点,同时回收砍掉的节点等待下次使用。

      当然,作为大树的类是整个游戏的重点逻辑:生成什么样节点?

        1、通过玩法得知必须在不同方向的树枝之间存在一个没有树枝的节点,使hero能生存。

        2、如果前一个是有树枝的,那么以什么概率来产生下一个节点是否要有树枝(有树枝必须是同方向的 or 无树枝),使hero生存。

        3、如果前一个树节点是无树枝的,那么再向前一个的树节点是否有树枝?根据难度来调节是否要产生树枝,增加难度。

      围绕着这三个问题要有一个得到树枝的逻辑函数TreeModel::getBranch()

    TreeBranchDirection TreeModel::getBranch()
    {
        auto isBranch = CCRANDOM_0_1()*10 < 7;
        if( treeQueue.size() == 0 )
            return DEFINE;
        if( !isBranch ) return DEFINE;
        auto protree = treeQueue.at(treeQueue.size()-1);
        switch (protree->getHasBranch())
        {
            case LEFT:
                return (CCRANDOM_0_1()*10 < 5) ? DEFINE : LEFT;
                break;
            case RIGHT:
                return (CCRANDOM_0_1()*10 < 5) ? DEFINE : RIGHT;
                break;
            case DEFINE:
                return getAgainBranch();
                break;
            default:
                return DEFINE;
                break;
        }
    }
    
    TreeBranchDirection TreeModel::getAgainBranch()
    {
        if( treeQueue.size() < 2 )
            return DEFINE;
        auto protree = treeQueue.at(treeQueue.size()-2);
        switch (protree->getHasBranch())
        {
        case LEFT:
            return (CCRANDOM_0_1()*10 < 6) ? RIGHT : LEFT;
            break;
        case RIGHT:
            return (CCRANDOM_0_1()*10 < 6) ? LEFT : RIGHT;
            break;
        case DEFINE:
            return (CCRANDOM_0_1()*10 < 4) ? LEFT : RIGHT;
            break;
        default:
            return DEFINE;
            break;
        }
    }

      这其中的 概率随机数是可以调整的,如果你想增加难度 那就调整吧!

    五、时间线GameScore

      游戏结束有两个点1、碰到树枝2、时间终止

      时间进度我用的ProgressTimer 进度表示时间百分比。

      我想到了两种逻辑:

        1、speed 法, 通过分数来决定速度,分数越高时间越少,不断的砍树来维持时间平衡。

        2、addProgress 增量法, 通过分数来决定砍树获得每次增加的量,分数越高增量越低,最后维持在一个平衡点,在这个平衡点上保持速度均衡。

      我最后选得增量,这两种方法相对都很不错。

     

    六、数据存储UserDefault

      整个游戏不需要大量的存储数据,因为只是记录最高分数,在设置游戏结束分数的时候进行读写

    void GameOver::setScore(int score)
    {
        int maxScore = score;
        char string[50] = {0};
        sprintf(string, "Score %d", score);
        _newScore->setString(string);
    
        maxScore = UserDefault::getInstance()->getIntegerForKey("maxScore");
        if( maxScore < score )
        {
            UserDefault::getInstance()->setIntegerForKey("maxScore",score);
        }
        newScore->setVisible(( maxScore < score ));
        char str2[50] = {0};
        sprintf(str2, "Max Score %d", ( maxScore < score ) ? score : maxScore);
        _highestScore->setString(str2);
    
        UserDefault::getInstance()->flush();
    }

    七、主场景 HelloWorldScene

      主场景控制游戏的开始与结束。逻辑判断并不多。

      点击判断:

    bool HelloWorld::onTouchBegans(Touch *touch, Event *event)
    {
        auto pos = touch->getLocation();
        Size visibleSize = Director::getInstance()->getVisibleSize();
        auto model = TreeModel::getInstance();
    
        auto isRight = pos.x > visibleSize.width/2;
        timber->playAction(isRight ? RIGHT : LEFT);
        if(isRight)
        {
            timber->setPosition(visibleSize.width/2+timber->getContentSize().width/2+20,150);
        }
        else
        {
            timber->setPosition(visibleSize.width/2-timber->getContentSize().width/2-20,150);
        }
    
        if(getIsOver())
        {
            timber->setTimberDie();
            gameOver();
            return false;
        }
    
        auto dic = visibleSize.width*2;
        auto time = 0.5;
        auto tree = model->deleteTreeHeadNode();
        if( isRight )
        {
            tree->runAction(Spawn::create(RotateBy::create(time,-180),MoveBy::create(time,Vec2(-dic,0)),nullptr));
        }
        else
        {
            tree->runAction(Spawn::create(RotateBy::create(time,180),MoveBy::create(time,Vec2(dic,0)),nullptr));
        }
    
        _score++;
        score->setScore(_score);
        if(getIsOver())
        {
            timber->setTimberDie();
            gameOver();
        }
    
        return true;
    }

      是否游戏结束:

    bool HelloWorld::getIsOver()
    {
        auto model = TreeModel::getInstance();
    
        if(model->getFirstBranch() == timber->getTimberDir()) return true;
        return false;
    }

      重置游戏,从新开始:

    void HelloWorld::onRest()
    {
        _score = 0;
        TreeModel::getInstance()->onReset();
        score->onReset();
        timber->onReset();
        list->setEnabled(true);
        auto isBgShow = (CCRANDOM_0_1()*10 < 5);
        bg1->setVisible(isBgShow);
        bg2->setVisible(!isBgShow);
        Size visibleSize = Director::getInstance()->getVisibleSize();
        timber->setPosition(visibleSize.width/2-timber->getContentSize().width/2-20,150);
    }

      当然coco2dx的粒子系统也很不错 我加入了 雪花特效以及声音特效:

     

    ParticleSystem* pl = ParticleSnow::create();
         pl->setTexture(Director::getInstance()->getTextureCache()->addImage("particle.png"));
         pl->setPosition(visibleSize.width/2,visibleSize.height);
         this->addChild(pl);

     

    八、总结

      这个游戏算是我做的比较全的demo了,加入了屏幕适配、桌面图片icon、声音、粒子、数据。虽然比较简单,但能学习、做好、完善其实还是比较不错的,因为工作比较忙所以抽空能敲一敲代码,不过总算没有半途而废。

      TimberMan.apk

      链接:http://pan.baidu.com/s/1o6A0Dce 密码:29mz

      TimberMan代码

      链接: http://pan.baidu.com/s/1pJynvdT 密码: bt1v

     

  • 相关阅读:
    day 30 粘包 自定义报头
    day29 网络基础之网络协议和通信
    day28 面向对象的进阶 反射 和类的内置方法
    day 27 模块和包 面向对象的复习
    CGI,FastCGI,PHP-CGI和PHP-FPM的区别
    跨平台的移动应用开发引擎CrossApp简介
    element-ui组件中的select等的change事件中传递自定义参数
    关于setInterval和setTImeout中的this指向问题
    懒加载和预加载的区别
    vueX的五个核心属性
  • 原文地址:https://www.cnblogs.com/Richard-Core/p/4029821.html
Copyright © 2011-2022 走看看