1. 程式人生 > >實驗4 碰撞檢測與運動模擬(Box2D桌球)

實驗4 碰撞檢測與運動模擬(Box2D桌球)

說明:

課程教材《計算機遊戲程式設計》(基礎篇)(第3版) 提供示例程式碼,而課程實驗在示例程式碼的基礎上提出更高的實驗要求。除此之外,本人也會額外加入些個人創意,希望同學們在參考之餘也能加入自己的想法。

實現效果:

實驗報告:

一、實驗目的與要求

1. 瞭解物理模擬、精靈的繪製與移動、觸控事件的應用等。

2. 熟悉基於Box2D的物理引擎。

3. 掌握Box2D觸屏檢測和碰撞檢測機制。

二、實驗內容與方法

1.完成遊戲編譯(70分)

成功編譯並執行教材P150“遊戲物理模擬例項-基於Box2D的遊戲例項”。

2.完成方案一 (10分)

修改遊戲程式碼,實現修改內容一,即修改擊球方式。

3.完成方案二 (10分)

修改遊戲程式碼,實現修改內容二,即增加一個進洞口。

4.完成方案三 (10分)

修改遊戲程式碼,實現修改內容三,即修改球杆跟隨力度變化的狀態。

三、實驗步驟與過程

記錄關鍵步驟/設計過程/設計結果的截圖

1.完成遊戲編譯(70分)

老套路編譯例程並執行程式。

圖 1

2.修改擊球方式 (10分)

修改擊球方式為:在擊球區域拖動滑鼠左鍵,可以實現球杆的瞄準以及能量的蓄積,鬆開滑鼠左鍵,實現擊球。

  • 仔細研究原始碼,發現“在擊球區域拖動滑鼠左鍵,可以實現球杆的瞄準”這一功能原本就已經實現了,無需多做修改。
  • 而對於“在擊球區域拖動滑鼠左鍵,可以實現能量的蓄積”這一功能,只需把原來在“能量滑塊區域”滑動更改為在“擊球區域”中滑動即可。

因此,在拖動滑鼠呼叫的函式onTouchMoved()中,無需再把“球杆瞄準”和“能量積蓄”兩個功能分別寫在“擊球區”和“能量滑動區”中實現,而是統一放到“擊球區”中實現即可。

此時,“當前擊球能量”變數_curPower的值可更改為由“白球”的座標和“目標點”的座標的距離長度(為更好控制數值,可讓它們再除以2)。

即curPower=(touch->getLocation()-changePos(mainBilliards->getPosition())).getLength()/2

  • “鬆開滑鼠左鍵,實現擊球”這一功能在原始碼中也已經實現了,只不過它限定了在“能量滑動區域”的範圍裡。因此,只需要把這一個限定條件“_powerRect.containsPoint(touch->getLocation())” 給刪掉就可以了。

修改後程式碼為:

圖 2

圖 3

圖 4

3.增加個進洞口 (10分)

Ps一張“球洞”圖片,命名為“hole”。

圖 5

  • 直接利用BilliardSprite類來建立“球洞”物件,但是與建立“檯球”物件不同的是,由於“球洞”不是“運動物體”而是“靜態物體”,所以在create時要注意引數isStatic設定為true。

圖 6

由於檯球遊戲一般有四個洞,所以我也給這個遊戲設了四個“球洞”。所以,四個球洞除了位置的不同外,還應該有旋轉角度的不同,由上圖可見四個球洞精靈物件分別要旋轉0、90、180、270度。但由於BilliardSprite初始化函式中沒有設定精靈物件的旋轉角度的程式碼。所以可以另外在BilliardSprite類中寫一個方法函式initHole(float rotation)來設定精靈物件的旋轉角度。

但要注意的是,BilliardSprite中的update函式中會不斷地更新精靈的位置以及根據body的角度變化來設定精靈的旋轉。但是在預設情況下,物體都是不動的,所以會不斷地給精靈的旋轉角度設定為0 ,這樣initHole函式設定的“洞口”旋轉角度就前功盡廢了。如下圖update程式碼所示:

圖 7

所以,為了“球洞”精靈不受update函式的影響。我們可以在initHole函式執行this->unscheduleAllSelectors(),解除update定時器,讓“球洞”維持“靜止”。

而“球洞”精靈的位置,要由“球洞”物體的位置來轉換而成。因此,“球洞”初始化函式initHole程式碼有:

圖 8

  • 當“白球”進洞時,重新開始遊戲。該功能實現只需要如實驗三一樣,在遊戲主場景PhysicsBox2dScene.cpp中執行如下程式碼,進行與新場景的切換即可:

CCDirector::sharedDirector()->replaceScene(PhysicsBox2dScene::createScene());

於是,問題的關鍵就在於如何判斷“白球進洞”並執行上述程式碼了。可給碰撞監聽類GameContactListener新增一個bool型別的私有變數mainBallInHole,表示白球是否進洞,並新增該變數的get方法。

當物體間發生碰撞後,GameContactListener.cpp的BeginContact函式會被呼叫,該函式通過b2Contact引數獲取到兩個碰撞物件。因此,只需通過獲取這兩個碰撞物件的name並進行比對,如果是“main”和“hole”便能確定是“白球進洞”,然後再把mainBallInHole變數設為true。

而PhysicsBox2dScene.cpp的update函式中通過get方法獲取到mainBallInHole變數再進行if判斷,條件符合後便執行場景切換函式即可。

程式碼為:

GameContactListener.cpp:

圖 9

PhysicsBox2dScene.cpp的update函式:

圖 10

PhysicsBox2dScene.cpp:

圖 11

  • 當除了白球的“普通球”進洞時,該球要消失。結合②部分可知,只需在GameContactListener.cpp的BeginContact函式中新增一個name為“ball”與“hole”碰撞的情況即可。其中,最好的實現方法莫過於把要移除的小球給記錄在連結串列ballsRemovalList中,在時間長step執行後及場景被渲染前,執行GameContactListener的函式deleteBody來遍歷ballsRemovalList,對其中記錄的小球物件進行刪除。

然而,由於BilliardSprite物件被呼叫的地方比較多,導致直接刪除該物件(delete BilliardSprite*)程式報錯,本人功力不夠深厚沒能解決這一問題。只能退而求其次,在BilliardSprite類中寫一個新的函式方法deleteBody(),通過呼叫其來刪除BilliardSprite物件中的_body以及移除_sprite 。這樣做雖然不是最佳方案但也能實現同樣的功能。

程式碼為:

GameContactListener.cpp:

圖 12

PhysicsBox2dScene.cpp (要在step後執行刪除操作):

圖 13

GameContactListener.cpp:

圖 14

BilliardSprite.cpp:

圖 15

4.修改球杆跟隨力度變化的狀態 (10分)

重點觀察PhysicsBox2dScene.cpp中的函式updateLine()的“計算球杆位置”部分的程式碼,有如下所示:

圖 16

從這個程式碼中我們很難看得出什麼,我們對其進行展開和轉換。以x軸座標為例,即:

(((end_p - start_p).getLength() + 45) * start_p.x - 45 * end_p.x) / (end_p - start_p).getLength()

我們可以把其轉化為:

start_p.x + 45 * (start_p.x - end_p.x) / (end_p - start_p).getLength()

由轉換後的程式碼我們就比較容易看出start_p.x指白球的x座標,而 (start_p.x - end_p.x) / (end_p - start_p).getLength() 指的是夾角的餘弦值。因此,易知這個45指的應該是球杆與白球的距離(y軸座標分析同理)。

可知,預設情況下球杆離白球的距離為45。因此,我們只需根據力度的數值改變這45的距離即可。引入_curPower,把程式碼中的45都改為45+_curPower/2  (除以2是為了適當控制距離,令球杆不要離白球太遠),即:

圖 17

5其餘

下面介紹自己新添的內容:

  • 由一個洞口新增到四個洞口(前文已說明)
  • 視窗解析度調整為1600 x 900
  • 小球由5個增添到10個,並對位置進行了調整
  • 新增新的背景精靈bg,即為綠色的檯球布。程式碼為:

圖 18

6程式執行

初始圖:

圖 19

注意球杆和白球距離,以及力量條間的關係:

圖 20

圖 21