天天看點

使用Box2d實作物體在液體中的漂浮效果(一)



今天我們來學習使用Box2d實體引擎制作物體在液體中的漂浮效果,沒有新的技術,隻是綜合運用了以前博文中介紹過的技術。

注:如果使用Box2d flash的版本,可以直接使用buoyancycontroller來實作本文制作的效果。

首先我們來分析一下大體思路:

首先需要在場景中建立一個液體區域(以下簡稱液體,我們不做液體流動的模拟,隻是建立一個矩形的區域),由于需要檢測物體與液體的接觸(Contact),是以液體應該也是一個b2Body對象,隻不過液體不檢測碰撞,故我們将其設定為傳感器(Sensor)。

要實作物體與液體的接觸檢測,我們需要建立一個繼承自b2ContactListener的子類,重寫BeginContact和EndContact方法,在BeginContact和EndContact中對物體的速度進行調整(因為液體具有一定的阻力和粘稠度)。

物體在液體中還會受到浮力的作用,浮力的大小與物體浸入液體的體積有關,而要計算物體浸入液體的體積,就要用到射線投射的相關知識與多邊形面積計算的相關知識了。

好了,下面我們開始來制作,首先還是看一下運作效果截圖:

使用Box2d實作物體在液體中的漂浮效果(一)

首先我們以cocos2d iOS withBox2d為模闆建立工程(Box2d版本為2.3.1),接着在HelloWorldLayer中添加下面的方法建立液體區域(也就是上面圖檔中的下半部分綠色區域):

-(void)createWater {

   CGSize winSize = [[CCDirectorsharedDirector] winSize];

   float height = winSize.height;

   float width = winSize.width;

   b2BodyDef bodyDef;

   bodyDef.type = b2_staticBody;

   bodyDef.position.Set(0, 0);

   b2Body* body =world->CreateBody(&bodyDef);

   b2FixtureDef fixtureDef;

   fixtureDef.isSensor = true;

   b2PolygonShape* shape = newb2PolygonShape();

   shape->SetAsBox(width / PTM_RATIO,height * 0.4f / PTM_RATIO);

   fixtureDef.shape = shape;

   body->CreateFixture(&fixtureDef);

}

該方法在場景中添加了一個靜态的矩形物體,将其設定為傳感器。

在initPhysics方法中調用該方法即可。

接着我們修改一下addNewSpriteAtPosition方法:

-(void)addNewSpriteAtPosition:(CGPoint)p

{

b2BodyDef bodyDef;

bodyDef.type = b2_dynamicBody;

bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);

b2Body *body = world->CreateBody(&bodyDef);

b2PolygonShape dynamicBox;

   float width = CCRANDOM_0_1() * 1.5f + 0.5f;

   float height = CCRANDOM_0_1() * 1.0f +0.5f;

dynamicBox.SetAsBox(width, height);

b2FixtureDef fixtureDef;

fixtureDef.shape = &dynamicBox;   

fixtureDef.density = 0.7f;

fixtureDef.friction = 0.3f;

body->CreateFixture(&fixtureDef);

}

修改前的方法會建立一個帶有紋理貼圖的大小為1平方米的盒子,盒子密度為1,修改後,我們去掉盒子的貼圖紋理,并且使用随機數将盒子的長寬修改為随機值,并将盒子的密度修改為0.7f(這裡我們液體密度是1,為了使物體漂浮,需要盒子密度小于液體密度)。

修改完成後我們嘗試在場景中點選添加盒子,發現盒子全部“沉到”液體底部,沒有任何效果。

我們繼續添加一個類MyContactListener,繼承自b2ContactListener,類聲明如下:

#import"Box2D.h"

classMyContactListener : public b2ContactListener {

public:

   void BeginContact(b2Contact* contact);

   void EndContact(b2Contact* contact);

};

實作如下:

#import"MyContactListener.h"

#import"FloatingObjectData.h"

voidMyContactListener::BeginContact(b2Contact* contact) {

   //擷取接觸的兩個fixture

   b2Fixture* fixtureA =contact->GetFixtureA();

   b2Fixture* fixtureB =contact->GetFixtureB();

   //由于場景中隻有一個傳感器(即液體區域),是以如果碰撞的兩個物體都不是傳感器,則

   //不是物體與液體的互相作用,忽略這次接觸

   if (!fixtureA->IsSensor() &&!fixtureB->IsSensor()) {

       return;

   }

   //擷取與液體接觸的物體對象

   b2Body* body = fixtureA->IsSensor() ?fixtureB->GetBody() : fixtureA->GetBody();

   //物體浸入液體之前的速度

   b2Vec2 bodyVelocity =body->GetLinearVelocity();

   //物體浸入液體之後由于受到液體的作用,将其速度降低

   body->SetLinearVelocity(b2Vec2(bodyVelocity.x * 0.7f, bodyVelocity.y* 0.3f));

   //同時降低物體的角速度

   body->SetAngularVelocity(body->GetAngularVelocity() * 0.7);

   //設定物體的UserData,标記其已經浸入液體

   FloatingObjectData* objectData =[[FloatingObjectData alloc] init];

   objectData.isUnderWater = true;

   body->SetUserData(objectData);

}

voidMyContactListener::EndContact(b2Contact* contact) {

   b2Fixture* fixtureA =contact->GetFixtureA();

   b2Fixture* fixtureB =contact->GetFixtureB();

   if (!fixtureA->IsSensor() &&!fixtureB->IsSensor()) {

       return;

   }

   b2Body* body = fixtureA->IsSensor() ?fixtureB->GetBody() : fixtureA->GetBody();

   //設定物體的UserData,标記其已經離開液體

   FloatingObjectData* objectData =(FloatingObjectData*)body->GetUserData();

   if (objectData != nil) {

       objectData.isUnderWater = false;

   }

}

代碼中添加了詳細的注釋,不做過多解釋了。代碼裡面我們用到了一個FloatingObjectData類,這個類使我們建立的用來記錄浸入液體的物體的狀态的類,聲明如下:

@interfaceFloatingObjectData : NSObject

@property BOOLisUnderWater;

@propertyfloat volumnUnderWater;

@end

實作:

#import"FloatingObjectData.h"

@implementationFloatingObjectData

@synthesizeisUnderWater;

@synthesizevolumnUnderWater;

-(id)init {

   if (self = [super init]) {

       isUnderWater = false;

       volumnUnderWater = 0;

   }

   return self;

}

@end

比較簡單,定義了兩個屬性,isUnderWater用來标記物體是否在液體中,volumnUnderWater用來記錄物體浸入液體的體積。

上面的代碼添加好之後,我們在initPhysics方法中添加下面的初始化代碼:

MyContactListener*myContactListener = new MyContactListener();

world->SetContactListener(myContactListener);

這樣world就會使用我們定義好的ContactListener中重載的兩個方法來處理碰撞了。

現在再次運作程式,我們發現添加物體的時候,當物體落入水中,能夠感覺到物體受到阻力導緻速度減慢了一點,但是物體仍然不能夠漂浮。關于物體浮力效果的制作部分我們留到下一篇教程中完成。