转载请标明出处:http://blog.youkuaiyun.com/tim_shadow/article/details/23952113
1.BOX2D简介
Box2D是一个用于模拟2D刚体物体的C++引擎。作者为Erin Catto。
2. 学习目标
使添加到场景中的小鸟能够做自由落体运动。
3.思路
Box2D 是一套基于刚体模拟的物理引擎,它的核心概念为世界、物体、形状、约束和关节,这些概念具体实现为 Box2D 的各个组件
为了实现一个物理场景,我们需要做的是创建一个世界,然后创建我们需要的物体,设置好它的形状和其他属性,将其添加到世界当中,然后周期性地更新这个世界,那么 Box2D 就会为我们高效且出色地完成模拟物理运动的任务
但是以上还只是Box2D的世界中的物体的更新,我们还需让它与Cocos2d的世界联系起来。
4实现
按照思路,我们首先需要实现一个Box2D的世界:
bool GameLayer::initBox2d() {
b2Vec2 gravity;
//设置重力 x方向种力加速度为 0 ,y方向重力加速度为 9.8 ,方向向下
gravity.Set(0.0, -9.8);
//初始化,这个方法是 Box2D中很少会用到的用 new创建的对象
__b2World = new b2World(gravity);
//设置允许睡眠,可以提高物理世界中物体的处理效率,只有在发生碰撞时才唤醒该对象
__b2World->SetAllowSleeping(true);
//设置开启连续,避免碰撞检测时的穿透现象
__b2World->SetContinuousPhysics(true);
//初始化 调试的界面
initDebugview();
return true;
}
当我们添加一个 精灵的时候我们为这个 精灵,在Box2D的世界里面生成一个 刚体,然后将他们绑定到一起
void GameLayer::setB2BodyForSprite(CCSprite* sprite) {
CCSize size = sprite->getContentSize();
CCPoint point = sprite->getPosition();
b2BodyDef bodydef;
bodydef.type = b2_dynamicBody;
bodydef.position.Set(point.x / PTM_RATIO, point.y / PTM_RATIO);
//在调用的时候已经将 body 添加到了 box2d的世界中了
b2Body* body = __b2World->CreateBody(&bodydef);
//设置阻尼 为 0.3
body->SetLinearDamping(0.2);
//将sprite绑定给 body ,将来便可以通过body 获取到 精灵
body->SetUserData(sprite);
//////////
b2PolygonShape shape;
//设置形状 注意看这个函数的注释,helf ,所以需要 额外处以 2
shape.SetAsBox(size.width / PTM_RATIO / 2, size.height / PTM_RATIO / 2);
b2FixtureDef fixturedef;
fixturedef.shape = &shape;
fixturedef.userData = sprite;
fixturedef.density = 12;
//将 body绑定到 精灵这样可以通过精灵获取到刚体了
sprite->setUserData(body);
//同样的,已经将夹具加入到了 body里面了
body->CreateFixture(&fixturedef);
}
void GameLayer::update(float dt) {
for (b2Body* b = __b2World->GetBodyList(); b; b = b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite* sprite = static_cast<CCSprite*>(b->GetUserData());
//物理引擎->精灵
if (sprite->getTag() == tag_bird) { //如果是小鸟类型,我需要将 小鸟的位置以 物理引擎的状态为准
b2Vec2 pos = b->GetPosition();
//弧度转为角度 角度 = (弧度/PI)×180;
float rotation = CC_RADIANS_TO_DEGREES(b->GetAngle());
sprite->setPosition(
ccp(pos.x * PTM_RATIO , pos.y * PTM_RATIO ));
sprite->setRotation(rotation);
if (pos.y * PTM_RATIO <= 10) {
CCDirector::sharedDirector()->pause();
GameScene *scene = static_cast<GameScene*>(getParent());
scene->getGameMenuLayer()->setVisible(true);
}
//精灵->物理引擎
} else { //如果不是小鸟,那么不受物理引擎的影响,我们吧经理的位置和角度传个 物理引擎,以同步
b2Vec2 b2Position = b2Vec2(sprite->getPosition().x / PTM_RATIO,
sprite->getPosition().y / PTM_RATIO);
//弧度转为角度 弧度 = (角度/180)*PI;
float32 b2Angle = -CC_DEGREES_TO_RADIANS(sprite->getRotation());
b->SetTransform(b2Position, b2Angle);
}
}
}
//××××不要忘记了Box2D世界的更新××××,后面的参数为迭代次数控制,这两个迭代次数控制的就是微分的粒度,迭代次数越多,则模拟越精细,效果越细腻,
// 但相应地,耗费的计算量也越大。一般情况下,我们将这两个参数设置为 8~10
__b2World->Step(dt, 8, 8);
}
好了,到这里可以通过每个 精灵的 tag 知道他们是不同的种类,如果是小鸟的种类,那么我需要按照 Box2D的世界的规则来运动小鸟,所以,我需要把 Box2D的世界的 刚体的属性设置给 精灵,反之其他的种类做其他的操作:
将刚体的属性 设置给 精灵:
if (sprite->getTag() == tag_bird) { //如果是小鸟类型,我需要将 小鸟的位置以 物理引擎的状态为准
b2Vec2 pos = b->GetPosition();
//弧度转为角度 角度 = (弧度/PI)×180;
float rotation = CC_RADIANS_TO_DEGREES(b->GetAngle());
sprite->setPosition(
ccp(pos.x * PTM_RATIO , pos.y * PTM_RATIO ));
sprite->setRotation(rotation);
if (pos.y * PTM_RATIO <= 10) {
CCDirector::sharedDirector()->pause();
GameScene *scene = static_cast<GameScene*>(getParent());
scene->getGameMenuLayer()->setVisible(true);
}
//精灵->物理引擎
}
将精灵的属性设置给刚体:
else { //如果不是小鸟,那么不受物理引擎的影响,我们吧精灵的位置和角度传给 物理引擎,以同步
b2Vec2 b2Position = b2Vec2(sprite->getPosition().x / PTM_RATIO,
sprite->getPosition().y / PTM_RATIO);
//弧度转为角度 弧度 = (角度/180)*PI;
float32 b2Angle = -CC_DEGREES_TO_RADIANS(sprite->getRotation());
b->SetTransform(b2Position, b2Angle);
}
在这里我是将Box2D的世界放在了一个自定义的GameLayer里面,每当GameLayer里面添加一个 精灵的时候,便调用一次 setB2BodyForSprite (CCSprite * sprite) 将精灵创建一个 对应刚体并绑定 ,所以,我这里需要 重写 GameLayer 的addChild(CCNode *node) 方法:
void GameLayer::addChild(CCSprite * child) {
CCNode::addChild(child);
setB2BodyForSprite(child);
}
void GameLayer::addChild(CCSprite * child, int zOrder) {
CCNode::addChild(child, zOrder);
setB2BodyForSprite(child);
}
void GameLayer::addChild(CCSprite* child, int zOrder, int tag) {
setB2BodyForSprite(child);
CCNode::addChild(child, zOrder, tag);
}
到此,基本功能已经完善,为了能够明确的看到 Box2D世界中的刚体的位置,我们需要启动调试
//初始化调试界面
bool GameLayer::initDebugview() {
m_debugDraw = new GLESDebugDraw(PTM_RATIO);
__b2World->SetDebugDraw(m_debugDraw);
uint32 flags = 0;
flags += b2Draw::e_shapeBit;
m_debugDraw->SetFlags(flags);
return true;
}
重写 GameLayer 里面的draw() 方法 将 Box2D的刚体画到 GameLayer 里面,如果不重写这个方法,是看不到 刚体的 ,记得调用 父类的 Draw方法,以免看不到 里面的精灵,只看到 刚体了
void GameLayer::draw() {
CCLayerColor::draw();
ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position);
// 保存状态
kmGLPushMatrix();
// 绘制调试图形
__b2World->DrawDebugData();
// 恢复刚才保存的状态
kmGLPopMatrix();
}
我们在GameLayer里面创建一个 bird 然后 添加到 Gamelayer :
bool GameLayer::init() {
if (!CCLayerColor::initWithColor(ccc4(255, 0, 255, 255)) || !initBox2d()) {
return false;
}
setTouchEnabled(true);
scheduleUpdate();
CCSize visiblesize = CCDirector::sharedDirector()->getVisibleSize();
//载入纹理
CCTexture2D* texture = CCTextureCache::sharedTextureCache()->addImage(
"bird_fly.png");
// 设定小鸟纹理的宽度和高度
float height = texture->getContentSize().height / 3;
float width = texture->getContentSize().width;
CCAnimation* animation = CCAnimation::create();
//设置间隔时间
animation->setDelayPerUnit(0.15f);
//添加精灵帧
for (int i = 0; i < 3; i++) {
animation->addSpriteFrameWithTexture(texture,
// x0,y0 , width, height
CCRectMake(0, height*i, width, height));
}
CCRepeatForever* repeatForever = CCRepeatForever::create(
CCAnimate::create(animation));
bird = CCSprite::create("bird_fly.png", CCRectMake(0,0,width,height));
bird->runAction(repeatForever);
bird->setPosition(ccp(visiblesize.width/5,visiblesize.height*4.0/5.0));
addChild(bird, 1, tag_bird);
return true;
}
然后运行调试,就可以看到 一个鸟儿 从空中掉下来了。
也可以为 GameLayer添加些特殊的处理,比如点击事件:
void GameLayer::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent) {
b2Body * body = static_cast<b2Body*>(bird->getUserData());
b2Vec2 v = body->GetLinearVelocity();
v.y = 10;
body->SetLinearVelocity(v);
}
别忘记了将GameLayer 设置为可以点击:
setTouchEnabled(true);
5.一些其他的问题解决:
1.编译时错,可以查看下 makefile.mk 里面是不是把所有的 .cpp 文件都加入了,并且路径没有写错
附上我的:
SOURCES = main.cpp \
../Classes/AppDelegate.cpp \
../Classes/MenuScene.cpp \
../Classes/GameScene.cpp \
../Classes/MoveTowardAction.cpp \
../Classes/GLES-Render.cpp
2.如果编译的时候说 Box2d/b2Body.h 文件找不到,可以查看下makefile.mk 文件里面 是否包含 了这个目录
附上我的:
INCLUDES = -I.. -I../Classes \
-I$(COCOS_ROOT)/external/
还有这个,如果这个不加上貌似不能找到 Box2d/b2Body.h 的实现,编译的时候也会报错
STATICLIBS += \
$(LIB_DIR)/libbox2d.a \
3.如果发现 精灵与 刚体不同步,可以检查,update 里面的方法,和参数是否正确
4.如果界面只有刚体或着只有精灵,那么检查GameLayer的draw方法,看是不是调用了父类的方法,顺序有没有错:
void GameLayer::draw() {
CCLayerColor::draw();
ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position);
// 保存状态
kmGLPushMatrix();
// 绘制调试图形
__b2World->DrawDebugData();
// 恢复刚才保存的状态
kmGLPopMatrix();
}
5.如果只有刚体运动或者 精灵运动,那么需要检查是否update 方法执行到了,update 默认是不执行的,需要调用:
scheduleUpdate();