准备工作,首先修改 box2d 的 b2Body 类,
增加一个public 修饰的 bool 类型标识变量 m_isInWater,用于标识物体当前是否在水中。
然后,切到 box2d 的 b2World 类,在 CreateBody 方法的尾部添加一行初始化标识变量的代码:
b2Body* b2World::CreateBody(const b2BodyDef* def)
{
b2Assert(IsLocked() == false);
if (IsLocked())
{
return NULL;
}
void* mem = m_blockAllocator.Allocate(sizeof(b2Body));
b2Body* b = new (mem) b2Body(def, this);
// Add to world doubly linked list.
b->m_prev = NULL;
b->m_next = m_bodyList;
if (m_bodyList)
{
m_bodyList->m_prev = b;
}
m_bodyList = b;
++m_bodyCount;
/** Added By Bruce Yang on 2011.11.25.12.49~ */
b->m_isCuttable = true; // body 默认设置为可被切割的~
b->m_isBalloon = false; // body 默认设置为非气球~
b->m_isInWater = false; // body 默认设置不在水中(即使出生就在水中也不要紧,contactListener会立即做相应设置)~
return b;
}contactListener 的相关代码:
//
// MyContactListener.m
// GameScene
//
// Created by Bruce Yang on 2/18/10.
// Copyright (c) 2012年 EricGameStudio. All rights reserved.
//
#import "MyContactListener.h"
MyContactListener::MyContactListener() : _contacts() {
}
MyContactListener::~MyContactListener() {
}
/**
* 处理物体落水受到浮力的情况~
* 在刚入水的时候,物体的速度将骤减,然后受到一个持续向上的浮力,直到物体脱离水区域为止~
* 本来在 x 轴方向是不应该受到浮力的影响的,但考虑到水的粘滞性,x 轴方向的速度收缩为原来的 0.8 倍~
* 还有就是,角速度的大小也会受到液体粘滞性的影响,因此角速度也要做相应的处理~
*/
void addBuoyancyTag(MyContact contact) {
float ySpeedDecreaseFactor = 0.3f;
float xSpeedDecreaseFactor = 0.7f;
float rSpeedDecreaseFactor = 0.7f;
if(contact.fixtureA == [BYSingle getInstance].buoyancy) {
// 1.y方向的速度在入水的临界点骤减为原来的 0.3 倍~
b2Body* body = contact.fixtureB->GetBody();
b2Vec2 oldSpeed = body->GetLinearVelocity();
body->SetLinearVelocity(b2Vec2(oldSpeed.x * xSpeedDecreaseFactor, oldSpeed.y * ySpeedDecreaseFactor));
body->SetAngularVelocity(body->GetAngularVelocity() * rSpeedDecreaseFactor);
// 2.标识为 “受水浮力影响” 状态~
body->m_isInWater = true;
}
if(contact.fixtureB == [BYSingle getInstance].buoyancy) {
b2Body* body = contact.fixtureA->GetBody();
b2Vec2 oldSpeed = body->GetLinearVelocity();
body->SetLinearVelocity(b2Vec2(oldSpeed.x * xSpeedDecreaseFactor, oldSpeed.y * ySpeedDecreaseFactor));
body->SetAngularVelocity(body->GetAngularVelocity() * rSpeedDecreaseFactor);
body->m_isInWater = true;
}
}
// 移除 “受水浮力影响” 标识(切割水中物体的时候会出错,蛋疼)~
void removeBuoyancyTag(MyContact contact) {
if(contact.fixtureA == [BYSingle getInstance].buoyancy) {
contact.fixtureB->GetBody()->m_isInWater = false;
}
if(contact.fixtureB == [BYSingle getInstance].buoyancy) {
contact.fixtureA->GetBody()->m_isInWater = false;
}
}
void MyContactListener::BeginContact(b2Contact* contact) {
MyContact myContact = { contact->GetFixtureA(), contact->GetFixtureB() };
// added by Bruce Yang on 2012.04.13.13.34~
addBuoyancyTag(myContact);
_contacts.push_back(myContact);
}
void MyContactListener::EndContact(b2Contact* contact) {
MyContact myContact = { contact->GetFixtureA(), contact->GetFixtureB() };
// added by Bruce Yang on 2012.04.13.13.34~
removeBuoyancyTag(myContact);
vector::iterator pos;
pos = find(_contacts.begin(), _contacts.end(), myContact);
if (pos != _contacts.end()) {
_contacts.erase(pos);
}
}
void MyContactListener::PreSolve(b2Contact* contact, const b2Manifold* oldManifold) {
}
void MyContactListener::PostSolve(b2Contact* contact, const b2ContactImpulse* impulse) {
}
主场景的tick方法:
-(void) tick: (ccTime) dt {
int32 velocityIterations = 8;
int32 positionIterations = 1;
_world->Step(dt, velocityIterations, positionIterations);
/** Iterate over the bodies in the physics world */
for (b2Body* b = _world->GetBodyList(); b; b = b->GetNext()) {
if(b->GetUserData() != NULL) {
// Synchronize the AtlasSprites position and rotation with the corresponding(相应的) body
CCSprite *actor = (CCSprite*)b->GetUserData();
CGPoint po = CGPointMake(b->GetPosition().x*PTM_RATIO, b->GetPosition().y*PTM_RATIO);
if(![MGameScene isPositionOutOfBounds:po]) { // 如果没有越界的话,实时更新~
actor.position = po;
actor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
// 给接触水区域的物体施加持续向上的浮力~
if(b->m_isInWater) {
float absG = fabs(_world->GetGravity().y);
float mass = b->GetMass();
float density = 0;
if(b->GetFixtureListCount() != 0) {
density = b->GetFixtureList()->GetDensity();
} else {
density = 1.2f;
}
float volumn = mass / density;
// mass = rho * volumn,水的密度是1.0,
// 但为了使密度为 1.0 的冰块不致于很慢的沉入水中,将水的密度调为 0.9 以减小浮力~
float waterMass = 0.9f * volumn;
b->ApplyForceToCenter(b2Vec2(0, waterMass * absG));
}
} else {
if(actor.tag != TAG_STATE_READY_TO_BE_REMOVED) {
[actor removeFromParentAndCleanup:YES];
}
[MGameScene byDestroyBody:b]; // 对于越界的 body,一律销毁之~
}
}
}
}值得一提的是:
我开始是将表示变量放在 CCSprite 类里面的,这样会出现一个问题:
contactListener 当物体脱离水区域调用 removeBuoyancyTag() 方法的时候
如果在水中将该 body 切割的话(切割会将旧 body 销毁,并生成两份新的 body),从 body 里面取出 CCSprite 对象会报出错误,
后来在我将 isInWater 这个标识放到 b2Body 里面后,干掉了这个bug(没做细致的测试,不过试了蛮多次,没发现出问题)~