學習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