(此心得筆記文會有增加或刪減的情形,並且未經理整理還很絮亂,僅供個人學習記錄,如有錯誤實屬正常,根據版本的不同內容或許也需更改。)
(較長的程式碼我都還不會附,因為這個作者的網站上都可以下載,有興趣學習的可以去找看看,在他的chapter 10的下載點)
(請注意我的翻譯不是照著翻的,會依照中文的習性去翻譯,而且可能會因為偷懶或是翻不出來又不想浪費時間的關係而省略一些句子或是單字沒翻)

使用書本:Learning Cocos2D: A Hands-On Guide to Building iOS Games with Cocos2D, Box2D, and Chipmunk
作者:Rod Strougo & Ray Wenderlich

使用Box2D有四個主要步驟:

1.首次加入物件於scene中時,要告訴Box2D該物件要放在哪裡還有他的形狀。(只限第一次)
2.在遊戲中的update迴圈裡面,若要使用力場的話,要在這迴圈裡面告訴Box2D要使用什麼力場,像是重力。
3.在update迴圈裡面,告訴Box2D去模擬這個世界一段時間,Box2D就會根據你提供的力場和碰撞的結果計算出物體的新位置。
4.於是在update迴圈中,你會由Box2D得到物件的新位置去更新你的sprites。

注意因為Box2D是用C++寫的,所以要注意一些語法:

1.創造新物件:MyClass *myClassPointer = new MyClass();
2.指標:若要對某個指向某物件的指標呼叫作用於其上的某個方法 myClassPointer->doSomething();

注意到在BOX2D裡面的sample有一個值叫做PTM_RATIO,這個值是32,因為iphone螢幕的大小為480點寬,而一般(common)的sprite為其1/15寬(約32點寬),若PTM_RATIO的寬度是32點寬,那一般的sprite寬度就是一公尺,這非常好,因為BOX2D對物件的最佳化是0.1到10公尺。但我們自己的遊戲裡面的物件可能比32點寬還大或是小,不能盲目地照預設值。而盲目照預設值會造成一些不能預測的碰撞或反彈的方式,若你發現自己的遊戲裡面的物件有奇怪的反應,那可能就是這個值沒有設好的關係。

在這邊我們會在Constant.h裡面設定這個值為100,如果是iphone就改成50。

首先要在我們自己的專案中需使用Box2D的部分插入該資料庫:
1.假設我門創建新group,取名為scene3(依本書範例)。
2.將Box2D放入自己的專案中。
3.將GLES-Render.h和.mm黨放到Box2D的Group裡面。(新版似乎已經不需要這樣做!)

創造新世界:
1.在PuzzleLayer的head file中建立新世界的變數和draw debug變數。
2.在其implementaion file中建立設定新世界的方法,以及釋放新世界的方法(只要創造新變數就必須得釋放,當然,加到該世界中的東西也會被釋放)。

創造Box2D的物件:

在Box2D世界中每一個個別的物件都叫做body。而且他們是rigid body,碰撞時不會像籃球一樣變形而是直接彈開。
每一個body都由許多部分組成(piece),這些個別的部分叫做fixture,比方說,一把劍就有分成劍刃和劍柄個別來組成。通常這樣組成一個物件會比較單純,也可以組成各種形狀 (這之後再談論)。
用Box2D創造一個body的方法:首先要創造body definition,這描述了body的特性,像是他的位置和移動形式(movement type)。
有三種不同的移動形式:dynamic body(由Box2D負責處理),kinematic body(由遊戲的code負責處理)以及static body(這個body完全不移動)。
完成後,再把body definition傳遞到world裡面,world會幫你創造這些物件。

創建完後就是創造fixture來貼到body object上面去,過程如下:首先創造shape,這是透過連接vertices創造出來的形狀,像是方形或圓形。然後再將這shape傳遞到fixture definition,這是定義其它的特性,像是反彈力和密度。然後你再將fixture definition傳遞到body去,這會返回你一個fixture object。
注意到我們希望建立的箱子大小(size),在寫code的時候需除以2,不然會變成兩倍大!

但就算到這邊,也無法看到任何東西,我們還需要一些code!就是前面講到的draw debug。

Box2D Debug Drawing

其實Box2D debug drawing早已被一開始加入的GLES_Render給實現了!我們只須寫一點code創造debug drawing類的實例變數然後將之連接到你的世界,然後用draw方法呼叫它。(所以現在要編寫draw方法!放在init方法前面!)

在setupDebugDraw方法裡面,我們創造了一個新的GLESDebugDraw的實例變數,然後用SetDebugDraw方法將之傳遞到world裡面,然後特別設立一個flag在debug draw上面告訴它什麼是特別要去draw的,在這邊,我們感興趣的部分是shape。

注意:在setupDebugDraw裡面我們用PTM_RATIO去乘contentScaleFactor是因為GLESDebugDraw處理的東西是pixel(像素),而不是點(point),若沒有乘上PTM_RATIO就無法得到正確的大小顯示在retina display上!
除了形狀,你也可以用debug draw去渲染(render)其他的東西,像是joints(關節),axis-aligned bounding boxes或是center of mass(質心)。詳細的部分可以看b2WorldCallbacks.h裡面有寫!

再來你要確保debug draw類有機會去畫(draw),這邊我們要加入一個draw方法到layer去(PuzzleLayer.mm裡面的init方法下面)。這方法會覆寫layer的draw方法,你便可以實行debug drawing於每一個frame上了!(此為樣板(boilerplate) code)

這方法裡面設定了OpenGL狀態去處理debug drawing並且告訴world開始並使用特定的debug draw類去畫每一個物件。
最後一步就是你需要設定dealloc去清除你所建立的debug draw類!


通過調用init方法裡面的scheduleUpdate,cocos2D會自動尋找layer裡面叫做update的,並且會在每一個frame中都調用它。在這方法中你需要給cocos2D一些時間去模擬,這透過在world上調用step方法來達成。而你需要輸入參數velocity iteration和position iteration的數字去執行,這數字越高模擬就越好。但不要設太高,自己要斟酌大概怎樣低的數值可以讓遊戲運行的讓你可以接受!

NOTE:PuzzleLayer裡面的update方法使用了variable-based timestep,意思是說,它每個刻度用delta time的變量來調用。然而,Box2D用固定時間步(fixed timestep)表現得較好,意思是說,他每一步都是用固定時間量來調用的。當你的模擬越來越複雜時,如果你不是使用fixed timestep的話,會有一些奇怪的現象,像是關節的連接和物體的反彈都會奇怪,在這邊我們先這樣用(此為本書第十章,第十一章會改變),因為這裡的模擬還很單純。


創造地面

當你完成到這邊,編譯之後會發現方塊都一直往下掉,現在我們要設定地面讓他不會一直往下掉。

在PuzzleLayer.h的變數宣告裡面加入:
b2Body *groundBody;

然後在PuzzleLayer.mm裡面放入方法createGround。

NOTE:當你把body的位置給定後,比方說是(0,0),那麼之後設定的座標就是相對於body位置的座標了,比方說是端點是(10,10)的fixture,那這個fixture的world座標就是(10,10),但如果body的位置是(5,5),這個fixture的相對座標是(10,10),就代表這個fixture的world座標是(15,15)。這樣的好處是我只要改變body的位置,不需要把每個fixture的座標也改變,反正它是相對座標啊。

地面的密度設定成0,這是有特殊含義的,表示這個body是靜止的!

Warning:如果你忘了設定Box2D裡面的body的密度,那它會預設成0!

最後在init方法的最下面,於

return self;

前面,放入

[self createGround];

基本的Box2D互動與裝飾

首先打開加速度偵測器(accelerometer):
self.isAccelerometerEnabled = TRUE;

這會接收你的layer裡面的加速度偵測器(accelerometer)的回傳(callback)。當加速度偵測器獲得一個data,就會在你的layer裡面呼叫一個方法accelerometer:didAccelerate,請把這方法建立在init方法後面。這將把加速偵測器的輸入值轉換成重力向量,然後呼叫SetGravity去更新這個world裡面的重力向量。編譯後你會發現,如果你傾斜你的手機,畫面裡面的箱子會因此翻滾。

但跟我們的程式碼,這些箱子碰到地面靜止後會變成灰色的,就算旋轉你的手機他們也不會動,有一個最簡單的方法可以改善這個問題,就是設定讓物件不能休眠(doSleep = NO;),但這就不會節省處理時間了,所以有興趣的人可以參考另一種做法:high-pass filter option(詳情可參閱Apple’s Event Handling Guide for iOS ,在Motion Events section裡面)。

拖動物件

我們將使用一種簡單的方法來做到拖動箱子這件事情:mouse joint。這是連接使用者觸碰到的位置於想要移動的body上,並且讓body盡可能快地朝著想要移到的位置去移動。
關節(joint)是一種用來告訴Box2D說兩個bodies怎樣連接的,以及,他們的相對運動是什麼形式。
比方說我們可以用distance joint去連接兩個bodies。這種關節會讓這兩個bodies保持固定的距離。或是用revolute joint,這會讓這兩個bodies只能沿著相同的shared axis去旋轉。
使用方法:
1.打開PuzzleLayer.h然後加上一個變數
b2MouseJoint *mouseJoint;
2.回到PuzzleLayer.mm到ccTouchBegan方法中,刪除createBoxAtLocation這一行並且加入listing 10.26。

Listing 10.26的code主要是要執行AABB test。對於使用者想移動的物件,首先就是要能夠讓程式知道到底使用者要移動的物件是哪一個。AABB test是axis-aligned bounding box test的縮寫。這是一個高度優化的方法,我們會給定一個rectangle,只要物件的bounding box有和這rectangle交會到,這個物件就會很快的被回傳。不過,被回傳的物件是否真的有交會到,這由你自己去判斷。

那AABB到底是怎麼去測試的?假設有一個三角形的物件,你在這個三角形的外部右上方有碰觸,那這時會先作一個AABB測試瞭解碰觸點是否在任何形狀的bounding box裡面
,然後再呼叫TestPoint執行一個AABB測試看看觸碰點是否在這個三角形裡面(本例中,是沒有在裡面的)。

Listing 10.26的code中,我們設定了AABB test的上下界,這等同於一個圍繞著觸碰點的1-point大小的box(單位已轉換成meter)。再來是呼叫QueryAABB來執行這個test。他的參數是一個指標。

再來是建立SimpleQueryCallback.h,new>new file>ios>C and C++>head file,輸入SimpleQueryCallback.h然後將listing 10.27貼入。注意listing 10.27裡面覆寫了ReportFixture方法,當有可能的nterseciton時,這方法就會被呼叫。在這方法中,會檢查body是否是dynamic body(那些是唯一你想用mouse joint去移動的物件),並且使用TestPoint去檢查是否真的有intersection,然後儲存一個指向fixture的指標於實例變數中,並返回false(Box2D就知道不用再去尋找了),若返回true,Box2D就會繼續去尋找。

注意,這只會返回在給定點上第一個找到的物件,就目前的例子來說,就算在那個點上有很多物件,只返回第一個找到的物件也很夠了。

回到PuzzleLayer.mm,輸入:
#import "SimpleQueryCallback.h"
這明顯是要把該導入的東西給導入!

看到PuzzleLayer.mm裡面的ccTouchBegan方法,裡面最下面的一行調用了QueryAABB,在這個方法回傳後,world就會為每個可能的intersection調用SimpleQueryCallback物件,並且,會知道點是否真的有intersect到任何物東西。到目前為止的這些資訊要被拿來使用了!在ccTouchBegan方法最下面且在return前面的部分再添加Listing 10.29。

這段code檢查SimpleQueryCallback類是否有找到一個fixture,若有找到,他就會得到一個從fixture指向該body的指標,然後創造一個mouse joint的定義,在mouse joint定義上的參數說明如下:
bodyA:當你指定一個joint,事實上你通常是指定了兩個bodies。對於mouse joint來說,你通常希望第一個body是不移動(nonmoving)的body,所以你會指定一個固定的目標位置(target location)並且讓target移動到那個位置。對於bodyA,你設定它為groundBody。
bodyB:你想移動的body。本例中,此body為AABB test和SimpleQueryCallback所偵測到的body。
target:你希望body移動到的地方。本例中,為使用者輕敲(tap)的位置。
maxForce:移動body的最大作用力。本例中,你希望能移動任何質量的bodies,所以設定為該body質量的倍數。倍數越大移動到目標位置的速度越快。
collideConnected:bodyA和bodyB是否會碰撞。因為在這例子中,你要你選擇的body和地面有碰撞,所以選擇true。

Warning:當設定joint連接到ground bodies,最常犯的錯誤就是忘了設定collideConnected為true。這樣的話他會用使用預設值false,這表示你的body不會和地面碰撞。所以如果你發現你的body穿過world boundary,請仔細檢查是否你有設定了某些joint讓你的body不會和ground碰撞。

在創造完mouse joint definition後,它會用CreateJoint方法傳遞mouse joint給world,CreateJoint會確實創造joint。他也讓休眠的bodies醒來。如果fixture沒有找到,他就會啓動舊方法在觸碰點上創造box。到目前為止幾乎已經做完了,剩下就是把touch move和end貼上去。

質量、密度、摩擦係數以及彈性復原

前面我們都是使用預設值去設定body,但其實我們可以改變body的質量以及讓它與其他物體之間有摩擦力。而這需要你重新設定這三件事情:density、friction以及restitution。以下我們會使用一些例子來說明。

1.打開PuzzleLayer.mm,並且修改createBoxAtLocation方法(如Listing 10.31),注意到Listing 10.31主要是多設定以上三個參數。
2.於PuzzleLayer.mm的init方法最下面加入Listing 10.32。這主要是在你的scene中間創造三個大小相同的boxes,它們有相同的density、friction以及restitution。注意到code裡面有因應你運行的是在iphone還是在ipad上給定不同的size給這些boxes。一開始運行程式這三個boxes就會自動出現不需要自己去觸碰生成。所以把ccTouchBegan裡面觸碰生成box的指令給comment out。
3.運行程式,仔細去觀察它們的interaction。像是互相的滑動,或是把一個箱子往另一個丟會有碰撞,或是箱子碰到地面的反彈。
4.用Listing 10.35取代init方法裡面createBoxAtLocation的三行,這會創造三個大小不同的箱子,但是小箱子的密度特高,請仿照上面步驟去玩看看吧!
5.一樣用Listing 10.36去取代,這次改變的是摩擦力。這三個箱子依序越來越不光滑。

注意:當兩個摩擦係數不同的fixtures互相滑動時,Box2D會依照這兩個fixtures的摩擦係數的幾何平均數來計算摩擦力。

6.最後的測試是restitution。一樣使用Listing 10.37去取代。這三個箱子彈性依序越來越差。

注意:restitution值不同的bodies碰撞時,Box2D會採用最大的restitution值。

用Sprite裝飾你的Box2D吧!

基本的工具差不多都有了,接下來是將Sprite聯繫到Box2D的body上。

1.這邊有一些東西需要你去作者的網站下載。

注意:hd結尾的檔案是用於ipad螢幕,沒有hd作結尾的檔案用於iphone。

2.創造一個新的GameCharacter子類提供那些聯繫到Box2D之body的Sprite,這樣就能去追蹤那些相關的Box2D的body。先到Classes\Game Objects這個group下面,然後作一個新的subgroup叫做Box2D,在這個子group下建立新的file,File > New > New File,選擇 iOS > Cocoa Touch > Objective-C class,建立一個父類為GameCharacter的新類命名為Box2DSprite。
3.在Box2DSprite.h裡面建立Listing 10.38的code。這裡面的唯一的實例變數和property是為了相聯繫的Box2D的body,這是因為子類需要能夠儲存目前的角色狀態。他也定義了方法用來檢查是否這個Box2D的body接受一個mouse joint的輸入與否。
4.然後在Box2DSprite.mm中輸入Listing 10.39。這是用來synthesize變數以及返回一個Bool值TRUE,表示物件將可以被mouse joint給移動。
5.再回到PuzzleLayer.h添加幾個實例變數(如Listing 10.40)。這是持續追蹤你的Batch node的,以及有一個凍僵的Viking的參考(reference)。
6.回到PuzzleLayer.mm裡面,在最上面導入(如同Listing 10.41):
#import "Box2DSprite.h"
7.然後再一次去修改createBoxAtLocation(依照Listing 10.42的粗體字)。主要的變動是:首先增加一個Box2DSprite的sprite作為參數,然後將這個參數設定成Box2D body的使用者data,最後是取用這個Sprite的大小(content size)來創造一個box。除此之外,還增加一個參數指明是否如之前一樣創造一個box或cycle,方法僅只是使用b2CircleShape去取代b2PolygonShape然後再設定radius就可以了。注意到方法名稱也變了,現在是createBodyAtLocation而不是createBoxAtLocation,因為現在也包括創造一個circle而不單只是box。

注意:這邊的content size是包括了sprite image中可透視(transparent)的部分。在本例中是沒什麼關係,就當成是沒有任何可透視的部分就好了。但如果是作正式的遊戲,你就必須把映照到sprite image的Box2D body修改成接近於image的形狀才行。下一章會教導如何使用Vertex Helper來做到這件事情。

8.再來是寫幾個routine去使用createBodyAtLocation方法去創造聯繫Sprite的Box2D物件,將Listing 10.43的code寫在PuzzleLayer.mm裡面的init方法上面。這些方法都非常單純,首先是從Sprite sheet裡面傳遞有指定的sprite frame name的sprite去創造一個Box2DSprite,設定它的game object type以及friction/restitution/density/isBox等參數,然後把這個sprite加到sprtite batch node裡面去。
9.然後把init裡面的createBoxAtLocation給消除掉,用listing 10.44取代。這段code加入了一些bodies到scene裡面的特定位置去,注意到這有分為iPad和iPhone兩種版本,所以會載入適當的sprite sheet並且sprites會被放到每個devices中適當的位置去。這也設定了背景圖案,這邊請注意到,在載入背景圖片前,它先把像素格式(pixel format)設為(set...to...)RGB565,這是非常好的做法,當你要使用大的圖片時(例如,背景圖片),使用較低質量的像素格式,以節省內存(memory usage),在iOS的device中內存是受限制的。

警告:如果都不去注意一直使用大圖,很容易耗盡內存而被強制關閉app。有關像素格式(pixel format)可以參考Ricardo Quesada在Cocos2D寫的文章:
http://www.cocos2d-iphone.org/archives/61

10.到目前為止你已經創造了一個sprite sheet並且創造了好幾個有相同尺寸的Box2D bodies,但你還少了一個重要步驟,就是連接sprite的位置到這些Box2D body的位置上。對於你遊戲中每一個動畫frame,都應該隨著Box2D body的位置改變而更新sprite,所以下一步就是你還要在update方法裡面加入一些code。請參考Listing 10.45的程式碼加到update方法的最下方去。這個code是一個迴圈,繞過world裡面全部的bodies然後檢查他們是不是null。記得在createBodyAtLocation裡面它是如何去把Box2D body的user data設定成sprite嗎?這是使用它的地方。然後它又基於Box2D body的位置去設定sprite的位置與旋轉。位置的設定是使用了PTM_RATIO,旋轉是透過將Box2D的角度(radians)轉換成Cocos2D的角度(degrees)。
11.最後,目前我們不需要使用加速度偵測器(accelerometer),記得把isAccelerometerEnabled這一行給取消掉(如同Listing 10.46)。
12.再來就直接編譯吧,看看你的成品。

做一個Box2D Puzzle Game

這個遊戲主要是讓被凍在冰塊裡面的Viking照射到陽光後解凍,所以其實你還需要一些code使得當Viking到達特定位置後可以被偵測到。
一種單純的方法是在每一次update都去檢查他的位置並且檢查他是否在設定的位置內。這方法雖然可以,但還有另一種方式可以增加Box2D的威力!
因為Box2D是一個物理引擎,處理物體碰撞在一起時會發生什麼事情,它裡面已經有code去偵測物體碰撞,使得它可以適當的回應這碰撞。你可以藉由註冊Box2D的通知碰撞發生或是索取物件目前現行的碰撞的列表。
對目前這個小遊戲來說,只要Ole碰到scene的右下角,你就要知道。一種方式是在那個位置做一個很薄的物件,然後檢查Ole是否碰撞到那個虛擬的物件。
但這個方法不夠好,我們希望的不是它剛好確實到右下角,而是接近就可以了。幸運的是,Box2D提供了一個很棒的解答,就是sensor。
Sensors是Box2D的物件,他不會引起碰撞回應(response)。所以你可以有一個想像的Box在右下角,Ole和其他的物件都只會穿過去,好像那邊根本沒東西一樣。然而,碰撞到想像的Box時Box2D還是會回報。你可以找到回報,並且當Ole到那個點的時候標記你贏了。接下來就來看看怎麼做吧:

1.首先在PuzzleLayer.h裡面增加兩個實例變數,如同Listing 10.47。一個變數追蹤sensor body,一個追蹤使用者是否贏得這關的勝利。
2.回到PuzzleLayer.mm,在最上面導入GameManager.h(如同Listing 10.48)。這是因為你需要使用GameManager去轉場到破關畫面。
3.再來是要創造一個方法去創造一個sensor用來代表這是太陽會照到的地方,如同Listing 10.49。請寫在PuzzleLayer.mm裡面的init方法前面。這方法設立了一個body,然後用了一般的方法把box形狀的fixture貼到這個body上。然而,只有一個地方不一樣,就是fixture的isSensor設定為TURE。這允許了物件去偵測碰撞但又能讓任何東西去穿過它。
4.接下來加入一些code去呼叫createSensor方法以及display指令(instructions)的方法,如同Listing 10.50(等一下會去編,寫這兩個方法)。若你現在就要執行你等一下才要寫的這殘缺不全的指令方法,你仍然能夠編譯並且運行你的程式,但你會看到在右下角有一個奇怪的綠色box表示那個sensor。然而,它不會做任何事情ㄧ恩為你還未加入任何code去尋找碰撞。
5.現在把Listing 10.51加到update方法的最下面去吧。這段code會確認使用者是否贏了,若還未贏,會檢查是否Ole碰撞到了sensor。那這是怎麼作用的?Box2D會維持一份有關於所有的一個body於其他bodies的接觸清單。每一個接觸都包含資訊(什麼點(point)正在接觸)以及參考(正在接觸的fixtures的參考(references))(你可以從那些參考得到bodies)。這很單純的審核Ole全部的接觸,並且檢查其中是否有sensor body。若有,他就設定這一關贏了並且呼叫方法執行過關動畫(這等一下你就會寫了)。

注意:這方法有個小問題。他只有使用"目前(current)"Ole的碰撞狀態。如果Ole之前曾很快的移動並且在一個Box2D step內就穿過sensor area,這就不會被視作是碰撞。下一章我們會展示一個更好的方法使用Box2D偵測碰撞(會比較複雜),但以目前的關卡來說這樣的偵測就很夠用了。

6.最後一步是加入code去顯示本關一開始的指令並顯示玩家贏的時候的效果(參考Fig 10.18),把Listing 10.52加到init方法上面。這使用了一組CCActions(你在第五章學到的)讓label縮小或放大,然後等一下,轉換到破關畫面。編譯且運行code,如果一切都運行良好,你就能把Ole 拖到右下角並贏得這一關的勝利。


增加(Ramping It Up)

你也許注意到這關有一個問題:太簡單了!
就目前來說(as it stands),你可以單純的拖動冰凍起來的Ole到指定的區域。
要讓事情更有挑戰性一點的話,你可以設定一些東西,讓你無法直接移動物件,比方像是Ole和長石塊,你也將加入一群額外的物件去塞滿這空間。你將順便(while you are at it)添加一些額外的互相作用,像是加入音效或是讓骷髏頭"pop"並且在輕點後消失。
到目前為止,你一直對所有的Sprites使用相同的CCSprite的子類,這是因為他們都有相同的行為並且這很容易做到。但現在你需要每個物件有不同的行為,現在請維持你的code組織化且好的,你將為每個物件作Box2DSprite子類。
注意你將要為此開始(embark upon)寫一串code。但別擔心,接下來大部分的code都相當直觀,並且如果你不想自己輸入它們的話,你可以從sample project復制貼上它們。

1.首先加入一個新的Box2DSprite的子類的file。移到Classes\ GameObjects\Box2D,選取File > New > New File,然後選擇iOS > Cocoa Touch > Objective-C class並且按Next,於subclass欄位輸入Box2DSprite並按下Next,命名為Meteor然後按save(注意他的.m要改成.mm)。重復這些步驟去創造Skull.mm,Rock.mm,IceBlock.mm,LongBlock.mm以及FrozenOle.mm。
2.用Listing 10.53取代Meteor.h的內容。重復此步驟對其它表頭檔Skull.h,Rock.h,IceBlock.h,LongBlock.h以及FrozenOle.h寫入同樣的內容。
3.再來進入這些classes的實行檔(.mm),以FrozenOle.mm為例,用Listing 10.54的內容寫入。這單純設定顯示的frame為凍結的Ole圖片以及適當的設定遊戲物件type。他很妥善的封裝這邏輯在FrozenOle類裡面,所以主要遊戲關卡不須包含有關於什麼sprite frame在使用或是這個sprite的物件type的資訊。

注意:

4.注意到mouseJointBegan方法於使用者想在body上啓動一個mouse joint時會回傳一個false值(意思是你不能直接拖拉Ole了)。
5.再來是使用Listing 10.55去取代LongBlock.mm的內容。Code的內容和FrozenOle類的行為很像。
6.接下來是用Listing 10.56去取代IceBlock.mm的內容。這個實行檔也是相似的,除了他沒有覆寫mouseJointBegan方法,所以它接收的是預設行為(也就是允許mouse joint)。
7.再來,使用Listing 10.57取代Roch.mm的內容。這也是相似的實行檔,除了我們添加了一點趣味:當使用者開始去移動岩石時,會有音效。
8.再來使用Listing 10.58去取代Meteor.mm。這也是類似的實行檔,但它撥放的音效是不一樣的。
9.最後,使用Listing 10.59去取代Skull.mm的內容。在骷髏頭的情況中,當使用者輕觸骷髏頭時,它設定骷髏的狀態為死亡,並且返回false值(不允許mouse joint)。當狀態變成死亡時,它會執行下列步驟:
9.1 調用(call)DestroyBody()去清Box2D body,將做為參數的body傳入。
9.2 設定body的指標為null。
9.3 用Cocos2D的動畫來撥放一小段骷髏頭啪一聲消失,完成後再運行一個安排好的callback(你自己用一個函數叫做call,而你給系統一個函數指標讓它等一下取用,這叫做callback function,本code中是撥放一串動畫後,再執行一個函數的指標)。
9.4 callback使用了removeFromParentAndCleanup方法從這個scene中移除sprite。
10.現在來整合這些新類。切換到PuzzleLayer.mm然後在最上方導入一些表頭檔(如同Listing 10.60)。
11.到createMeteorAtLocation,清除前兩行,然後用Meteor類寫一行code去創造一個meteor,如同Listing 10.61(修改createMeteorAtLocation)。然後對其他的方法像是createSkullAtLocation、createLongBlockAtLocation等方法重複以上步驟,記得各自使用適當的類。
12.再回到init方法在底下加入一些code讓它能撥放背景音樂和額外的物件使得這關更加有趣,如同Listing 10.62。你現在在同樣的位置加入了全部的bodies,但這沒關係,因為Box2D會在關卡開始時自動把這些東西散開,它們就不會碰撞。如果你喜歡也可以修改code讓bodies放於你喜歡的地方。
13.於你在init方法裡面的時候記得註譯掉(comment out)debug draw,因為我們已經不需要了,參考Listing 10.63。
14.最後一步,到ccTouchBegan方法去,在你呼叫callback.,fixtureFound->GetBody()之後加入Listing 10.64中的三行code。這在物件上調用了mouseJointBegan方法,這樣它們就能去回應或是取消mouse joint了。

完成了!編譯然後執行吧。現在當你移動岩石或是Meteor時就能聽到音效了,而且當你輕點骷髏頭時你能看到它們消失。而且最好的是,你增加了困難度,因為你不再能直接移動Ole或是長冰塊,而且那邊有更多的物件擋住道路,所以你必須要能使用一點物理技巧去贏得勝利!參考Fig 10.19。

結論

你做出一個很酷的小puzzle遊戲,並使用Box2D自動處理遊戲中的物理事件。你應該已經熟悉了Box2D中最重要的概念,像是body、fixture、density、restitution以及friction。你也有了實作經驗,像是加入body到world、用sprites去裝飾還有加入簡單的碰撞檢查和遊戲邏輯。

下一章,你會更進一步並且會看到你能使用Box2D去創造橫向捲軸動作遊戲,完成汽車、橋、馬達和更多的東西。

arrow
arrow
    全站熱搜

    clouddeep 發表在 痞客邦 留言(0) 人氣()