Ubuntu Cocos2dx 学习笔记——添加Box2d物理游戏引擎

本文介绍如何将Box2D物理引擎与Cocos2d游戏框架结合使用,实现2D游戏中的物理效果,包括创建Box2D世界、绑定精灵与刚体、同步物理引擎与游戏世界及调试技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载请标明出处: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);
}

到这里,已经将 精灵在Box2d的世界里面创建了一个刚体,并将 精灵与刚体联系起来了,但是还不够,这时侯这两者之间的工作还是 独立的,比如位置,运动方向,选择角度等。所以我们需要在 画面更新的时候重新设置这些参数 

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();
}

GLESDebugDraw 这个类是在 "GLES-Render.h" 文件中定义的一个类,在 Cocos2d里面的 /opt/cocos2d/samples/Cpp/TestCpp/Classes/Box2DTestBed 里面可以找到,直接复制过来就好了。

我们在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();



   











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值