程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> Cocos2d-x加Box2D制作彈弓類游戲

Cocos2d-x加Box2D制作彈弓類游戲

編輯:關於C語言

文章原版為英文版,地址鏈接在文章尾部給出。原文代碼版本為object-c版,本文代碼版本為C++版。對原文大部分內容進行了翻譯,並將對oc版的說明更改為C++版。文章cocos2d-x版本cocos2d-1.0.1-x-0.11.0。

如何用Box2D和cocos2d-x制作彈弓類游戲 第一部分

這是一篇由ios教程團隊成員Gustavo Ambrozio上傳的博客。一位擁有超過20年軟件開發經驗,超過3年ios開發經驗的軟件工程師,CodeCrop軟件創始人。

在這個教程系列中我們將會通過使用cocos2d-x和Box2D創建一個很COOL的彈弓類型游戲。

我們將使用Ray的可愛而富有天賦的老婆Vicki創作的彈弓,栗子,狗,貓和憤怒的松鼠素材來創建游戲。素材我會在上傳附件)

 

在這個教程系列,你將學到:

  • 怎麼用旋轉關節(rotation joints)
  • 怎麼用連接關節(weld joints)
  • 怎麼讓視角跟隨拋射物
  • 怎麼根據碰撞檢測判斷力量來消除敵人
  • 和很多其他的

 

這個教程系列假設你已經掌握了  Intro to Box2D with Cocos2D Tutorial: Bouncing Balls Tutorial或者已經掌握了相關知識。

教程中還會使用很多制作撞球游戲中的概念。

 

開始吧

新建HelloWorld項目,清空項目。記得選擇需要Box2d支持的cocos2d-x工程。聲明一個catapult類。和HelloWorld類除了名字全都一樣。

 

加入些精靈

 

首先我們先添加項目將用的資源。

現在我們來加入些不會被物理模擬的精靈。默認的CCSprite的錨點是中心,我將錨點改到了左下角為了更容易的放置它們。

在init方法中// add your codes below...下面添加代碼:

  1. CCSprite *sprite = CCSprite::spriteWithFile("bg.png");  //背景圖 
  2.         sprite->setAnchorPoint(CCPointZero); 
  3.         this->addChild(sprite, -1); 
  4.      
  5.         CCSprite *sprite = CCSprite::spriteWithFile("catapult_base_2.png"); //投射器底部後面那塊 
  6.         sprite->setAnchorPoint(CCPointZero); 
  7.         sprite->setPosition(CCPointMake(181.0, FLOOR_HEIGHT)); 
  8.         this->addChild(sprite, 0); 
  9.      
  10.         sprite = CCSprite::spriteWithFile("squirrel_1.png");        //左邊松鼠 
  11.         sprite->setAnchorPoint(CCPointZero); 
  12.         sprite->setPosition(CCPointMake(11.0, FLOOR_HEIGHT)); 
  13.         this->addChild(sprite, 0); 
  14.      
  15.         sprite = CCSprite::spriteWithFile("catapult_base_1.png");   //投射器底部前面那塊 
  16.         sprite->setAnchorPoint(CCPointZero); 
  17.         sprite->setPosition(CCPointMake(181.0, FLOOR_HEIGHT)); 
  18.         this->addChild(sprite, 9); 
  19.      
  20.         sprite = CCSprite::spriteWithFile("squirrel_2.png");    //右邊松鼠 
  21.         sprite->setAnchorPoint(CCPointZero); 
  22.         sprite->setPosition(CCPointMake(240.0, FLOOR_HEIGHT)); 
  23.         this->addChild(sprite, 9); 
  24.      
  25.         sprite = CCSprite::spriteWithFile("fg.png");    //帶冰的地面 
  26.         sprite->setAnchorPoint(CCPointZero); 
  27.         this->addChild(sprite, 10); 

你也許注意到了很多使用Y坐標的地方用了宏FLOOR_HEIGHT,但我們並未define它。

  1. #define FLOOR_HEIGHT    62.0f 

定義了這個宏之後,如果我們改變了地板高度,我們可以更加簡便的放置精靈。

上效果圖。

看起來不錯!

上面就是非物理模擬的部分。

 

 

增加彈弓臂

是時候給世界加些物理屬性了,接下來的代碼就是加世界邊框的模板式的代碼了,讓我們改變一點來描述我們的世界。

類聲明中添加:

  1. private: 
  2.     b2World* m_world; 
  3.     b2Body* m_groundBody; 

init方法尾部添加:

  1. b2Vec2 gravity; 
  2. gravity.Set(0.0f, -10.0f); 
  3. bool doSleep = true; 
  4. m_world = new b2World(gravity); 
  5. m_world->SetAllowSleeping(doSleep); 
  6. m_world->SetContinuousPhysics(true); 
  7.  
  8. // Define the ground body. 
  9. b2BodyDef groundBodyDef; 
  10. groundBodyDef.position.Set(0, 0); // bottom-left corner 
  11.  
  12. // Call the body factory which allocates memory for the ground body 
  13. // from a pool and creates the ground box shape (also from a pool). 
  14. // The body is also added to the world. 
  15. m_groundBody = m_world->CreateBody(&groundBodyDef); 

默認是世界的尺寸是iphone屏幕尺寸。因為我們場景的寬度是世界寬度的2被。完成這個任務我們只需要讓寬度乘以1.5.

另外,由於我們世界的地板並不是在屏幕的底部,所以我們需要編寫相應的代碼。

邊界代碼:

  1. b2EdgeShape groundBox; 
  2.     // bottom 
  3.     groundBox.Set(b2Vec2(0,FLOOR_HEIGHT/PTM_RATIO), b2Vec2(screenSize.width*2.0f/PTM_RATIO,FLOOR_HEIGHT/PTM_RATIO)); 
  4.     m_groundBody->CreateFixture(&groundBox, 0); 
  5.      
  6.     // top 
  7.     groundBox.Set(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*2.0f/PTM_RATIO,screenSize.height/PTM_RATIO)); 
  8.     m_groundBody->CreateFixture(&groundBox, 0); 
  9.      
  10.     // left 
  11.     groundBox.Set(b2Vec2(0,screenSize.height/PTM_RATIO), b2Vec2(0,0)); 
  12.     m_groundBody->CreateFixture(&groundBox, 0); 
  13.      
  14.     // right 
  15.     groundBox.Set(b2Vec2(screenSize.width*1.5f/PTM_RATIO,screenSize.height/PTM_RATIO), b2Vec2(screenSize.width*1.5f/PTM_RATIO,0)); 
  16.     m_groundBody->CreateFixture(&groundBox, 0); 

說明:box2d某次更新後以前的SetAsEdge函數被刪除了,但是可以使用b2EdgeShape類型對象來生成邊界,函數名也變為Set。

現在讓我們增加彈弓臂,首先增加物體(body)和夾具(fixture)的指針。打開HelloWorld.h把下面的代碼加入到類中。

  1. private: 
  2.     b2Fixture *m_armFixture; 
  3.     b2Body *m_armBody; 

進入到HelloWorld.cpp文件中的init函數的底部:

  1. // Create the catapult's arm 
  2.     CCSprite *arm = CCSprite::spriteWithFile("catapult_arm.png"); 
  3.     this->addChild(arm, 1); 
  4.      
  5.     b2BodyDef armBodyDef; 
  6.     armBodyDef.type = b2_dynamicBody; 
  7.     armBodyDef.linearDamping = 1; 
  8.     armBodyDef.angularDamping = 1; 
  9.     armBodyDef.position.Set(230.0f/PTM_RATIO, (FLOOR_HEIGHT+91.0f)/PTM_RATIO); 
  10.     armBodyDef.userData = arm; 
  11.     m_armBody = m_world->CreateBody(&armBodyDef); 
  12.      
  13.     b2PolygonShape armBox; 
  14.     b2FixtureDef armBoxDef; 
  15.     armBoxDef.shape = &armBox; 
  16.     armBoxDef.density = 0.3F; 
  17.     armBox.SetAsBox(11.0f/PTM_RATIO, 91.0f/PTM_RATIO); 
  18.     m_armFixture = m_armBody->CreateFixture(&armBoxDef); 

 

 

你如果看過之前的Box2D教程那麼這些代碼對你而言應該很熟悉。

我們先讀取彈弓臂精靈並把它加入到層中。注意z軸索引。當我們向scene中加入靜態精靈時候我們使用Z軸索引。

讓我們的彈弓臂位於2塊彈弓底部之間看起來不錯!

類聲明中增加:

  1. void tick(cocos2d::ccTime dt); 

cpp文件增加:

  1. void HelloWorld::tick(ccTime dt) 
  2.     int velocityIterations = 8; 
  3.     int positionIterations = 1; 
  4.  
  5.     m_world->Step(dt, velocityIterations, positionIterations); 
  6.  
  7.     //Iterate over the bodies in the physics world 
  8.     for (b2Body* b = m_world->GetBodyList(); b; b = b->GetNext()) 
  9.     { 
  10.         if (b->GetUserData() != NULL) { 
  11.             //Synchronize the AtlasSprites position and rotation with the corresponding body 
  12.             CCSprite* myActor = (CCSprite*)b->GetUserData(); 
  13.             myActor->setPosition( CCPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO) ); 
  14.             myActor->setRotation( -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()) ); 
  15.         }    
  16.     } 
  17. }

在init方法尾部加入:

  1. schedule(schedule_selector(Catapult::tick)); 

看到沒?我們並沒有設置精靈的位置,因為tick方法會更正精靈的位置到box2D物體的位置。

接下來我們創建box2d物體作為一個動態物體。userData屬性在這裡很重要,因為正如我上段提到的,精靈會跟隨物體。

另外注意到坐標被設置到在FLOOR_HEIGHT之上。因為我們這裡使用的坐標是物體的中心,我們不能用左下角在使用Box2d時候。

接下來就是創建物體物理特性的夾具,一個簡單的矩形。

我們設置物體夾具的大小比精靈尺寸小一點,因為精靈尺寸比實際彈弓臂圖案尺寸大一點。

在這幅圖中,黑色矩形框是精靈尺寸,紅色矩形框是夾具尺寸。

運行你會看到機器臂直立著。

 

 

旋轉關節

我們需要某種約束來限制投射器的轉動在一定角度內。借助關節(joints)你可以約束Box2D關聯物體運動.

有一種特殊的關節可以完美解決我們的問題——旋轉關節revolute joint)。想象一個釘子將2個物體釘在一個特殊的點,但仍然允許他們轉動。

讓我們試試吧!回到HelloWorld.h在類中加入屬性:

  1. b2RevoluteJoint *m_armJoint; 

回到類實現文件在生成發射器臂之後加入下面的代碼:

  1. b2RevoluteJointDef armJointDef; 
  2. armJointDef.Initialize(m_groundBody, m_armBody, b2Vec2(233.0f/PTM_RATIO, FLOOR_HEIGHT/PTM_RATIO)); 
  3. armJointDef.enableMotor = true; 
  4. armJointDef.enableLimit = true; 
  5. armJointDef.motorSpeed  = -10; //-1260; 
  6. armJointDef.lowerAngle  = CC_DEGREES_TO_RADIANS(9); 
  7. armJointDef.upperAngle  = CC_DEGREES_TO_RADIANS(75); 
  8. armJointDef.maxMotorTorque = 700; 
  9. m_armJoint = (b2RevoluteJoint*)m_world->CreateJoint(&armJointDef); 

 

 

當我們創建關節時你不得不修改2個物體和連接點。你可能會想:“我們不應該把投射器臂連接到投射器底部嗎》”。在現實世界中,沒錯。

但是在Box2D中這可不是必要的。你可以這樣做但你不得不再為投射器底部創建另一個物體並增加了模擬的復雜性。

由於投射器底部在何時都是靜態的並且在Box2d中樞紐物體hinge body)不必要在其他的任何物體中,我們可以只使用我們已擁有的大地物體groundBody)。

角度限制外加馬達motor)然我們的投射器更加像真實世界中的投射器。

你也許會注意到我們在關節上設置一個馬達激活,通過設置“enableMotor”,“motorSpeed”,和“maxMotorTorque”。

通過設置馬達速度為負,可以使投射器臂持續的順時針轉動。

然而,我們還需要通過設置”enableLimit“,”lowerAngle“,”upperAngle“激活關節。這讓關節活動范圍角度9到75°。這如我們所想的那樣模擬投射器運動。

然後我們為了向後拉動投射器將增加另一個關節。當我們釋放這個力後馬達會讓投射器臂向前運動,更像真實的投射器啦!

馬達的速度值是每秒弧度值。沒錯,不是很直觀,我知道。我所做的就是不聽修改值直到獲得了我想的效果。你可以從小的值開始增加知道你獲得了期望的速度。最大馬達扭矩maxMotorTorque)是馬達可達的最大扭矩。你可以改變這個值來看看物體的反應。那麼你會清楚他的作用。

運行app你會看到投射器臂位置現在偏左了:

 

 

 

推動投射器臂吧!

好的,現在是時候移動這個投射器臂啦!為了完成這個任務我們將會使用鼠標關節mouse joint)。如果你讀了雷的彈球游戲教程你就一定已經知道鼠標關節是什麼了。

但你沒讀過,這裡是Ray的定義:

 

“In Box2D, a mouse joint is used to make a body move toward a specified point.”

那就是我們正需要的,所以,然我們先聲明一個鼠標關節變量在類定義中:

  1. private:     
  2.     b2MouseJoint *m_mouseJoint; 
  3.  
  4. public: 
  5.     virtual void ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event); 
  6.     virtual void ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event); 
  7.     virtual void ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event); 

 

接下來在類實現文件增加CCTouchesBegan方法:

  1. void Catapult::ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event) 
  2.     if (m_mouseJoint != NULL)   {       return;     } 
  3.  
  4.     CCTouch *touch = (CCTouch *)touches->anyObject(); 
  5.     CCPoint location = touch->locationInView(touch->view()); 
  6.     location = CCDirector::sharedDirector()->convertToGL(location); 
  7.     b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO); 
  8.  
  9.     if (locationWorld.x < m_armBody->GetWorldCenter().x + 150.0/PTM_RATIO) 
  10.     { 
  11.         b2MouseJointDef md; 
  12.         md.bodyA = m_groundBody; 
  13.         md.bodyB = m_armBody; 
  14.         md.target = locationWorld; 
  15.         md.maxForce = 2000; 
  16.  
  17.         m_mouseJoint = (b2MouseJoint *)m_world->CreateJoint(&md); 
  18.     } 

在init方法中調用tick方法代碼前:

  1. m_mouseJoint = NULL; 

 

又是引用Ray的話:

"When you set up a mouse joint, you have to give it two bodies. The first isn’t used, but

the convention is to use the ground body. The second is the body you want to move”.

當你創建一個鼠標關節,你不得不給他2個物體,第一個實際上並不會被使用,但為了方便就把ground body賦給它吧,第二個是你想移動的物體。

目標就是我們想要用關節來移動物體。我們首先要將觸摸點左邊轉換成cocos2d-x坐標在轉換為Box2D坐標。我們只在觸摸在投射器臂物體左邊時才會創建關節。50像素的偏移讓我們可以觸摸投射器臂稍微偏右的位置。

最大力參數將決定應用在投射器臂上跟隨目標點移動的最大力。就我們而言,我們不得不確保它足夠強壯來抵消被應用與旋轉關節的馬達的扭矩。

In our case we have to make it strong enough to counteract the torque applied by the motor of the revolute joint.

如果你把這個值設置的太小,那麼你將不能拉回投射器臂,因為它的扭矩過大。你可以減小我們的轉動關節的最大馬達扭矩maxMotorTorque)或增大最大力maxForce)參數。

為了確定什麼值合適我建議你用下鼠標關節的最大力參數和旋轉關節的最大馬達扭矩參數。減小maxForce到500並試驗,你將看到你不能推動投射器臂。那麼減小maxMotorTroque到1000你將看到你又可以推動它了。但讓我們先完成這些的實現。。

 

我們現在不得不實現CCTouchesMoved方法,這樣鼠標關節會跟隨你的觸摸:

  1. void Catapult::ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event) 
  2.     if (m_mouseJoint == NULL)   {   return; } 
  3.  
  4.     CCTouch *touch = (CCTouch *)touches->anyObject(); 
  5.     CCPoint location = touch->locationInView(touch->view()); 
  6.     location = CCDirector::sharedDirector()->convertToGL(location); 
  7.     b2Vec2 locationWorld = b2Vec2(location.x/PTM_RATIO, location.y/PTM_RATIO); 
  8.  
  9.     m_mouseJoint->SetTarget(locationWorld); 

這個方法足夠簡單。它只是把屏幕坐標轉換為世界坐標,在將鼠標關節的目標點轉換為這個點。

為了完成它我們不得不通過銷毀鼠標關節釋放投射器臂。讓我們在CCTouchesEnded方法中實現它:

  1. void Catapult::ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event) 
  2.     if (m_mouseJoint != NULL) 
  3.     { 
  4.         m_world->DestroyJoint(m_mouseJoint); 
  5.         m_mouseJoint = NULL; 
  6.         return; 
  7.     } 

 

 

很簡單!只是銷毀了關節清理了變量。試試看程序吧!用你的鼠標拉動投射器臂。當你放開左鍵時你會看到投射器臂很快的恢復到初始位置。

這真是太快了。正如你記得的,控制它的速度的,就是旋轉關節revolute joint)的馬達速度motorSpeed)和最大馬達扭矩maxMotorTorque)。讓我們減小馬達速度來看看會發生什麼。

回到init方法改小值。一個讓我的程序工作的不錯的值是-10.改變這個值然後你會看到速度是用來讓投射器看起來更真實的參數。

    預備!瞄准!開火!   是時候了,填充橡子。 我們將要在游戲開頭創建每一個子彈物體,然後在一個個實用。因此我們需要1個空間來存儲他們。在頭文件類聲明中加入:  
  1. std::vector<b2Body *> m_bullets; 
  2.     int m_currentBullet; 
增加一個方法用來生產子彈:(頭文件中加聲明)  
  1. void Catapult::createBullets(int count) 
  2.     m_currentBullet = 0; 
  3.     float pos = 62.0f; 
  4.      
  5.     if (count > 0) 
  6.     { 
  7.         // delta is the spacing between corns 
  8.         // 62 is the position o the screen where we want the corns to start appearing 
  9.         // 165 is the position on the screen where we want the corns to stop appearing 
  10.         // 30 is the size of the corn 
  11.          
  12.         float delta = (count > 1)?((165.0f - 62.0f - 30.0f) / (count - 1)):0.0f; 
  13.                  
  14.         for (int i=0; i<count; i++, pos += delta) 
  15.         { 
  16.             // Create the bullet 
  17.             CCSprite *sprite = CCSprite::spriteWithFile("acorn.png"); 
  18.             this->addChild(sprite, 1); 
  19.              
  20.             b2BodyDef bulletBodyDef; 
  21.             bulletBodyDef.type = b2_dynamicBody; 
  22.             bulletBodyDef.bullet = true; 
  23.             bulletBodyDef.position.Set(pos/PTM_RATIO,(FLOOR_HEIGHT+15.0f)/PTM_RATIO); 
  24.             bulletBodyDef.userData = sprite; 
  25.             b2Body *bullet = m_world->CreateBody(&bulletBodyDef); 
  26.             bullet->SetActive(false); 
  27.              
  28.             b2CircleShape circle; 
  29.             circle.m_radius = 15.0/PTM_RATIO; 
  30.              
  31.             b2FixtureDef ballShapeDef; 
  32.             ballShapeDef.shape = &circle; 
  33.             ballShapeDef.density = 0.8f; 
  34.             ballShapeDef.restitution = 0.2f; 
  35.             ballShapeDef.friction = 0.99f; 
  36.             bullet->CreateFixture(&ballShapeDef); 
  37.              
  38.             m_bullets.push_back(bullet); 
  39.         } 
  40.     } 
這個方法的大部分你現在應該很熟悉啦。我們的方法將創建幾個子彈和在第一個松鼠和投射器身體之間的平均的空隙。 bulletBodyDef的bullet參數可能是你之前沒有見過的細節之一。這告訴box2d這個物體是一個高速物體,在模擬期間box2d會小心的模擬它。 box2d的官方文檔解釋了為什麼我們要把這些物體設置為子彈子彈物體: “Game simulation usually generates a sequence of images that are played at some frame rate. This is called discrete simulation. In discrete simulation, rigid bodies can move by a large amount in one time step. If a physics engine doesn't account for the large motion, you may see some objects incorrectly pass through each other. This effect is called tunneling." “游戲模擬通常是以一定的幀速播放一系列圖。這被稱為離散模擬。在離散模擬中,剛體可以在一個時間步中移動一大段距離。如果物理引擎沒有對高速移動的解決方案,你可能看到某些物體穿越了,被稱作隧道效應”。   默認情況下,box2d應用持續碰撞檢測continuous collision detection (CCD))來防止動態物體貫穿靜態物體。通過從舊的坐標到新坐標來掃描形狀從而完成前述功能。引擎通過掃描和計算沖撞的TOItime of impact)尋找新的碰撞。物體在第一個TOI移動並在接下來的時間步中停止。 正常情況下,持續碰撞檢測並不用於動態物體間。這是為了讓性能更加優化。但在某些游戲中你需要動態物體應用持續碰撞檢測。例如,你想做個發射高速子彈打一堆磚塊的游戲。沒有持續碰撞檢測,子彈可能會因為隧道效應貫穿磚塊。 接下來我們會創建一個用來將子彈粘在投射器上的方法。我們還需要2個額外的類屬性,在頭文件中增加代碼:  
  1. b2Body *m_bulletBody; 
  2. b2WeldJoint *m_bulletJoint; 
子彈關節將會保持對我們創建的在子彈和投射器臂直接關節的引用。 現在回到實現文件,在createBullets方法後增加下面的方法:  
  1. bool Catapult::attachBullet() 
  2.     if (m_currentBullet < m_bullets.size()) 
  3.     { 
  4.         m_bulletBody = (b2Body*)m_bullets.at(m_currentBullet++); 
  5.         m_bulletBody->SetTransform(b2Vec2(230.0f/PTM_RATIO, (155.0f+FLOOR_HEIGHT)/PTM_RATIO), 0.0f); 
  6.         m_bulletBody->SetActive(true); 
  7.          
  8.         b2WeldJointDef weldJointDef; 
  9.         weldJointDef.Initialize(m_bulletBody, m_armBody, b2Vec2(230.0f/PTM_RATIO,(155.0f+FLOOR_HEIGHT)/PTM_RATIO)); 
  10.         weldJointDef.collideConnected = false; 
  11.          
  12.         m_bulletJoint = (b2WeldJoint*)m_world->CreateJoint(&weldJointDef); 
  13.         return true; 
  14.     } 
  15.      
  16.     return false; 

我們首先獲得當前子彈的指針之後我們有一個方法來循環遍歷它們)。SetTramsform方法改變了物體中心的位置。坐標就是投射器的尖端坐標。我們接下來激活子彈物體來讓box2d開始進行對它的物理模擬。

然後我們創建了一個連接關節weld joint)。連接關節通過初始化時指定的點來連接2個物體並且不允許在它們之間的連接點向前方的其他移動。

我們設置碰撞連接為假,因為我們不想子彈和投射器臂之間碰撞。

注意,如果還有可用子彈我們返回真。對於之後檢查關卡是否因為我們發射完所有子彈而結束很有用。

讓我們再創建一個方法在連接子彈後調用初始化方法。

  1. void Catapult::resetGame( ) 
  2.     this->createBullets(4); 
  3.     this->attachBullet(); 

現在增加一個函數調用在init方法末尾:調用tick前)

  1. CCCallFunc *callSelectorAction = CCCallFunc::actionWithTarget(this, callfunc_selector(HelloWorld::resetGame)); 
  2.     this->runAction(CCSequence::actions(
  3.                                         callSelectorAction, 
  4.                                         NULL)); 

 

運行程序你會發現有些詭異的事情發生了!

橡子在投射器放彈位置偏左。這是因為我設置橡子的位置在投射器臂頂部偏9度。但在init方法最後投射器仍然是0度,那麼子彈實際上粘到了錯誤的位置。

為了修補這個bug我們只能給引擎些時間來放置投射器臂。讓我們來改變調用resetGame方法的時間:

  1. CCDelayTime *delayAction = CCDelayTime::actionWithDuration(0.2f); 
  2.     CCCallFunc *callSelectorAction = CCCallFunc::actionWithTarget(this, callfunc_selector(HelloWorld::resetGame)); 
  3.     this->runAction(CCSequence::actions(delayAction, 
  4.                                         callSelectorAction, 
  5.                                         NULL)); 

這樣使得調用推遲了半秒。我們之後會有更好的解決方法,但現在就用這個辦法。現在你會看到橡子到了正確的位置。

 

如果我們現在拉動投射器臂並釋放,橡子並不會飛出去,因為它仍被焊在投射器上。我們需要釋放子彈。那麼久銷毀關節吧。但何時何處來銷毀關節呢?

最佳方法就是在每個模擬步中被調用的tick方法中確認坐標。

首先我們需要一種方法來知道投射器臂是否被釋放。在類聲明中增加代碼:

  1. bool m_releasingArm; 

現在回到CCTouchesEnded方法中在銷毀鼠標關節前的條件:

  1. void Catapult::ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event) 
  2.     if (m_mouseJoint != NULL) 
  3.     { 
  4.         if (m_armJoint->GetJointAngle() >= CC_DEGREES_TO_RADIANS(5)) 
  5.         { 
  6.             m_releasingArm = true; 
  7.         } 
  8.          
  9.         m_world->DestroyJoint(m_mouseJoint); 
  10.         m_mouseJoint = NULL; 
  11.         return; 
  12.     } 

在init方法中添加:

  1. m_releasingArm = false; 

這讓我們僅在投射器臂被拉的角度大於20度時才設置釋放的變量為真。如果我們只是拉動投射器臂一點點,子彈並不會被釋放。

增加下面代碼在tick方法尾部:

  1. // Arm is being released 
  2.     if (m_releasingArm && m_bulletJoint != NULL) 
  3.     { 
  4.         // Check if the arm reached the end so we can return the limits 
  5.         if (m_armJoint->GetJointAngle() <= CC_DEGREES_TO_RADIANS(10)) 
  6.         { 
  7.             m_releasingArm = false; 
  8.  
  9.             // Destroy joint so the bullet will be free 
  10.             m_world->DestroyJoint(m_bulletJoint); 
  11.             m_bulletJoint = NULL; 
  12.  
  13.         } 
  14.     } 

很簡單,是吧?我們等到投射器臂幾乎回到了初始位置然後我們釋放了子彈。

運行工程你將看到子彈飛的很快!

在我看來有點太快了,通過減小旋轉關節的最大扭矩來讓子彈慢點吧。

回到init方法,將最大扭矩從4800減小到700。你也可以試試其他的值。

  1. armJointDef.maxMotorTorque = 700;//4800 

 

移動的鏡頭

要是場景跟隨子彈移動,那麼我們就能看到全部運動過程。這實在是不錯。

我們可以很容易的做到,只要適當的改變場景的坐標屬性。增加下面的代碼到tick方法末端

  1. // Bullet is moving. 
  2.     if (m_bulletBody && m_bulletJoint == NULL) 
  3.     { 
  4.         b2Vec2 position = m_bulletBody->GetPosition(); 
  5.         CCPoint myPosition = this->getPosition(); 
  6.         CCSize screenSize = CCDirector::sharedDirector()->getWinSize(); 
  7.  
  8.         // Move the camera. 
  9.         if (position.x > screenSize.width / 2.0f / PTM_RATIO) 
  10.         { 
  11.             myPosition.x = -MIN(screenSize.width * 2.0f - screenSize.width, position.x * PTM_RATIO - screenSize.width / 2.0f); 
  12.             this->setPosition(myPosition); 
  13.         } 
  14.     } 

條件語句判斷子彈如果不是粘在投射器上,那它一定在運動。我們獲得坐標然後確認它是否在屏幕正中右側。如果是就改變屏幕坐標來使子彈遺址位於屏幕正中。

注意MIN的負號,我們這樣做是讓屏幕朝相反方向左)移動。

現在程序很COOL!

資源已經打包到附件

原文地址:

http://www.raywenderlich.com/4756/how-to-make-a-catapult-shooting-game-with-cocos2d-and-box2d-part-1

 

 

 

 

 

 

 

 

 

 

本文出自 “Ghost” 博客,請務必保留此出處http://mssyy2010.blog.51cto.com/4595971/847000

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved