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

时间: 2024-10-12 23:02:26

Cocos2d-x 3.2 学习笔记(十二)TimberMan!疯狂伐木工!的相关文章

Swift学习笔记十二:下标脚本(subscript)

下标脚本就是对一个东西通过索引,快速取值的一种语法,例如数组的a[0].这就是一个下标脚本.通过索引0来快速取值.在Swift中,我们可以对类(Class).结构体(structure)和枚举(enumeration)中自己定义下标脚本的语法 一.常规定义 class Student{ var scores:Int[] = Array(count:5,repeatedValue:0) subscript(index:Int) -> Int{ get{ return scores[index];

虚拟机VMWare学习笔记十二 - 将物理机抓取成虚拟机

1. 安装VMware vCenter Converter Standalone Client 运行虚拟机,File -- Virtualize a Physical Machine 这时如果电脑中没有VMware vCenter Converter Standalone Client ,则会进行安装. 安装过程 之后图标会出现在桌面上,双击运行 选择连接到本地服务器,登陆 点击转换计算机 这个,可以将本地计算机抓取成虚拟机,也可以将其他可以访问的计算机(需知道管理员用户名及密码)抓取成虚拟机.

《Hibernate学习笔记十二》学生、课程、分数关系的设计与实现

<Hibernate学习笔记十二>学生.课程.分数关系的设计与实现 这个马士兵老师的Hibernate视频学习的一个题目,这里面要用到多对多.多对一的关联关系以及联合主键,因此觉得挺好的,自己写篇博文来记录下. 先考虑数据库表 1.学生表:为简单起见,只考虑了学生id和学生姓名,其中id为主键 2.课程表:为简单起见,只考虑了课程id和课程名称,其中id为主键 3.分数表 分数表有两种解决方案 3.1 第一种为:使用联合主键:student_id 和 course_id 3.2 第二种:不使用

laravel3学习笔记(十二)

原作者博客:ieqi.net ==================================================================================================== 请求反射 HTTP 协议本身是无状态性的,但是在应用中处理各种业务逻辑时我们必须要有状态的把控,这样,折中的办法就是将状态进行标记然后嵌入到 HTTP 协议的请求中,然后应用根据这些标记来进行状态的串联以及处理.所以我们就要对请求进行反射处理以获取请求信息, Lara

java jvm学习笔记十二(访问控制器的栈校验机制)

欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 本节源码:http://download.csdn.net/detail/yfqnihao/4863854 这一节,我们会简单的描述一下jvm访问控制器的栈校验机制. 这节课,我们还是以实践为主,什么是栈校验机制,讲一百遍不如你自己实际的代码一下然后验证一下,下面我们下把环境搭起来. 第一步,配置系统环境.(copy吧,少年) path=%JAVA_HOME%/bin JAVA_HOME=C:/Java/jdk1.6

OC学习笔记十二 多态

一.多态概念 多态的前提,必须存在继承关系,在代码中的表现形式就是父类类型保存子类类型,即父类的指针指向子类对象. 二.多态特性 在OC中,调用方法是,会检测对象的真实类型,称为动态绑定. 父类保存子类指针,在调用方法时,有以下步骤: 1).在编译的时候,会检查 父类指向子类的指针 调用的方法,在父类中是否存在,如果有,编译通过 2).在运行时,会动态检测 初始对象 的真实类型 三.多态用途 提供动态绑定特性,减少不必要的程序冗余.在方法中,把父类当成参数,使该方法具备调用所有子类同样方法的能力

学习笔记十二 : squid

一 squid简介 二 squid 安装配置 三 案例

JavaScript权威设计--JavaScript函数(简要学习笔记十二)

1.作为命名空间的函数 有时候我们需要声明很多变量.这样的变量会污染全局变量并且可能与别人声明的变量产生冲突. 这时.解决办法是将代码放入一个函数中,然后调用这个函数.这样全局变量就变成了 局部变量. 如: function my(){ } my(); //别忘记调用 这段代码定义了一个单独的全局变量:名叫"my"的函数. 我们还可以这么写,定义一个匿名函数: (function(){ //这里第一个左括号是必须的,如果不写,JavaScript解析器会将 //function解析成函

JavaBean(web基础学习笔记十二)

一.JavaBean简介 JavaBean是使用Java语言开发的一个可重用的组件,在JSP的开发中可以使用JavaBean减少重复代码,使整个JSP代码的开发更简洁.JSP搭配JavaBean来使用, 有以下的优点: 可将HTML和Java代码分离,这主要是为了日后维护的方便.如果把所有的程序代码(HTML和Java)写到JSP 页面中,会使整个程序代码又多又复杂,造成日后维护上的困难. 可利用JavaBean的优点.将常用到的程序写成JavaBean组件,当在JSP要使用时,只要调用Java

Oracle学习笔记(十二)

十三.存储过程和存储函数1.掌握存储过程(相当于建立一个函数或者方法体,然后通过外部对其调用) 指存储在数据库中供所有程序调用的子程序叫做存储过程或存储函数. 相同点: 完成特定功能的程序 区别: 是否用return语句返回值 (1)创建和使用存储过程 用create procedure命令建立存储过程和存储函数 语法: create or replace procedure 过程名(参数列表) as PL/SQL 子程序体(说明部分); 事例: (a)打印一个存储过程:打印HelloWorld