新聞中心
歡迎回來,上篇我們講到了物理引擎中重力環(huán)境模擬以及主角考拉與地面墻壁的碰撞,相信大家已經(jīng)對(duì)2D世界的物理模擬有了一定的了解,現(xiàn)在我們接著講如何讓考拉動(dòng)起來吧!
讓考拉動(dòng)起來!
這里控制考拉移動(dòng)變得非常簡(jiǎn)單,它只有向前和跳兩個(gè)能力(源碼中我加了考拉向后走功能,建議大家自己加幾個(gè)虛擬按鍵來實(shí)現(xiàn)更非富的功能)如果你按著屏幕左半部考拉會(huì)向前走,按住右半部考拉會(huì)跳起來(原文設(shè)定考拉不會(huì)后退-_-)。
我們需要在Player.h里加兩個(gè)成員變量:
bool _forwardMarch; //是否向前走
bool _mightAsWellJump; //可以跳躍嗎?
在player.cpp的init方法或在構(gòu)造函數(shù)里把它們?cè)O(shè)置為false
在GameLevelLayer類里加上觸摸,在.h文件里加上:
virtual void registerWithTouchDispatcher(); void ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent); void ccTouchesMoved(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent); void ccTouchesEnded(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent);
在GameLevelLayer.cpp的init里加上(加載地圖代碼后)
setTouchEnabled(true); //設(shè)置可觸摸
然后寫注冊(cè)觸摸方法
void GameLevelLayer::registerWithTouchDispatcher() { CCDirector* pDirector = CCDirector::sharedDirector(); pDirector->getTouchDispatcher()->addStandardDelegate(this, 0); //注冊(cè)多點(diǎn)觸摸 }
現(xiàn)在,讓我們看看那三個(gè)觸摸事件吧!
void GameLevelLayer::ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent) { CCSetIterator iter = pTouches->begin(); for (; iter!=pTouches->end(); iter++) { CCTouch* pTouch = (CCTouch*)(*iter); CCPoint touchLocation = this->convertTouchToNodeSpace(pTouch); //把touch點(diǎn)位置轉(zhuǎn)換為本地坐標(biāo) if (touchLocation.x > 240) //在屏幕最右邊點(diǎn),就是跳 { _player->bMightAsWellJump = true; } else { _player->bForwardMarch = true; } } } void GameLevelLayer::ccTouchesEnded(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent) { _player->bForwardMarch = false; //松開按鍵時(shí),設(shè)置為不可跳也不是向前狀態(tài) _player->bMightAsWellJump = false; }
代碼一目了然,ccTouchesBegan時(shí)根據(jù)玩家按的位置決定了考拉狀態(tài)是前進(jìn)還是跳躍,松開按鍵時(shí)將這兩個(gè)狀態(tài)變量重置為false。
真正的角色移動(dòng)是在player的update方法里進(jìn)行的,看代碼:
void Player::update(float delta) { CCPoint gravity = ccp(0.f, -450.f); //考拉每秒下降450個(gè)單位 CCPoint gravityStep = ccpMult(gravity, delta); //計(jì)算在重力影響下delta時(shí)間內(nèi)具體下降了多少, 即dt時(shí)間后下落速度為多少 CCPoint forwardMove = ccp(800.f, 0.f); //前進(jìn)速度,每秒前進(jìn)800.f CCPoint forwardStep = ccpMult(forwardMove, delta); // 1 this->_velocity = ccpAdd(this->_velocity, gravityStep); //當(dāng)前速度=當(dāng)前速度+重力加速度 this->_velocity = ccp(this->_velocity.x *0.9f, this->_velocity.y); //2 if (this->bForwardMarch) { this->_velocity = ccpAdd(this->_velocity, forwardStep); //當(dāng)前速度要加上向前的速度矢量 }// 3 CCPoint minMovement = ccp(-120.f, -350.f); CCPoint maxMovement = ccp(120.0f, 250.f); this->_velocity = ccpClamp(this->_velocity, minMovement, maxMovement); //4 CCPoint stepVelocity = ccpMult(this->_velocity, delta); //計(jì)算下此速度下主角移動(dòng)了多少 this->_desiredPosition = ccpAdd(this->getPosition(), stepVelocity); //當(dāng)前期望要去的位置=當(dāng)前位置+當(dāng)前速度 }
讓我們來詳細(xì)地看一下新增部分:
當(dāng)玩家觸摸前進(jìn)的時(shí)候和重力模擬類似,我們加了一個(gè)向前的推進(jìn)力800.f,處理方式與重力相同
第2步橫向速度乘以0.9是模擬摩擦力效果,考慮下當(dāng)有向前推力時(shí)玩家會(huì)向前移動(dòng),沒有了呢?是不是立即停下來?那樣看起來太生硬了,所以這里x軸速度每幀乘以0.9就起到了均勻減速的效果,就像摩擦力一樣
檢查一下玩家是否按了前,按前進(jìn)時(shí)速度上就要加上向前的推動(dòng)力,就起到了向前進(jìn)的效果了
這一步是調(diào)用了clamp是限定速度前后上下不要太大,給它一個(gè)大值。之前的那句設(shè)定下落速度大值可以去掉了。
好了運(yùn)行一下,我們可以看到我們的主角小考拉現(xiàn)在可以前進(jìn)了!
讓考拉跳起來!
跳躍是動(dòng)作游戲里最明顯的一個(gè)特征。我們希望角色跳的平滑逼真,現(xiàn)在讓我們來實(shí)現(xiàn)它。
到player類的update方法里,在if(this->_forwardMarch)語句之前加上下面代碼:
CCPoint jumpForce = ccp(0.f, 310.f); if(this->_mightAsWellJump && this->_onGround) { this->_velocity = ccpAdd(this->_velocity, jumpForce); }
只要加一個(gè)向上的力,角色就可以跳起來了。
如果你止步于此,你會(huì)得到一個(gè)老式的雅代利式跳躍,即每次跳躍都是相同的高度,每次你都施給玩家一個(gè)同樣的力,然后等著重力把你拉回地面來。
這么做似乎沒什么不妥,如果你要求不高的話,但是仔細(xì)觀察一下各種流行的平臺(tái)游戲,如超級(jí)馬里奧,索尼克,洛克人,水上魂斗羅等,似乎玩家能夠通過按鍵的力度來控制跳躍的高度,達(dá)到更靈活的效果。那是怎么做到的呢?
其實(shí)實(shí)現(xiàn)這個(gè)效果也很簡(jiǎn)單,所謂玩家按鍵力度不過就是按鍵時(shí)間的長(zhǎng)久,按的時(shí)間長(zhǎng)也就是施加跳躍力的時(shí)間就長(zhǎng),跳的當(dāng)然高了,半路上如果玩家不給力了,當(dāng)然會(huì)跳到一半掉鏈子,不過玩家有種錯(cuò)覺就是想跳得高就使勁按著跳躍鍵,不想跳了松開鍵就是-_-
CCPoint jumpForce = ccp(0.f, 310.f); //向上的跳躍力 玩家一直按著跳躍鍵時(shí)的跳躍力 float jumpCutOff = 150.f; //玩家沒有按住跳躍鍵時(shí)的跳躍力 if(this->bMightAsWellJump && this->onGround) //如果當(dāng)前玩家按了跳躍鍵并且在地上 { this->_velocity = ccpAdd(this->_velocity, jumpForce); //跳躍就是加上一個(gè)向上的速度 } else if (!this->bMightAsWellJump && this->_velocity.y > jumpCutOff) //玩家沒有按住跳躍鍵,并且向上的速度已經(jīng)超過了設(shè)定的值,就限定向上跳躍速度 { this->_velocity = ccp(this->_velocity.x, jumpCutOff); }
注釋解釋的很清楚,就不多解釋了。
好了,build一下run下我們的游戲吧,看吧,我們的小考拉可以自由歡騰地上下翻飛了。
跳是跳的很歡了,不過悲劇的是,它跳到最右邊就跳出屏幕,看不見了。
來修正這個(gè)問題,這個(gè)問題其實(shí)就是視點(diǎn)跟隨,在cocos2dx上有解決這一問題的標(biāo)準(zhǔn)算法,貼出代碼:
void GameLevelLayer::setViewpointCenter(cocos2d::CCPoint pos) { CCSize winSize = CCDirector::sharedDirector()->getWinSize(); //限定角色不能超過半屏 int x = MAX(pos.x, winSize.width/2); int y = MAX(pos.y, winSize.height/2); //限定角色不能跑出屏幕 x = MIN(x, (_map->getMapSize().width * _map->getTileSize().width) - winSize.width/2); y = MIN(y, (_map->getMapSize().height * _map->getTileSize().height) - winSize.height/2); CCPoint actualPosition = ccp(x, y); CCPoint centerOfView = ccp(winSize.width/2, winSize.height/2); CCPoint viewPoint = ccpSub(centerOfView, actualPosition); //設(shè)定一下地圖的位置 _map->setPosition(viewPoint); }
方法參數(shù)就是玩家考拉當(dāng)前位置。這個(gè)方法可以不但能左右跟隨還能上下跟隨主角,非常好用。這個(gè)方法原理在很多博客都有提到,原理其實(shí)就是地圖在跟玩家做返方向運(yùn)動(dòng),大家可以查閱一下其他文章解釋它的原理,不過我在這里不想再多說了,不是一兩句能說清,我們只要會(huì)用這個(gè)方法就行了。
我們要做的就是在GameLevelLayer類的update方法里調(diào)用就行了,可以放在update方法結(jié)尾之前,如下:
this->setViewpointCenter(_player->getPosition());
現(xiàn)在build再run一下,這回我們的小考拉再也不會(huì)跑出屏幕外了!
嘗一下受傷的滋味!
現(xiàn)在我們可以著手做游戲過關(guān)和GameOver的功能了。
地圖里有個(gè)hazards層,這個(gè)層里放置一些考拉碰上就會(huì)掛的object。其實(shí)本質(zhì)也上碰撞檢測(cè),看代碼:
void GameLevelLayer::handleHazardCollisions(Player* player) { CCArray *tiles = this->getSurroundingTilesAtPosition(player->getPosition(), _hazards); CCDictionary* dic = NULL; CCObject* obj = NULL; float x=0.f; float y = 0.f; int gid = 0; CCARRAY_FOREACH(tiles, obj) { dic = (CCDictionary*)obj; x = dic->valueForKey("x")->floatValue(); y = dic->valueForKey("y")->floatValue(); CCRect tileRect = CCRectMake(x, y, _map->getTileSize().width, _map->getTileSize().height); CCRect pRect= player->collisionBoundingBox(); gid = dic->valueForKey("gid")->intValue(); if (gid && tileRect.intersectsRect(pRect)) //如果有釘刺并且玩家與它發(fā)生碰撞了,gameOver { this->gameOver(false); } } }
代碼看上去是不是很熟悉呀,你沒記錯(cuò),其實(shí)就是從checkAndResolveCollisions方法里拷來的,只不過沒分那么多情況而且處理方式也簡(jiǎn)單了只是調(diào)用了gameOver方法,gameOver的布爾值參數(shù)為true表示游戲勝利,為false就是失敗
_hazards在GameLevelLayer類也是個(gè)CCTMXLayer* 地圖層類型的成員變量,在此類的init方法里初始化它:
_hazards = _map->layerNamed("hazards");
然后在update方法里調(diào)用這個(gè)方法,現(xiàn)在update方法是這個(gè)樣子:
void GameLevelLayer::update(float delta) { _player->update(delta); this->handleHazardCollisions(_player); this->checkForAndResolveCollisions(_player); this->setViewpointCenter(_player->getPosition()); }
現(xiàn)在實(shí)現(xiàn)gameOver方法了,當(dāng)玩家跳到harads層里的刺上時(shí),我們會(huì)調(diào)用這個(gè)方法游戲結(jié)束,或者當(dāng)玩家走到終點(diǎn)時(shí),我也會(huì)調(diào)用這個(gè)方法,這個(gè)方法會(huì)顯示出一個(gè)restart按鈕,并在屏幕上打印出一些信息,告訴玩家你死了或者你贏了之類的-_-
void GameLevelLayer::gameOver(bool bWon) { bGameOver = true; CCString* gameText; if (bWon) { gameText = CCString::create("You Won!"); } else gameText = CCString::create("You have Died!"); CCLabelTTF* diedLabel = CCLabelTTF::create(gameText->getCString(), "Marker Felt", 40); diedLabel->setPosition(ccp(240, 200)); CCMoveBy *slideIn = CCMoveBy::create(1.f, ccp(0, 250)); CCMenuItemImage* replay = CCMenuItemImage::create("replay.png","replay.png","replay.png", this, menu_selector(GameLevelLayer::restartGame)); CCArray *menuItems = CCArray::create(); menuItems->addObject(replay); CCMenu *menu = CCMenu::create(); menu->addChild(replay); menu->setPosition(ccp(240, -100)); this->addChild(menu); this->addChild(diedLabel); menu->runAction(slideIn); }
方法開頭的變量bGameOver也是GameLevelLayer類新定義的一個(gè)成員變量,在init里要初始化為false,此變量表示游戲是否結(jié)束。這個(gè)變量作用是你在update方法里開頭判斷一下,如果bGameOver==true,則直接返回不要做任何事了。如下:
void GameLevelLayer::update(float delta) { if(bGameOver) return; _player->update(delta); this->handleHazardCollisions(_player); this->checkForAndResolveCollisions(_player); this->setViewpointCenter(_player->getPosition()); }
現(xiàn)在你再編譯運(yùn)行,碰上釘板試試,會(huì)出現(xiàn)杯具性的結(jié)局...
不要嘗試的太多,小心動(dòng)物保護(hù)組織會(huì)找上你家門來(-_-這就是所謂的美式幽默嗎?)
修正一個(gè)致命BUG
還記得前面出現(xiàn)過當(dāng)考拉掉出地圖出現(xiàn)的事情嗎?在游戲地圖里有些坑我們本意是考拉掉下去游戲會(huì)結(jié)束,但事實(shí)上程序崩掉了。凡是帶有TILED地圖的都會(huì)出現(xiàn)這樣的問題,只要角色走出地圖邊界游戲就會(huì)崩掉,這個(gè)問題困擾了好多人,也包括我。其實(shí)解決這個(gè)BUG很簡(jiǎn)單,關(guān)鍵在tileGIDAt方法。
此方法你打開源碼實(shí)現(xiàn)就知道,它有個(gè)CCASSERT宏,判斷出地圖就會(huì)拋出異常。本來嗎此方法是取地圖上某處tile的gid,你都出地圖了還取什么當(dāng)然要拋異常啦。這里我們就在getSurroundingTilesAtPosition方法里加個(gè)判斷,注意當(dāng)然要在調(diào)用tileGIDAt方法之前,不讓考拉出地圖:
if (tilePos.y > (_map->getMapSize().height-1)) { this->gameOver(false); return NULL; }
在checkForAndResolveCollisions方法里有調(diào)用getSurroundingTilesAtPosition方法,如果它返回個(gè)空數(shù)組可就不妙了,所以在checkForAndResolveCollisions也要改一下,在 CCArray* tiles = this->getSurroundingTilesAtPosition(player->getPosition(), _walls);一句之后,加上
if (bGameOver) //可能玩家掉坑里,就不處理了 { return; }
編譯再運(yùn)行,把考拉掉進(jìn)坑里試試,發(fā)現(xiàn)程序不再DOWN了!
Winner!
在最后,我們處理下考拉走到終點(diǎn)時(shí)顯示勝利的情況。
這里我們簡(jiǎn)單點(diǎn),只是判斷下考拉向右走了多遠(yuǎn)就取勝
void GameLevelLayer::checkForWin() { if (_player->getPositionX()>3130.0) { this->gameOver(true); } }
真實(shí)游戲里我們通常在終點(diǎn)放置一個(gè)object,如門呀城堡小旗什么的,主角碰到就勝利了可進(jìn)入下一關(guān),有興趣的自己嘗試!
這個(gè)方法也要放到GameLevelLayer的update方法里,如下:
void GameLevelLayer::update(float delta) { if(bGameOver) return; _player->update(delta); this->handleHazardCollisions(_player); this->checkForWin(); this->checkForAndResolveCollisions(_player); this->setViewpointCenter(_player->getPosition()); }
運(yùn)行一下,走到終點(diǎn)試試:
添加音效
勝利了之后沒有音樂怎么行,一個(gè)無聲的世界可真是會(huì)令人絕望呀,來我們加些音效吧!
在GameLevelLayer.cpp和 player.cpp加上頭文件如下:
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;
在GamelevelLayer的init方法里加上:
SimpleAudioEngine::shareEngine()->playBackgroundMusic("level1.mp3");
在player.cpp里的update方法里判斷玩家跳的地方加上音效:
if(this->bMightAsWellJump && this->onGround) //如果當(dāng)前玩家按了跳躍鍵并且在地上 { this->_velocity = ccpAdd(this->_velocity, jumpForce); //跳躍就是加上一個(gè)向上的速度 SimpleAudioEngine::sharedEngine()->playEffect("jump.wav"); }
在GameLevelLayer.cpp的gameOver方法里也加上音效:
void GameLevelLayer::gameOver(bool bWon) { if (bGameOver) //不要反復(fù)調(diào)用 { return; } bGameOver = true; SimpleAudioEngine::sharedEngine()->playEffect("hurt.wav");
編譯運(yùn)行,試一試你親手制作的帶有音樂感的游戲吧!
×××地址:下載
剩下我們還能做什么?
要做的還有什么,角色動(dòng)畫沒有吧,此外還有怪物,AI, 關(guān)卡,角色狀態(tài)機(jī)等等。這些都在IOS GAME三件套 Platformer Start Kit里,前面說過,本教程只是這個(gè)Start Kit的前奏曲,那真正的Platformer game是什么樣的呢?
學(xué)完此教程后,你可以學(xué)到
怎么樣?心動(dòng)了吧,涉于版權(quán)問題不便在此公開,有興趣的可到此看看: 橫版平臺(tái)游戲源碼
此外大名鼎鼎的三件套之一橫版格斗游戲 Beat 'Em Up Game Starter Kit 也是非常精彩哦!網(wǎng)上公開的源碼教程還不到里面的二十分之一-_-
有興趣可到此看看: 橫版格斗游戲源碼
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。
當(dāng)前名稱:cocos2d-x如何制作一個(gè)類馬里奧的橫版平臺(tái)動(dòng)作游戲續(xù)2-創(chuàng)新互聯(lián)
網(wǎng)站地址:http://fisionsoft.com.cn/article/dpioos.html