本教程基於子龍山人翻譯的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定義,指定shape爲polygon 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不能被移動也不會參與仿真。很明顯,我們想讓籃球參與仿真。
· 設置body的user 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,然後看body的user
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;
編譯並運行,你應該可以看到球會往下掉,並且會從屏幕底部往上面彈起來。
關於仿真的一些注意事項
前面我們說後面會討論density,friction和restitution參數的意義。
· Density就是單位體積的質量(密度)。因此,一個對象的密度越大,那麼它就有更多的質量,當然就會越難以移動.
· Friction 就是摩擦力。它的範圍是0-1.0,0意味着沒有摩擦,1代表最大摩擦,幾乎移不動的摩擦。
· Restitution回覆力。它的範圍也是0到1.0. 0意味着對象碰撞之後不會反彈,1意味着是完全彈性碰撞,會以同樣的速度反彈。
建議多去改一改這些參數,看看具體會給小球帶來什麼影響。一定要去試哦!
如果你想學習更多有關box2d相關的內容,請繼續關注。接下來會帶來更好的教程。
本次工程下載:http://dl.dbank.com/c0mx9imdgw
深入學習:<cocos2d-x for wp7>使用cocos2d-x和BOX2D來製作一個BreakOut(打磚塊)遊戲(一)