Cocos2d-x之初級物理引擎

我連什麼是重力都不懂,卻讓我開始用物理引擎。c++

zombie

一切故事發生的背景

同濟大學軟件學院每一個學期會要求學生獨立或者組隊完成一個大項目,因而2016年的大項目是用cocos2d-x這款引擎製做一個本身的遊戲(我最終作的網遊,請勿模仿)。xcode

以前寫了一篇cocos2d-x關於鍵盤按住事件的教程,不足之處還請你們多多指出。app

在本身作練習的過程當中,逐漸接觸到了碰撞,行走,降落一類的行爲。我發現,若是所有由本身來實現,不只實現起來複雜,並且執行效率也不必定見得高,因此我決心開始學習cocos2d-x Physics 2D。函數

物理引擎的基礎

兩種物理引擎

根據官方文檔的介紹,目前有兩大重要的物理引擎, Box2D 和 Chipmunk,而且cocos2d-x已經集成了它們,在3.x版本中可基於Chipmunk的核心API的物理引擎使用。學習

一句話: cocos2d-x 3.x版本中使用Chipmunk物理引擎更加方便了,你們都升級成3.x吧=。=url

爲何要用物理引擎

我爲何要用物理引擎?舉個開發中遇到的問題,在上一期的博文中,咱們成功建立了一個hero sprite(對,就是那個可愛的殭屍)。如今咱們要再建立一個堅果牆 sprite,叫作 wall sprite,它的做用是:hero會被wall而沒法繼續前進。spa

你們都知道,若是什麼都不設置,咱們控制hero的時候,是會直接穿過wall sprite的。這確定不是咱們想要的,機智的霄同窗就想到了一個辦法:獲取hero要走的下一步位置(仔細看看上一期,就會發現這並不難),而後再判斷這個位置(Point)是否是存在一個精靈,若是是,就強制取消移動命令。debug

好辦法啊,思路清晰,我簡直要爲他鼓掌了。調試

方法雖好,惋惜我不會。以前提到過,我是一個呆呆呆呆的初學者,我只想用最方便的方式來實現我要的效我甚至不惜犧牲程序的效率來追求減小代碼量,一想到要再次封裝一大堆,我就燙燙燙。code

cocos2d-x physics 2D就能夠完美解決這個問題,並且方法十分簡單。

存在物理規律的世界?

咱們這個世界受着來自物理規律的支配,那麼物理引擎建立出來的scene也一樣要存在某種物理規律,身爲造物主的你能夠自由定義這些規律。

首先,咱們在Demo.cpp中建立一個物理世界。

Scene* Demo::createScene()
{
    auto scene = Scene::createWithPhysics();
    auto layer = HelloWorld::create();
    scene->addChild(layer);
    return scene;
}

很簡單對吧?只須要將Scene::create改爲Scene::createWithPhysics,在這裏scene中的物理世界就算建立成功啦。

從新建立sprites

上一次教程中,咱們建立了一個hero sprite, 建立的方法是這樣的:

auto hero = Sprite::create("hero.png");

太愚蠢了是否是?高貴的物理世界怎麼能這樣呢?...

很遺憾,這樣的建立方式是沒錯的,咱們依然沿襲這個方法來建立sprite。

若是此時,你開啓調試,就會發現sprite沒有開始自由落體運動。那究竟是哪裏出現了問題呢?

這樣建立出來的sprite只是一個空殼而已,它沒有任何靈魂和信仰的力量(Physics body),咱們此時須要給這隻可憐的小傢伙+1s信仰。

auto heroBody = PhysicsBody::createBox(hero->getContentSize());
hero->setPhysicsBody(bodyHero);

這樣,它擁有了一個Physics body。如今再調試,你就發現咱們可愛的hero已經在自由落體了。

但是,你不想聽聽createBox究竟是什麼意思嗎?body存在一個邊界,裏面的空間表示sprite的實體。而這個邊界有幾種存在的形態:矩形、圓形和多邊形。

在剛剛的例子中,咱們建立了一個矩形的邊界,規定了邊界範圍是hero sprite的大小(png圖片)。再說說以後要建立的堅果牆吧,它的形狀基本趨近一個圓形,那麼則可使用createCircle的方法來建立它的body.

setPhysicsBody顧名思義,就是將咱們的靈魂(body)賦給hero sprite.

掉...掉下去了

看着咱們的hero可以實現自由落體,我也很開心啊。但是...不一下子,它就掉到屏幕外面去了,怎麼辦?

恍然大悟,咱們的背景圖片(它也是一個sprite,這不能忘啊),沒有被添加body。可是咱們又發現一個問題,body是進不去的,因此,物理引擎專門提供了一個方法createEdgeBox,只建立邊界。

# Demo.cpp

auto map = Sprite::create("background.png");
auto mapFrame = PhysicsBody::createEdgeBox(map->getContentSize());

map->setPhysicsBody(mapFrame);

再次調試,hero穩穩地落在了地面上。

支配個人世界

雖然我不知道什麼是G = mg,可是我知道世界上必定是有重力的,嗯。因此咱們建立出來的scene中的物體也須要受到重力的做用。
API
看API文檔瞭解到咱們須要傳入一個Vec2類型的重力參數,第一個和第二個數值是什麼意思呢?我經過xcode進行調試發現:
debug
第二個數值爲默認的重力,98。那麼咱們就能夠經過setGravity方法來設置屬於咱們本身的重力了。

# Demo.cpp

auto scene = Scene::createWithPhysics();
scene->getPhysicsWorld()->setGravity(Vec2(0.0f, -500.0f));
...

先使用物理scene中的getPhysicsWorld方法來獲取咱們的物理世界,而後再設置重力,通過調試就能夠看見hero sprite和wall sprite飛快地加速降低了。

還有不少好玩的功能強勁的API能夠供你們使用,好比getAllBodies,都等着咱們去探索。

關係到具體body的屬性

那麼我想爲hero sprite和wall sprite添加一些屬於他們本身的物理屬性,怎麼作到呢?

就像現實世界中有人質量大,有人質量小同樣,我要給堅果牆設置一個極大的質量以致於不可動搖,而殭屍(our hero)就能夠自由行動(如何自由行動上一期已經說過啦~)。

就像API中所提到的,能夠在建立Physics body的時候,就傳入一個physics material進去。
API

以前咱們只是傳入一個sprite content進去(第一個參數),如今要傳入更多的參數,使hero sprite的physics body達到咱們預期的效果。

# Demo.cpp

auto hero = Sprite::create("hero.png");
auto heroBody = PhysicsBody::createBox(hero->getContentSize(), PhysicsMaterial(1.0f, 1.0f, 20.0f));

什麼是Physics Material呢?physics material

根據API可知,咱們能夠調用這個類的構造函數來建立一個physics material,使得physics body獲取必定量的材質。密度,還原力和摩擦力。對於咱們的需求來講,只要設置必要的摩擦力就夠了。

第三個參數offset爲偏移量,想要physics body和sprite的位置錯開的話,能夠填寫這個參數。


heroBody->setDynamic(true);      //設置爲靜態的剛體,不受重力影響  
heroBody->setMass(999999);  //設置剛體不可動  
heroBody->setRotationEnable(false);      //設置剛體不可轉動  
heroBody->getShape(0)->setRestitution(1.0f);

這些都是能夠在API文檔中找到設置physics body的方法,學會以後就能夠爲所欲爲地建立屬於本身的物理場景了。

沒有重力的世界

不是全部遊戲都是2D橫版闖關的,好比上帝視角。
重力在這個場景中存在嗎?存在,可是它不是明目張膽地表現出來。就好比一個個小棋子,定格在棋盤上,此時咱們不能爲這個場景添加劇力,因而:

# Demo1.cpp

auto scene = Scene::createWithPhysics();
scene->getPhysicsWorld()->setGravity(Vec2(0.0f, 0.0f));
...

而且還要爲sprite設置不受重力影響的效果。

# Demo1.cpp
auto sprite = Sprite::create("sprite.png");
auto spriteBody = PhysicsBody::createBox(sprite->getContentSize());
spriteBody->setGravityEnable(false);

這樣,咱們的小棋子就定格在棋盤上了。

動動動動起來

既然沒有了重力,咱們如何讓它們在存在一個做用力的狀況下,讓它們停下來呢?

首先,你須要一個做用力。applyForce和applyImpulse這兩個方法可以很好地幫助咱們建立給物體施加的力。

# Demo1.cpp

spriteBody->applyForce(Vec2(100.0f, 100.0f));
// spriteBody->applyImpulse(Vec2(100.0f, 100.0f));

咱們將物體發射到點100.0, 100.0的位置方向去。我還沒來得及解釋這兩個方法是什麼意思的時候,心急的朋友就立刻開始調試了,結果發現sprite並無按照預期的那樣動起來。這是爲何呢?

由於力不夠大啊孩子,很神奇的是,咱們彷佛只規定了力的反向而力的大小並無被規定,可是又如何衡量一個力的大小呢?這是一個很使人糾結的問題。

force

且先來看文檔, 參數只要求填入一個Vec2類型的數值,並且註釋是force ...

會不會是默認了添加1N的力呢?因而我將代碼改爲下面這樣:

# Demo1.cpp

spriteBody->applyForce(Vec2(100.0f, 100.0f) * 1000);
// spriteBody->applyImpulse(Vec2(100.0f, 100.0f) * 1000);

果真,精靈動了起來。可是問題又來了,不一會,我就發現精靈根本沒有停下來的意思,它在不停地運動。

形成這種問題,主要有兩個緣由:一、沒有摩擦力;二、添加力的方式存在問題。

趁熱打鐵,咱們先來解決力的問題。applyForce有什麼問題嗎?咱們仔細看看API文檔就會發現,這是添加了一個持續的力,這個力會不停地添加在sprite上,直到你手動地將其停下來。

那麼咱們須要一個瞬間的力,就相似彈弓同樣。applyImpulse這個時候就出場啦。這個方法能爲物體添加一個瞬時的力。

impulse

# Demo1.cpp

spriteBody->applyImpulse(Vec2(100.0f, 100.0f) * 1000);
// spriteBody->applyForce(Vec2(100.0f, 100.0f) * 1000);

快快快快快停下

但是咱們發現精靈仍是不能很好地停下,可是至少它不會像以前那麼飛奔了。

剛剛說了,摩擦力存在問題。咱們不是已經設置過摩擦力了嗎?它會有什麼問題呢?

摩擦力不表明空氣阻力,在cocos2d-x的物理引擎建立的世界中,是默認不存在空氣阻力這個高大上的屬性的。

完蛋,物理引擎都沒提供的功能,讓我如何是好啊。很幸運的是,physics body提供了一個叫作setLinearDamping(設置線性阻尼)的方法。

linear damping

這個方法能夠很好地使在無重力狀態下的物體停下來。

# Demo1.cpp

...
spriteBody->setLinearDamping(5.0f);

我將sprite body的阻尼設置爲5.0f,其所產生的具體效果,確定要在調試中才能看出。
很好,sprite在飛一段時間後,能很好地停下來了。

爛尾

固然物理引擎的魅力到這裏還並無被徹底探索出來,只是給你們一個系統地學習方案而已。

仍是那句老話,我但願將所學的一切用來解決實際的問題並將其轉化爲生產力,以上的所有都是我在學習中瞭解到的,每一章節都包含了不少小坑坑,不斷地填補,以致刻骨銘心。

以上。

相關文章
相關標籤/搜索