在cocos2d-x裏面使用BOX2D

本教程基於子龍山人翻譯的cocos2d的IPHONE教程,用cocos2d-x for XNA引擎重寫,加上我一些加工製作。教程中大多數文字圖片都是原作者和翻譯作者子龍山人,還有不少是我自己的理解和加工。感謝原作者的教程和子龍山人的翻譯。本教程僅供學習交流之用,切勿進行商業傳播。

子龍山人翻譯的Iphone教程地址:http://www.cnblogs.com/zilongshanren/archive/2011/05/27/2059453.html

Iphone教程原文地址:http://www.raywenderlich.com/457/intro-to-box2d-with-cocos2d-tutorial-bouncing-balls


BOX2D這個引擎,是個很好的模擬真實物理世界的引擎。憤怒的小鳥就是基於這個引擎做的。想想憤怒的小鳥,是不是很有衝動來學習下使用這個引擎了呢。

本教程目的就是讓你們熟悉在cocos2d裏面如何使用box2d,所採用的例子就是製作一個簡單的應用。裏面有一個籃球,使籃球自己碰到牆壁反彈。

本教程假定你學過前面的教程用cocos2d-x做一個簡單的windows phone 7遊戲,或者有同等的相關經驗。


下載XNA版的BOX2D:

http://box2dxna.codeplex.com/最新的BOX2D。然後解壓。到其目錄下的Box2D.XNA.TestBed\bin\Windows Phone\Debug這裏把這個BOX2D.XNA.DLL這個DLL文件複製出來。其實下載這麼個文件我們就需要這個DLL。然後估計有人會想,怎麼到這裏複製呢,那麼不是有個Box2D.XNA工程麼,其實,經過我實踐,發現原來Box2D.XNA\bin\Windows Phone\Debug這個目錄下的DLL有些問題。所以到人家的示例工程裏面把DLL複製出來。


這裏先介紹下BOX2D世界的相關理論。(其實推薦看Box2d參考手冊(英文)和Box2d參考手冊(中文),不是很多,才30+頁,看了就瞭解了BOX2D的理論,再進行以下的理解就簡單了)

在我們開始之前,讓我們先交待一下Box2D具體是如何運作的。

  你需要做的第一件事情就是,當使用cocos2d來爲box2d創建一個world對象的時候。這個world對象管理物理仿真中的所有對象。

  一旦我們已經創建了這個world對象,接下來需要往裏面加入一些body對象。body對象可以隨意移動,可以是怪物或者飛鏢什麼的,只要是參與碰撞的遊戲對象都要爲之創建一個相應的body對象。當然,也可以創建一些靜態的body對象,用來表示遊戲中的臺階或者牆壁等不可以移動的物體。

  爲了創建一個body對象,你需要做很多事情--首先,創建一個body定義結構,然後是body對象,再指定一個shap,再是fixture定義,然後再創建一個fixture對象。下面會一個一個解釋剛剛這些東西。

  • 你首先創建一個body定義結構體,用以指定body的初始屬性,比如位置或者速度。
  • 一旦創建好body結構體後,你就可以調用world對象來創建一個body對象了。
  • 然後,你爲body對象定義一個shape,用以指定你想要仿真的物體的幾何形狀。
  • 接着創建一個fixture定義,同時設置之前創建好的shape爲fixture的一個屬性,並且設置其它的屬性,比如質量或者摩擦力。
  • 最後,你可以使用body對象來創建fixture對象,通過傳入一個fixture的定義結構就可以了。
  • 請注意,你可以往單個body對象裏面添加很多個fixture對象。這個功能在你創建特別複雜的對象的時候非常有用。比如自行車,你可能要創建2個輪子,車身等等,這些fixture可以用關節連接起來。

  只要你把所有需要創建的body對象都創建好之後,box2d接下來就會接管工作,並且高效地進行物理仿真---只要你週期性地調用world對象的step函數就可以了。

  但是,請注意,box2d僅僅是更新它內部模型對象的位置--如果你想讓cocos2d裏面的sprite的位置也更新,並且和物理仿真中的位置相同的話,那麼你也需要週期性地更新精靈的位置



在cocos2d-x for wp7上使用BOX2D:


我們新建個cocos2d-x的工程,命名爲cocos2dBOX2DDemo,同樣的,OpenXLive的那個勾去掉。因爲我們不需要這個服務。同樣的,我們往工程裏面新建一個lib的文件夾。然後把需要的DLL添加到該工程的這個lib文件夾內。然後把帶歎號的引用移除後並添加該工程lib文件夾內的相同的DLL。並且在cocos2dBOX2DDemo工程中添加BOX2D.XNA.DLL這個引用。

然後新建一個類添加到Classes文件夾。命名爲BOX2DLayer。並使之繼承於CCLayer。

下載http://dl.dbank.com/c0md7urpb3並且添加到cocos2dBOX2DDemoContent工程的images文件夾。

接下來,在BOX2DLayer類裏面添加以下聲明:

public static double PTM_RATIO = 32.0;

這裏定義了一個“像素/米”的比率。當你在cocos2d裏面指定一個body在哪個位置時,你使用的單位要是米。但是,我們之前使用的都是像素作爲單位,那樣的話,位置就會不正確。根據Box2d參考手冊,Box2d在處理大小在0.1到10個單元的對象的時候做了一些優化。這裏的0.1米大概就是一個杯子那麼大,10的話,大概就是一個箱子的大小。

  因此,我們並不直接傳遞像素,因爲一個很小的對象很有60×60個像素,那已經大大超過了box2d優化時所限定的大小。因此,如果我們有一個64像素的對象,我們可以把它除以PTM_RATIO,得到2米---這個長度,box2d剛好可以很好地用來做物理仿真。

另外,爲什麼要用double類型呢,這個有個精度的問題,如果用int的話,我曾經嘗試過,籃球直接嵌在牆壁不反彈。

  好了,現在來點有意思的東西。在BOX2DLayer類裏面添加以下聲明

        World world;
        Body body;
        CCSprite ball;

然後重載Init方法並且修改爲:

       public override bool init()

        {

            if (!base.init())

                return false;

            if (!base.init())

            {

                return false;

            }

 

            CCSize winSize = CCDirector.sharedDirector().getWinSize();

 

            CCLabelTTF title = CCLabelTTF.labelWithString("Boxing", "Arial", 24);

            //title.Color = new ccColor3B(0, 255, 255);

            title.position = new CCPoint(winSize.width / 2, winSize.height - 50);

            this.addChild(title, 1);

 

            //Create sprite and add it to the layer

            ball = CCSprite.spriteWithFile(@"images/Ball");

            ball.position = new CCPoint(100, 300);

            this.addChild(ball);

 

            //Create the world

            Vector2 gravity = new Vector2(0.0f, -30.0f);

            bool doSleep = true;

            world = new World(gravity, doSleep);

 

            //Create edges around the entire screen

            BodyDef groundBodyDef = new BodyDef();

            groundBodyDef.position = new Vector2(0, 0);

            Body groundBody = world.CreateBody(groundBodyDef);

            PolygonShape groundBox = new PolygonShape();

            FixtureDef boxShapeDef = new FixtureDef();

            boxShapeDef.shape = groundBox;

            groundBox.SetAsEdge(new Vector2(0, 0), new Vector2((float)(winSize.width / PTM_RATIO), 0));

            groundBody.CreateFixture(boxShapeDef);

            groundBox.SetAsEdge(new Vector2(0, 0), new Vector2(0, (float)(winSize.height / PTM_RATIO)));

            groundBody.CreateFixture(boxShapeDef);

            groundBox.SetAsEdge(new Vector2(0, (float)(winSize.height / PTM_RATIO)),

                new Vector2((float)(winSize.width / PTM_RATIO), (float)(winSize.height / PTM_RATIO)));

            groundBody.CreateFixture(boxShapeDef);

            groundBox.SetAsEdge(new Vector2((float)(winSize.width / PTM_RATIO), (float)(winSize.height / PTM_RATIO)),

                new Vector2((float)(winSize.width / PTM_RATIO), 0));

            groundBody.CreateFixture(boxShapeDef);

 

            //Create ball body and shape

            BodyDef ballBodyDef = new BodyDef();

            ballBodyDef.type = BodyType.Dynamic;

            ballBodyDef.position = new Vector2((float)(100 / PTM_RATIO), (float)(300 / PTM_RATIO));

            ballBodyDef.userData = ball;

            body = world.CreateBody(ballBodyDef);

 

            CircleShape circle = new CircleShape();

            circle._radius = (float)(26.0 / PTM_RATIO);

 

            FixtureDef ballShapeDef = new FixtureDef();

            ballShapeDef.shape = circle;

            ballShapeDef.density = 1.0f;

            ballShapeDef.friction = 0.0f;

            ballShapeDef.restitution = 1.0f;

            body.CreateFixture(ballShapeDef);

 

            this.schedule(tick);

            return true;

        }

呃,這裏有很多陌生的代碼。我們一點點來解釋一下。下面,我會一段段地重複上面的代碼,那樣可以解釋地更加清楚一些。

           CCSize winSize = CCDirector.sharedDirector().getWinSize();

 

            CCLabelTTF title = CCLabelTTF.labelWithString("Boxing", "Arial", 24);

            //title.Color = new ccColor3B(0, 255, 255);

            title.position = new CCPoint(winSize.width / 2, winSize.height - 50);

            this.addChild(title, 1);

 

            //Create sprite and add it to the layer

            ball = CCSprite.spriteWithFile(@"images/Ball");

            ball.position = new CCPoint(100, 300);

            this.addChild(ball);

首先,我們往屏幕中間加入一個精靈。如果你看了前面的教程的話,這裏應該沒有什麼問題。這個加入一個Label是爲了讓精靈能夠更好的顯示,這個是cocos2d-x的一些問題,如果不添加一個Label,精靈的背景是黑的不是透明的。
            //Create the world

            Vector2 gravity = new Vector2(0.0f, -30.0f);

            bool doSleep = true;

            world = new World(gravity, doSleep);

接下來,我們創建了world對象。當我們創建這個對象的時候,需要指定一個初始的重力向量。這裏,我們設置y軸方向爲-30,因此,所有的body都會往屏幕下面下落。同時,我們還指定了一個值,用以指明對象不參與碰撞時,是否可以休眠。一個休眠的對象將不會花費處理時間,直到它與其實對象發生碰撞的時候纔會過來。

            //Create edges around the entire screen

            BodyDef groundBodyDef = new BodyDef();

            groundBodyDef.position = new Vector2(0, 0);

            Body groundBody = world.CreateBody(groundBodyDef);

            PolygonShape groundBox = new PolygonShape();

            FixtureDef boxShapeDef = new FixtureDef();

            boxShapeDef.shape = groundBox;

            groundBox.SetAsEdge(new Vector2(0, 0), new Vector2((float)(winSize.width / PTM_RATIO), 0));

            groundBody.CreateFixture(boxShapeDef);

            groundBox.SetAsEdge(new Vector2(0, 0), new Vector2(0, (float)(winSize.height / PTM_RATIO)));

            groundBody.CreateFixture(boxShapeDef);

            groundBox.SetAsEdge(new Vector2(0, (float)(winSize.height / PTM_RATIO)),

                new Vector2((float)(winSize.width / PTM_RATIO), (float)(winSize.height / PTM_RATIO)));

            groundBody.CreateFixture(boxShapeDef);

            groundBox.SetAsEdge(new Vector2((float)(winSize.width / PTM_RATIO), (float)(winSize.height / PTM_RATIO)),

                new Vector2((float)(winSize.width / PTM_RATIO), 0));

            groundBody.CreateFixture(boxShapeDef);

 接下來,我們爲整個屏幕創建了一圈不可見的邊。具體的步驟如下:

· 首先創建一個body定義結構體,並且指定它應該放在左下角。

· 然後,使用world對象來創建body對象。(注意,這裏一定要使用world對象來創建,不能直接new,因爲world對象會做一些內存管理操作。)

· 接着,爲屏幕的每一個邊界創建一個多邊形shape。這些“shape”僅僅是一些線段。注意,我們把像素轉換成了“meter”。通過除以之前定義的比率來實現的。

· 再創建一個fixture定義,指定shapepolygon shape

· 再使用body對象來爲每一個shape創建一個fixture對象。

· 注意:一個body對象可以包含許許多多的fixture對象。 

            //Create ball body and shape

            BodyDef ballBodyDef = new BodyDef();

            ballBodyDef.type = BodyType.Dynamic;

            ballBodyDef.position = new Vector2((float)(100 / PTM_RATIO), (float)(300 / PTM_RATIO));

            ballBodyDef.userData = ball;

            body = world.CreateBody(ballBodyDef);

 

            CircleShape circle = new CircleShape();

            circle._radius = (float)(26.0 / PTM_RATIO);

 

            FixtureDef ballShapeDef = new FixtureDef();

            ballShapeDef.shape = circle;

            ballShapeDef.density = 1.0f;

            ballShapeDef.friction = 0.0f;

            ballShapeDef.restitution = 1.0f;

            body.CreateFixture(ballShapeDef);

接下來,我們創建籃球的body。這個步驟和之前創建地面的body差不多,但是有下面一些差別需要注意一下:

· 我們指定body的類型爲dynamic body。默認值是static body,那意味着那個body不能被移動也不會參與仿真。很明顯,我們想讓籃球參與仿真。

· 設置bodyuser data屬性爲籃球精靈。你可以設置任何東西,但是,你設置成精靈會很方便,特別是當兩個body碰撞的時候,你可以通過這個參數把精靈對象取出來,然後做一些邏輯處理。

· 這裏使用了一個不同的shape類型--circle shape

· 在這裏,我們需要爲這個fixture指定一些參數,因此,我們沒有使用便捷方法來創建fixture。後面我們會講到這些參數的具體意義。  

this.schedule(tick);

最後一件事情就是調度一個tick方法。

實現tick方法:

        void tick(float dt)

        {

            world.Step(dt, 10, 10);

            for (Body b = world.GetBodyList(); b != null;b = b.GetNext() )

            {

                if (b.GetUserData() != null)

                {

                    CCSprite ballData = (CCSprite)b.GetUserData();

                    ballData.position = new CCPoint((float)(b.GetPosition().X * PTM_RATIO),

                        (float)(b.GetPosition().Y * PTM_RATIO));

                    ballData.rotation = -1 * MathHelper.ToDegrees(b.GetAngle());

                }

            }

        }

第一件事情就是調用world對象的step方法,這樣它就可以進行物理仿真了。這裏的兩個參數分別是速度迭代次數位置迭代次數”--你應該設置他們的範圍在8-10之間。(譯者:這裏的數字越小,精度越小,但是效率更高。數字越大,仿真越精確,但同時耗時更多。8一般是個折中,如果學過數值分析,應該知道迭代步數的具體作用)。

  接下來,我們要使我們的精靈匹配物理仿真。因此,我們遍歷world對象裏面的所有body,然後看bodyuser data屬性是否爲空,如果不爲空,就可以強制轉換成精靈對象。接下來,就可以根據body的位置來更新精靈的位置了。

現在我們再添加一些代碼作爲層的初始化用。

        public static new CCLayer node()

        {

            BOX2DLayer layer = new BOX2DLayer();

            if (layer.init())

            {

                return layer;

            }

            else

                layer = null;

            return layer;

        }

 

然後再修改AppDelegate.cs裏面的applicationDidFinishLaunching。在這個導演類裏面修改初始場景。

修改如下:

            // create a scene. it's an autorelease object

            //CCScene pScene = cocos2dBOX2DDemoScene.scene();

            CCScene pScene = CCScene.node();

            pScene.addChild(Classes.BOX2DLayer.node());

            //run

            pDirector.runWithScene(pScene);

 

            return true;

 

編譯並運行,你應該可以看到球會往下掉,並且會從屏幕底部往上面彈起來。

關於仿真的一些注意事項


  前面我們說後面會討論densityfrictionrestitution參數的意義。

· Density就是單位體積的質量(密度)。因此,一個對象的密度越大,那麼它就有更多的質量,當然就會越難以移動.

· Friction 就是摩擦力。它的範圍是0-1.00意味着沒有摩擦,1代表最大摩擦,幾乎移不動的摩擦。

· Restitution回覆力。它的範圍也是01.0. 0意味着對象碰撞之後不會反彈,1意味着是完全彈性碰撞,會以同樣的速度反彈。


  建議多去改一改這些參數,看看具體會給小球帶來什麼影響。一定要去試哦!
如果你想學習更多有關box2d相關的內容,請繼續關注。接下來會帶來更好的教程。



本次工程下載:http://dl.dbank.com/c0mx9imdgw


深入學習:<cocos2d-x for wp7>使用cocos2d-x和BOX2D來製作一個BreakOut(打磚塊)遊戲(一)




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章