今天要學(xué)習(xí)的是coscos2d-x官網(wǎng)索引的一篇初級游戲教程。作為小白的入門,再適合不過了。
資源文件下載
工程×××
大師的源碼
環(huán)境:
lubuntu 13.10 32bit Android 4.1 cocos2d-x 2.2
下面就一步步來完成。
在Eclipse中刷新工程,Resources文件夾就出現(xiàn)了。
換一個(gè)狂拽酷炫點(diǎn)的圖標(biāo)
將android工程中res文件夾下的icon.png換成這個(gè)就可以了。
將下載好的資源文件挨個(gè)拷貝到Resources文件夾中,注意Classes 文件夾不要拷貝,fonts文件夾合并就好。
先將HelloWorldScence.cpp中的
USING_NS_CC;
移動(dòng)到HelloWorldScence.h中,這樣定義成員的時(shí)候就不用老加上命名空間了。
首先在HelloWorld.h中添加兩個(gè)成員:
private: CCSpriteBatchNode* batchNode; CCSprite* ship;
batchNode = CCSpriteBatchNode::create("Spritesheets/Sprites.pvr.ccz"); this->addChild(batchNode); CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("Spritesheets/Sprites.plist"); ship = CCSprite::createWithSpriteFrameName("SpaceFlier_sm_1.png"); ship->setPosition(ccp(origin.x + visibleSize.width * 0.1, origin.y + visibleSize.height * 0.5)); batchNode->addChild(ship,1);
在頭文件中繼續(xù)添加幾個(gè)相關(guān)的private成員:
CCParallaxNode *backgroundNode; CCSprite *spacedust1; CCSprite *spacedust2; CCSprite *planetsunrise; CCSprite *galaxy; CCSprite *spacialanomaly; CCSprite *spacialanomaly2;
這里用到了CCParallaxNode類,用它來實(shí)現(xiàn)背景,可以加強(qiáng)背景的層次感,在進(jìn)行背景卷動(dòng)的時(shí)候,可以實(shí)現(xiàn)越近的背景卷動(dòng)得越快,越遠(yuǎn)越慢。(想想在火車上 向外看窗外的景色)
// 1) Create the CCParallaxNode backgroundNode = CCParallaxNode::create(); this->addChild(backgroundNode, -1); // 2) Create the sprites will be added to the CCParallaxNode spacedust1 = CCSprite::create("Backgrounds/bg_front_spacedust.png"); spacedust2 = CCSprite::create("Backgrounds/bg_front_spacedust.png"); planetsunrise = CCSprite::create("Backgrounds/bg_planetsunrise.png"); galaxy = CCSprite::create("Backgrounds/bg_galaxy.png"); spacialanomaly = CCSprite::create("Backgrounds/bg_spacialanomaly.png"); spacialanomaly2 = CCSprite::create("Backgrounds/bg_spacialanomaly2.png"); // 3) Determine relative movement speeds for space dust and background CCPoint dustSpeed = ccp(0.1, 0.1); CCPoint bgSpeed = ccp(0.05, 0.05); // 4) Add children to CCParallaxNode backgroundNode->addChild(spacedust1, 0, dustSpeed, ccp(0,visibleSize.height/2) ); // 2 backgroundNode->addChild(spacedust2, 0, dustSpeed, ccp( spacedust1->getContentSize().width,visibleSize.height/2)); backgroundNode->addChild(galaxy, -1, bgSpeed, ccp(0, visibleSize.height * 0.7)); backgroundNode->addChild(planetsunrise, -1 , bgSpeed, ccp(600, visibleSize.height * 0)); backgroundNode->addChild(spacialanomaly, -1, bgSpeed, ccp(900, visibleSize.height * 0.3)); backgroundNode->addChild(spacialanomaly2, -1, bgSpeed, ccp(1500, visibleSize.height * 0.9));
原理就是每一Frame將背景向后移動(dòng)一定的距離,也就是背景圖片以一定的速度運(yùn)動(dòng)。
在頭文件中添加update函數(shù),將其聲明為似有成員函數(shù),這個(gè)函數(shù)是會(huì)自動(dòng)調(diào)用的。
private: // scheduled Update void update(float dt);
void HelloWorld::update(float dt) { CCPoint backgroundScrollVert = ccp(-1000,0); backgroundNode->setPosition(ccpAdd(backgroundNode->getPosition(), ccpMult(backgroundScrollVert, dt))); }
this->scheduleUpdate();
而CCParallaxNode也沒有對應(yīng)的循環(huán)的方法,這里就要自定義類了。這里自定義一個(gè)CCParallaxNodeExtras類,繼承 CCParallaxNode。主要是添加一個(gè) incrementOffset() 方法,用戶實(shí)現(xiàn)循環(huán)顯示。
CCParallaxNodeExtras.h
#ifndef Cocos2DxFirstIosSample_CCParallaxNodeExtras_h #define Cocos2DxFirstIosSample_CCParallaxNodeExtras_h #include "cocos2d.h" USING_NS_CC; class CCParallaxNodeExtras : public CCParallaxNode { public : // Need to provide a constructor CCParallaxNodeExtras(); // just to avoid ugly later cast and also for safety static CCParallaxNodeExtras * node(); // Facility method (it’s expected to have it soon in COCOS2DX) void incrementOffset(CCPoint offset, CCNode* node); } ; #endif
#include "CCParallaxNodeExtras.h" // Hack to access CCPointObject (which is not a public class) class CCPointObject : CCObject { CC_SYNTHESIZE(CCPoint, m_tRatio, Ratio) CC_SYNTHESIZE(CCPoint, m_tOffset, Offset) CC_SYNTHESIZE(CCNode *, m_pChild, Child) // weak ref }; // Need to provide a constructor CCParallaxNodeExtras::CCParallaxNodeExtras() { CCParallaxNode(); // call parent constructor } CCParallaxNodeExtras * CCParallaxNodeExtras::node() { return new CCParallaxNodeExtras(); } void CCParallaxNodeExtras::incrementOffset(CCPoint offset,CCNode* node){ for( unsigned int i = 0; i < m_pParallaxArray->num; i++) { CCPointObject *point = (CCPointObject *)m_pParallaxArray->arr[i]; CCNode * curNode = point->getChild(); if( curNode->isEqual(node) ) { point->setOffset( ccpAdd(point->getOffset(), offset) ); break; } } }
在這里還定義了一個(gè)內(nèi)部類CCPointObject,關(guān)于CC_SYNTHESIZEz這個(gè)宏
#define CC_PROPERTY(varType, varName, funName)\ protected: varType varName;\ public: virtual varType get##funName(void);\ public: virtual void set##funName(varType var);
對原HelloWorldScence.cpp進(jìn)行一定的修改,首先backgroundNode的聲明要修改:
CCParallaxNodeExtras *backgroundNode;
backgroundNode = CCParallaxNodeExtras::node();
CCArray *spaceDusts = CCArray::arrayWithCapacity(2) ; spaceDusts->addObject(_spacedust1) ; spaceDusts->addObject(_spacedust2) ; for ( int ii = 0 ; ii <spaceDusts->count() ; ii++ ) { CCSprite * spaceDust = (CCSprite *)(spaceDusts->objectAtIndex(ii)) ; float xPosition = _backgroundNode->convertToWorldSpace(spaceDust->getPosition()).x ; float size = spaceDust->getContentSize().width ; if ( xPosition < -size ) { _backgroundNode->incrementOffset(ccp(spaceDust->getContentSize().width*2,0),spaceDust) ; } } CCArray *backGrounds = CCArray::arrayWithCapacity(4) ; backGrounds->addObject(_galaxy) ; backGrounds->addObject(_planetsunrise) ; backGrounds->addObject(_spacialanomaly) ; backGrounds->addObject(_spacialanomaly2) ; for ( int ii = 0 ; ii <backGrounds->count() ; ii++ ) { CCSprite * background = (CCSprite *)(backGrounds->objectAtIndex(ii)) ; float xPosition = _backgroundNode->convertToWorldSpace(background->getPosition()).x ; float size = background->getContentSize().width ; if ( xPosition < -size ) { _backgroundNode->incrementOffset(ccp(2000,0),background) ; } }
SOURCES = main.cpp \ ../Classes/AppDelegate.cpp \ ../Classes/HelloWorldScene.cpp\ ../Classes/CCParallaxNodeExtras.cpp
android版本的編譯需要修改proj.android/jni/Android.mk
LOCAL_SRC_FILES := hellocpp/main.cpp \ ../../Classes/AppDelegate.cpp \ ../../Classes/HelloWorldScene.cpp\ ../../Classes/CCParallaxNodeExtras.cpp
我們還可以在場景中添加一些星空的效果,可以用內(nèi)置的粒子系統(tǒng)來實(shí)現(xiàn),在init() 的后面添加下面的語句:
//Add some stars HelloWorld::addChild(CCParticleSystemQuad::create("Particles/Stars1.plist")); HelloWorld::addChild(CCParticleSystemQuad::create("Particles/Stars2.plist")); HelloWorld::addChild(CCParticleSystemQuad::create("Particles/Stars3.plist"));
這里用到了移動(dòng)設(shè)備的重力傳感器。cocos2d-x對加速計(jì)進(jìn)行了封裝,我們可以不用關(guān)心具體平臺(tái)api,直接使用抽象后的加速計(jì)api就可以了。
首先是要實(shí)現(xiàn)基類的關(guān)于傳感器的虛函數(shù),在頭文件中添加:
virtual void didAccelerate(CCAcceleration* pAccelerationValue);
void HelloWorld::didAccelerate(CCAcceleration* pAccelerationValue) { #define KFILTERINGFACTOR 0.1 #define KRESTACCELX -0.6 #define KMAXDIFFY 0.2 #define KMAXDIFFX 0.1 double rollingX = 0.0; double rollingY = 0.0; // Cocos2DX inverts X and Y accelerometer depending on device orientation // in landscape mode right x=-y and y=x !!! (Strange and confusing choice) //exchange value float tmp; tmp = pAccelerationValue->x; pAccelerationValue->x = pAccelerationValue->y; pAccelerationValue->y = tmp; rollingX = (pAccelerationValue->x * KFILTERINGFACTOR); rollingY = (pAccelerationValue->y * KFILTERINGFACTOR); float accelX = pAccelerationValue->x - rollingX; float accelY = pAccelerationValue->y - rollingY; CCSize winSize = CCDirector::sharedDirector()->getWinSize(); float accelDiffx = accelX - KRESTACCELX; float accelDiffy = accelY; float accelFractionx = accelDiffx / KMAXDIFFX; float accelFractiony = accelDiffy / KMAXDIFFY; shipPointsPerSecY = 0.5 * winSize.height * accelFractionx; shipPointsPerSecX = 0.5 * winSize.width * accelFractiony; }
這個(gè)函數(shù)在每次檢測到傳感器的信息之后就會(huì)回調(diào),傳回一個(gè)CCAcceleration對象,這里只要x方向的值就可以了。注意這里還對傳感器的值進(jìn)行了一定的處理,讓小飛機(jī)有一種飛行的感覺,同時(shí)速度是和屏幕大小對應(yīng)的,算是多設(shè)備匹配的一個(gè)trick。
接下來在update() 中更行飛船的位置,在后面添加下面的代碼:
CCSize winSize = CCDirector::sharedDirector()->getWinSize(); float maxY = winSize.height - ship->getContentSize().height/2; float minY = ship->getContentSize().height/2; float maxX = winSize.width - ship->getContentSize().width/2; float minX = ship->getContentSize().width/2; float diffy = (shipPointsPerSecY * dt); float diffx = (shipPointsPerSecX * dt); float newY = ship->getPosition().y + diffy; float newX = ship->getPosition().x + diffx; newY = MIN(MAX(newY, minY), maxY); newX = MIN(MAX(newX, minX), maxX); ship->setPosition(ccp(newX, newY));
最后就是啟動(dòng)傳感器的檢測了,在init()中添加:
this->setAccelerometerEnabled(true);
在HelloWorld.h中添加幾個(gè)私有成員變量,
CCArray* asteroids; int nextAsteroid; float nextAsteroidSpawn;
添加幾個(gè)public輔助函數(shù):
//For get random float value float randomValueBetween(float low, float high); //Set visibility of Node void setInvisible(CCNode * node); //Get millisecs current sys-time float getTimeTick();
在init()中對成員進(jìn)行初始化:
//init asteroids asteroids = new CCArray(); //Store asteroids for(int i = 0; i < 15; ++i) { CCSprite *asteroid = CCSprite::createWithSpriteFrameName("asteroid.png"); asteroid->setVisible(false); batchNode->addChild(asteroid); asteroids->addObject(asteroid); } nextAsteroid = 0; nextAsteroidSpawn = 0.0;
float HelloWorld::randomValueBetween(float low, float high) { return (((float) 2 * rand() / 0xFFFFFFFFu) * (high - low)) + low; } float HelloWorld::getTimeTick() { timeval time; gettimeofday(&time, NULL); unsigned long millisecs = (time.tv_sec * 1000) + (time.tv_usec/1000); return (float) millisecs; } void HelloWorld::setInvisible(CCNode * node) { node->setVisible(false); }
//Set asteroids float curTimeMillis = getTimeTick(); if (curTimeMillis > nextAsteroidSpawn) { float randMillisecs = randomValueBetween(0.20,1.0) * 1000; nextAsteroidSpawn = randMillisecs + curTimeMillis; float randY = randomValueBetween(0.0,winSize.height); float randDuration = randomValueBetween(2.0,10.0); CCSprite *asteroid = (CCSprite *)asteroids->objectAtIndex(nextAsteroid); nextAsteroid++; if (nextAsteroid >= (int)asteroids->count()) nextAsteroid = 0; asteroid->stopAllActions(); asteroid->setPosition( ccp(winSize.width+asteroid->getContentSize().width/2, randY)); asteroid->setVisible(true); asteroid->runAction(CCSequence::create(CCMoveBy::create(randDuration, ccp(-winSize.width-asteroid->getContentSize().width, 0)), CCCallFuncN::create(this, callfuncN_selector(HelloWorld::setInvisible)), NULL // DO NOT FORGET TO TERMINATE WITH NULL (unexpected in C++) )); }
和小行星的添加類似,首先修改頭文件
CCArray* shipLasers; int nextShipLaser;
//init lasers shipLasers = new CCArray(); for(int i = 0; i < 5; ++i) { CCSprite *shipLaser = CCSprite::createWithSpriteFrameName("laserbeam_blue.png"); shipLaser->setVisible(false); batchNode->addChild(shipLaser); shipLasers->addObject(shipLaser); } this->setTouchEnabled(true); nextShipLaser = 0;
這里的碰撞檢測包括兩個(gè)部分:激光和小行星的碰撞,飛機(jī)和小行星的碰撞。
首先添加一個(gè)私有成員,用于記錄飛機(jī)的生命值。
int lives;
在init()添加對其的初始化:
lives = 3;
在update()中添加檢測的操作:
//Collision Detection //Asteroids CCObject* asteroid; CCObject* shipLaser; CCARRAY_FOREACH(asteroids, asteroid){ if (!((CCSprite *) asteroid)->isVisible() ) continue; CCARRAY_FOREACH(shipLasers, shipLaser){ if (!((CCSprite *) shipLaser)->isVisible()) continue; if (((CCSprite *) shipLaser)->boundingBox().intersectsRect(((CCSprite *)asteroid)->boundingBox()) ) { ((CCSprite *)shipLaser)->setVisible(false); ((CCSprite *)asteroid)->setVisible(false); continue; } } if (ship->boundingBox().intersectsRect(((CCSprite *)asteroid)->boundingBox()) ) { ((CCSprite *)asteroid)->setVisible(false); ship->runAction( CCBlink::create(1.0, 9)); lives--; } }
理論上當(dāng)飛機(jī)生命沒有的時(shí)候,游戲就要結(jié)束了。
這里游戲的勝利條件是堅(jiān)持了60s,失敗條件是飛機(jī)被撞擊了三次。
添加一個(gè)枚舉聲明,表示游戲結(jié)束的原因:
typedef enum { WIN, LOSE } EndReason;
添加私有成員:
double gameOverTime; bool gameOver;
//Called when game ended void endScene(EndReason endReason); //Restart game void restartTapped();
void HelloWorld::restartTapped() { CCDirector::sharedDirector()->replaceScene (CCTransitionZoomFlipX::create(0.5, this->scene())); // reschedule this->scheduleUpdate(); } void HelloWorld::endScene( EndReason endReason ) { if (gameOver) return; gameOver = true; CCSize winSize = CCDirector::sharedDirector()->getWinSize(); char message[10] = "You Win"; if ( endReason == LOSE) strcpy(message,"You Lose"); CCLabelBMFont * label ; label = CCLabelBMFont::create(message, "fonts/Arial.fnt"); label->setScale(0.1); label->setPosition(ccp(winSize.width/2 , winSize.height*0.6)); this->addChild(label); CCLabelBMFont * restartLabel; strcpy(message,"Restart"); restartLabel = CCLabelBMFont::create(message, "fonts/Arial.fnt"); CCMenuItemLabel *restartItem = CCMenuItemLabel::create(restartLabel, this, menu_selector(HelloWorld::restartTapped) ); restartItem->setScale(0.1); restartItem->setPosition( ccp(winSize.width/2, winSize.height*0.4)); CCMenu *menu = CCMenu::create(restartItem, NULL); menu->setPosition(CCPointZero); this->addChild(menu); // clear label and menu restartItem->runAction(CCScaleTo::create(0.5, 1.0)); label ->runAction(CCScaleTo::create(0.5, 1.0)); // Terminate update callback this->unscheduleUpdate(); }
運(yùn)行效果如下:
游戲中的音效可以為游戲添色不少。
首先將蘋果的caf格式轉(zhuǎn)換成android支持的wav.終端cd到工程目錄下的 Resources/Sounds ,執(zhí)行下面的命令(事先裝好ffmpeg)
ffmpeg -i SpaceGame.caf SpaceGame.wav
ffmpeg -i explosion_large.caf explosion_large.wav
ffmpeg -i laser_ship.caf laser_ship.wav
順利的話Sounds文件夾下面就生成了轉(zhuǎn)好碼的音頻文件了。
在HelloWorld.h 中添加頭文件和命名空間:
#include "SimpleAudioEngine.h" using namespace CocosDenshion;
在init() 中加入對音效的初始化,這里播放背景樂,同時(shí)預(yù)加載小行星爆炸的音效和發(fā)射激光的音效。
SimpleAudioEngine::sharedEngine()->playBackgroundMusic("SpaceGame.wav",true); SimpleAudioEngine::sharedEngine()->preloadEffect("explosion_large.wav"); SimpleAudioEngine::sharedEngine()->preloadEffect("laser_ship.wav");
在碰撞檢測中對應(yīng)位置加入
SimpleAudioEngine::sharedEngine()->playEffect("Sounds/explosion_large.wav");
在觸摸函數(shù)中添加
SimpleAudioEngine::sharedEngine()->playEffect("Sounds/laser_ship.wav");
發(fā)現(xiàn)linux版本不能正常編譯,報(bào)錯(cuò):
fatal error: SimpleAudioEngine.h: No such file or directory compilation terminated.
修改proj.linux/MakeFile
EXECUTABLE = SpaceGame INCLUDES = -I.. -I../Classes \ -I$(COCOS_ROOT)/CocosDenshion/include SOURCES = main.cpp \ ../Classes/AppDelegate.cpp \ ../Classes/HelloWorldScene.cpp\ ../Classes/CCParallaxNodeExtras.cpp COCOS_ROOT = ../../.. include $(COCOS_ROOT)/cocos2dx/proj.linux/cocos2dx.mk SHAREDLIBS += -lcocos2d -lcocosdenshion COCOS_LIBS = $(LIB_DIR)/libcocos2d.so $(TARGET): $(OBJECTS) $(STATICLIBS) $(COCOS_LIBS) $(CORE_MAKEFILE_LIST) @mkdir -p $(@D) $(LOG_LINK)$(CXX) $(CXXFLAGS) $(OBJECTS) -o $@ $(SHAREDLIBS) $(STATICLIBS) $(OBJ_DIR)/%.o: %.cpp $(CORE_MAKEFILE_LIST) @mkdir -p $(@D) $(LOG_CXX)$(CXX) $(CXXFLAGS) $(INCLUDES) $(DEFINES) $(VISIBILITY) -c $< -o $@ $(OBJ_DIR)/%.o: ../%.cpp $(CORE_MAKEFILE_LIST) @mkdir -p $(@D) $(LOG_CXX)$(CXX) $(CXXFLAGS) $(INCLUDES) $(DEFINES) $(VISIBILITY) -c $< -o $@
主要是搜索路徑和庫的鏈接。然后linux版本就可以編譯了。
android版本直接編譯運(yùn)行即可。
到此為止,已經(jīng)基本完成了一個(gè)聲色俱全的移動(dòng)平臺(tái)游戲了~
可以做的還有很多,比如
1.給飛機(jī)添加血量的顯示;
2.添加爆炸的動(dòng)畫;
3.添加敵機(jī);
4.遭遇大boss
...
Cocos2D-X Tutorial for iOS and Android: Space Game
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。