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