在Cocos2d-x中集成了2个物理引擎,一个是Chipmunk,一个是Box2D。前者是用C语言编写的,文档和例子相对较少;Box2D是用C++写的,并且有比较完善的文档和资料。所以在需要使用物理引擎的时候,大多数开发者会选择Box2D。Box2D是一款用来模拟刚体在物理世界运动的仿真引擎。通过Box2D物理引擎,世界中的物体就可以按照运动定律进行运动。
注:Box2D下的类都是以b2为前缀的,希望不要与你的命名相冲突
1. 首先我们介绍下需要用到的基本概念。
世界(world) :世界代表了一个遵循物理规律的空间,所有的物体都在世界中运动,世界具有创建销毁刚体,创建销毁关节等功能
刚体(body) :一块十分坚硬的物质,它上面的任何两点之间的距离都是完全不变的。刚体具体划分为静态刚体,动态刚体和棱柱刚体(漂浮刚体)
形状(shape):一块严格依附于刚体(body)的 2D 碰撞几何结构(collision geometry)。形状通过关联附加到刚体上,这样刚体就具备了视觉上的外形。
关节(joint):它是一种用于把两个或多个物体固定到一起的约束。Box2D 支持的关节类型有:旋转,棱柱,距离等等。关节可以支持限制(limits)和马达(motors)
关节限制(joint limit):一个关节限制(joint limit)限定了一个关节的运动范围。例如人类的胳膊肘只能做某一范围角度的运动
关节马达(joint motor):一个关节马达能依照关节的自由度来驱动所连接的物体。例如,你可以使用一个马达来驱动一个肘的旋转
2. 我们现在来简单的使用下Box2D,体验一下模拟的物理世界
(1)首先需要创建世界,创建世界需要两个步骤,首先生成重力向量,然后根据重力向量生成世界
bool HelloWorld::init()
{
if ( !CCLayer::init() )
{
return false;
}
// b2Vec2带的两个参数是表示x,y方向的重力大小,正负表示方向
b2Vec2 gravity(0.0f,-10.0f);
// 创建Box2D的世界
world = new b2World(gravity);
// 设置刚体休眠(当刚体到达边界的时候,停止对刚体的计算,节约CPU)
world->SetAllowSleeping(true);
// 使用连续的物理检测
world->SetContinuousPhysics(true);
// 注册碰撞检测的监听
world->SetContactListener(this);
// 创建漂浮刚体
addBird(1, 3.33, b2_kinematicBody);
// 创建静态刚体
addGrass(5.0, 0.5, b2_staticBody);
// 设置屏幕可以触摸
setTouchEnabled(true);
// 开辟线程回调模拟
this->scheduleUpdate();
return true;
}
(2)创建非静态刚体
void HelloWorld::addBird(float x,float y,b2BodyType objectType) { /***** 构建刚体的参数(包括位置,类型) *****/ // 定义刚体用到的精灵 CCSprite *sprite = CCSprite::create("bird.png"); addChild(sprite); // 配置刚体的定义(类似隐形,没有肉体的刚体) b2BodyDef def; def.position = b2Vec2(x, y); // 设置位置 def.type = objectType; // 配置刚体的类型,分为三类:静态刚体,漂浮刚体,动态刚体 if (objectType == b2_kinematicBody) { def.linearVelocity = b2Vec2(2, 0); // 两个参数分别代表横向速度和纵向速度 } // 定义形状(此处为多边形) b2PolygonShape birdShape; // 定义边界(世界边界),参数是半宽,半高,因为生成的大小是其传入参数的2倍 birdShape.SetAsBox(sprite->getContentSize().width / RATIO / 2, sprite->getContentSize().height / RATIO / 2); // 配置刚体的体积(材料(有肉的实体)) b2FixtureDef fixtureDef; fixtureDef.density = 1; // 密度 fixtureDef.friction = 0.3; // 摩擦因数 fixtureDef.restitution = 0.8f; // 反弹效果 fixtureDef.shape = &birdShape; // 配置刚体的实体 /***** 构建刚体的形体 *****/ // 创建刚体 birdBody = world->CreateBody(&def); // 将材料加入到刚体里 birdBody->CreateFixture(&fixtureDef); // 绑定精灵到刚体 birdBody->SetUserData(sprite); }
(3) 添加静态刚体
void HelloWorld::addGrass(float x,float y,b2BodyType objectType) { /***** 构建刚体的参数(包括位置,类型) *****/ // 定义刚体用到的精灵 CCSprite *sprite = CCSprite::create("grass.png"); addChild(sprite,1); // 配置刚体的定义(类似隐形,没有肉体的刚体) b2BodyDef def; def.position = b2Vec2(x, y); // 设置位置 def.type = objectType; // 配置刚体的类型,分为三类:静态刚体,漂浮刚体,动态刚体 // 定义形状(此处为多边形) b2PolygonShape grassShape; // 定义边界(世界边界),参数是半宽,半高,因为生成的大小是其传入参数的2倍 grassShape.SetAsBox(sprite->getContentSize().width / RATIO / 2 , sprite->getContentSize().height / RATIO / 2); // 配置刚体的体积(材料(有肉的实体)) b2FixtureDef fixtureDef; fixtureDef.density = 1; fixtureDef.friction = 0.3; fixtureDef.shape = &grassShape; // 配置刚体的实体 /***** 构建刚体的形体 *****/ // 创建刚体 grassBody = world->CreateBody(&def); // 将材料加入到刚体里 grassBody->CreateFixture(&fixtureDef); // 绑定精灵到刚体 grassBody->SetUserData(sprite); }
(4)驱动模拟世界
// 世界类用于驱动模拟 void HelloWorld::update(float a) { float32 timeStep = 1.0f / 60.f; // Step()函数的第一个参数是时间步数,后面两个参数分别是速度阶段和位置阶段 world->Step(timeStep, 8, 3); CCSprite *s; for (b2Body *b = world->GetBodyList(); b; b = b->GetNext()) { if (b->GetUserData() != NULL) { s = (CCSprite*)b->GetUserData(); // 漂浮刚体的运动模拟 s->setPosition(ccp(b->GetPosition().x * RATIO, b->GetPosition().y * RATIO)); if (s->getPosition().x < -10 || s->getPosition().x > 490 || s->getPosition().y < -10) { this->removeChild(s); world->DestroyBody(b); } } } }
(5)设置屏幕可以触摸,实现触摸屏幕添加刚体的效果
void HelloWorld::registerWithTouchDispatcher(void) { CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 1, false); }
bool HelloWorld::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) { CCPoint p = pTouch->getLocation(); addBird(p.x / RATIO, p.y / RATIO, b2_dynamicBody); return true; }
(6)让HelloWorld类继承b2ContactListener,重写BeginContact函数,实现刚体的检测碰撞
// 碰撞检测 void HelloWorld::BeginContact(b2Contact* contact) { if ((contact->GetFixtureA()->GetBody() == grassBody || contact->GetFixtureB()->GetBody() == grassBody)) { CCLog("有鸟落在草地上!"); } else { CCLog("鸟与鸟之间发生碰撞"); } }
效果如图所示: