cocos2dx 3.0 物理引擎概述

概述

在遊戲中模擬真實的物理世界是個比較麻煩的,通常都是交給物理引擎來做。比較知名的有Box2D了,它幾乎能模擬所有的物理效果,而chipmunk則是個更輕量的引擎等。在Cocos2d-x 2.0中,遊戲直接使用物理引擎,引擎提供了一個簡單的CCPhysicsSprite,處理了物理引擎的body與CCSprite的關係,而物理引擎的其他要素並沒有和引擎對應起來,遊戲需要選擇直接調用chipmunk或Box2D的api來處理邏輯。然而直接使用物理引擎是比較複雜的,它物理引擎的接口參賽很多,很複雜,而且需要開發人員對物理引擎和Cocos2d-x都很瞭解,才能把兩者融合得很好。

這個情況在3.0中有了改變,全新的Physics integration,把chipmunk和Box2D封裝到引擎內部,遊戲開發不用關心底層具體是用的哪個物理引擎,不用直接調用物理引擎的接口。

物理引擎和Cocos2d-x進行了深度融合:

  • 物理世界被融入到Scene中,即當創建一個場景時,就可以指定這個場景是否使用物理引擎。
  • Node自帶body屬性,也就是sprite自帶body屬性。
  • Cocos2d-x 3.0對物理引擎的Body(PhysicsBody),Shape(PhysicsShape),Contact(PhysicsContact),Joint(PhysicsJoint),World(PhysicsWorld)進行了封裝抽象,使用更簡單。
  • 更簡單的碰撞檢測監聽EventListenerPhysicsContact。

創建帶物理引擎的遊戲工程

在3.0中創建工程由/tools/project-creator下的create_project.py腳本完成。

默認創建的工程已支持物理引擎,內部啓用的是chipmunk。

你可以註釋掉ccConfig.h裏的CC_USE_PHYSICS宏定義去關閉它。

創建帶物理世界的scene

下面的代碼創建帶物理世界的scene,並傳遞給child layer。

在.h文件中添加以下代碼

void setPhyWorld(PhysicsWorld* world){m_world = world;}
private:
    PhysicsWorld* m_world;

在.cpp文件的createScene方法中添加以下代碼

auto scene = Scene::createWithPhysics();
scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);

auto layer = HelloWorld::create();
layer->setPhyWorld(scene->getPhysicsWorld());

Scene類有了新的靜態工廠方法,createWithPhysics(),創建帶物理世界的scene。
Scene的getPhysicsWorld()方法獲取PhysicsWorld實例,

PhysicsWorld的setDebugDrawMask()方法,在調試物理引擎中是很有用的,它把物理世界中不可見的shape,joint,contact可視化。當調試結束,遊戲發佈的時候,你需要把這個debug開關關閉。

通過setPhyWorld()方法來傳遞PhysicsWorld給ChildLayer。一個scene只有一個PhysicsWorld,其下的所有layer共用一個PhysicsWorld實例。

PhysicsWorld默認是有帶重力的,默認大小爲Vect(0.0f, -98.0f), 你也可以通過的setGravity()方法來設置Physics的重力參數。

你還可以通過setSpeed()來設置物理世界的模擬速度。

創建物理邊界

我們知道物理世界中,所有物體受重力的影響。
物理引擎提供staticShape創建一個不受重力影響的形狀,在Cocos2d-x 2.0中,我們需要了解物理引擎的staticShape相關的各種參數來完成邊界設置。

在3.0中,PhysicsShape屬於Node的一個屬性,要設置PhysicsWorld的屬性,都需要通過一個Node實例來中介傳達。

下面的代碼展示如何創建一個圍繞屏幕四周的物理邊界。

Size visibleSize = Director::getInstance()->getVisibleSize();
auto body = PhysicsBody::createEdgeBox(visibleSize, PHYSICSBODY_MATERIAL_DEFAULT, 3);
auto edgeNode = Node::create();
edgeNode->setPosition(Point(visibleSize.width/2,visibleSize.height/2));
edgeNode->setPhysicsBody(body);
scene->addChild(edgeNode);

PhysicsBody包含很多工廠方法,createEdgeBox創建一個矩形邊界,參數含義依次是:

  1. 矩形區域大小,這裏設置爲visibleSize。
  2. 設置材質,可選參數,默認爲PHYSICSBODY_MATERIAL_DEFAULT。
  3. 邊線寬度,可選參數,默認爲1。

然後我們創建一個Node,把剛纔創建的body附加到Node上,並設置好Node的position爲屏幕中心點。
最後,把Node添加到scene。

Node的addChild方法,在3.0中,有對物理body做處理,它會自動把node的body設置到scene的PhysicsWorld上去。

PhysicsBody中的工程方法,針對參數設置的body大小,會自動創建對應的PhysicsBody和一個PhysicsShape,這也是通常情況下,直接使用物理引擎創建一個body需要做的事情。3.0的Physics integration極大的簡化了使用物理引擎的代碼量。

創建受重力作用的sprite

在3.0中創建一個受重力作用的Sprite也很簡單。

void HelloWorld::addNewSpriteAtPosition(Point p)
{
    auto sprite = Sprite::create("circle.png");
    sprite->setTag(1);
    auto body = PhysicsBody::createCircle(sprite->getContentSize().width / 2);
    sprite->setPhysicsBody(body);
    sprite->setPosition(p);
    this->addChild(sprite);
}

首先創建一個sprite,然後用PhysicsBody::createCircle創建一個圓形的body附加在sprite上。
整個過程和之前創建邊界的過程是一致的。

你也可以創建自己的PhysicsShape然後通過PhysicsBody的addShape()方法加入到body中,但需要注意的是,shape的重量(mess,通過密度和體積計算得出)和轉動慣量(moment)是會自動加到body上的,並且shape加到body後是不能改變它相對於body的位置和角度的,不需要時可以通過removeShape()來移除。

碰撞檢測

Cocos2d-x中,事件派發機制做了重構,所有事件均有事件派發器統一管理。物理引擎的碰撞事件也不例外,
下面的代碼註冊碰撞begin回調函數。

auto contactListener = EventListenerPhysicsContact::create();
contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegin, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);

碰撞檢測的所有事件由EventListenerPhysicsContact來監聽,創建一個實例,然後設置它的onContactBegin回調函數,CC_CALLBACK_1是Cocos2d-x 3.0使用C++ 11的回調函數指針轉換助手函數,由於onContactBegin回調有一個參數,所有這裏使用CC_CALLBACK_1來做轉換。

_eventDispatcher是基類Node的成員,Layer初始化後就可直接使用。

你還可以使用EventListenerPhysicsContactWithBodiesEventListenerPhysicsContactWithShapesEventListenerPhysicsContactWithGroup 來監聽你感興趣的兩個物體、兩個形狀,或者某組物體的碰撞事件,但是要注意設置物體碰撞相關的mask值(下面會詳細說明),因爲物體碰撞事件在默認情況下是不接收的,即使你創建了相應的EventListener。

PhysicsBody碰撞相關的mask設置和group設置跟Box2D的設置是一致的。

mask設置分爲**CategoryBitmask**, ContactTestBitmask 和 CollisionBitmask,你可以通過相關的get/set接口來獲得或者設置他們。

他們是通過邏輯與來進行測試的。

當一個物體的**CategoryBitmask**跟另一個物體的**ContactTestBitmask**的邏輯與結果不爲零時,將會發送相應的事件,否則不發送。

而當一個物體的**CategoryBitmask**跟另一個物體的**CollisionBitmask**的邏輯與測試結果不爲零時,將會發生碰撞,否則不發生碰撞。

注意,在默認情況下**CategoryBitmask**的值爲0xFFFFFFFF,**ContactTestBitmask**的值爲0x00000000,**CollisionBitmask**的值爲0xFFFFFFFF,也就是說默認情況下所有物體都會發生碰撞但不發送通知。


另一個碰撞相關的設置是**group**(組),當它大於零時,同組的物體將發生碰撞,當它小於零時,同組的物體不碰撞。注意,當**group**不爲零時,他將忽略mask的碰撞設置(是否通知的設置依然有效)。

EventListenerPhysicsContact裏有四個碰撞回調函數,他們分別是onContactBeginonContactPreSolveonContactPostSolveonContactSeperate

在碰撞剛發生時,onContactBegin會被調用,並且在此次碰撞中只會被調用一次。你可以通過返回true或者false來決定物體是否發生碰撞。你可以通過PhysicsContact::setData()來保存自己的數據以便用於後續的碰撞處理。需要注意的是,onContactBegin返回flase時,onContactPreSolveonContactPostSolve將不會被調用,但onContactSeperate必定會被調用。

onContactPreSolve發生在碰撞的每個step,你可以通過調用PhysicsContactPreSolve的設置函數來改變碰撞處理的一些參數設定,比如彈力,阻力等。同樣你可以通過返回true或者false來決定物體是否發生碰撞。你還可以通過調用PhysicsContactPreSolve::ignore()來跳過後續的onContactPreSolveonContactPostSolve回調事件通知(默認返回true)。

onContactPostSolve發生在碰撞計算完畢的每個step,你可以在此做一些碰撞的後續處理,比如摧毀某個物體等。

onContactSeperate發生在碰撞結束兩物體分離時,同樣只會被調用一次。它跟onContactBegin必定是成對出現的,所以你可以在此摧毀你之前通過PhysicsContact::setData()設置的用戶數據。

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