Cocos2D 开发之 学习Box2d物理引擎(1)

时间:2022-06-06 04:57:06

刚刚接触Box2d,下面通过解读一下默认项目代码了解一下神奇的Box2d物理引擎。

新建一个Box2d的项目。运行这个项目我们单击屏幕,就会出现很多带有字母的小盒子,每个小盒子受重力影响,而且盒子之间会发生碰撞。

下面我注意解说一下其中最主要的HelloWorldLayer类。

下面引用http://www.raywenderlich.com 中的一段解释(这是一个非常不错的博客)


Box2D世界相关理论

在我们开始之前,让我们先交待一下Box2D具体是如何运作的。
  你需要做的第一件事情就是,当使用cocos2d来为box2d创建一个world对象的时候。这个world对象管理物理仿真中的所有对象。
  一旦我们已经创建了这个world对象,接下来需要往里面加入一些body对象。body对象可以随意移动,可以是怪物或者飞镖什么的,只要是参与碰撞的游戏对象都要为之创建一个相应的body对象。当然,也可以创建一些静态的body对象,用来表示游戏中的台阶或者墙壁等不可以移动的物体。
  为了创建一个body对象,你需要做很多事情–首先,创建一个body定义结构,然后是body对象,再指定一个shap,再是fixture定义,然后再创建一个fixture对象。下面会一个一个解释刚刚这些东西。

  • 你首先创建一个body定义结构体,用以指定body的初始属性,比如位置或者速度。
  • 一旦创建好body结构体后,你就可以调用world对象来创建一个body对象了。
  • 然后,你为body对象定义一个shape,用以指定你想要仿真的物体的几何形状。
  • 接着创建一个fixture定义,同时设置之前创建好的shape为fixture的一个属性,并且设置其它的属性,比如质量或者摩擦力。
  • 最后,你可以使用body对象来创建fixture对象,通过传入一个fixture的定义结构就可以了。
  • 请注意,你可以往单个body对象里面添加很多个fixture对象。这个功能在你创建特别复杂的对象的时候非常有用。比如自行车,你可能要创建2个*,车身等等,这些fixture可以用关节连接起来。

只要你把所有需要创建的body对象都创建好之后,box2d接下来就会接管工作,并且高效地进行物理仿真—只要你周期性地调用world对象的step函数就可以了。
  但是,请注意,box2d仅仅是更新它内部模型对象的位置–如果你想让cocos2d里面的sprite的位置也更新,并且和物理仿真中的位置相同的话,那么你也需要周期性地更新精灵的位置。


首先看一下文件目录吧!

Cocos2D 开发之  学习Box2d物理引擎(1)

其中最后的两个文件GLES-Render用于实现OpenGL ES命令的调用,以绘制各种图形,通常在开发中不需要修改。主要实现的内容在HelloWorldLayer文件中。

1、先看一下init(注:这里把有关Game Center的label的有关代码删掉了,这部分大家都懂的)

-(id) init
{
if( (self=[super init])) {

// enable events

self.touchEnabled = YES;
self.accelerometerEnabled = YES;
CGSize s = [CCDirector sharedDirector].winSize;

// init physics
[self initPhysics];

// create reset button
[self createMenu];

//Set up sprite
#if 1
// Use batch node. Faster
CCSpriteBatchNode *parent = [CCSpriteBatchNode batchNodeWithFile:@"blocks.png" capacity:100];
spriteTexture_ = [parent texture];
#else
// doesn't use batch node. Slower
spriteTexture_ = [[CCTextureCache sharedTextureCache] addImage:@"blocks.png"];
CCNode *parent = [CCNode node];
#endif
[self addChild:parent z:0 tag:kTagParentNode];


[self addNewSpriteAtPosition:ccp(s.width/2, s.height/2)];

CCLabelTTF *label = [CCLabelTTF labelWithString:@"Tap screen" fontName:@"Marker Felt" fontSize:32];
[self addChild:label z:0];
[label setColor:ccc3(0,0,255)];
label.position = ccp( s.width/2, s.height-50);
[self scheduleUpdate];//调用该方法,可以让Box2D接管世界
}
return self;
}

这里的处理提供了两种创建小盒子精灵的方法:第一种是使用 batch node,速度快;第二种不使用batch node ,而是使用纹理缓存 texture cache,速度慢。

注意到其中的 [selfscheduleUpdate]; 调用的是

-(void) update: (ccTime) dt
{
//It is recommended that a fixed time step is used with Box2D for stability
//of the simulation, however, we are using a variable time step here.
//You need to make an informed choice, the following URL is useful
//http://gafferongames.com/game-physics/fix-your-timestep/

int32 velocityIterations = 8; //速度迭代次数
int32 positionIterations = 1; //位置迭代次数
//迭代次数越多,box2d物理仿真的效果越好,但是耗费的资源也就越多,机器也会卡

// Instruct the world to perform a single step of simulation. It is
// generally best to keep the time step and iterations fixed.
world->Step(dt, velocityIterations, positionIterations);
//周期性的调用step方法实现高效的物理仿真
}

这里的两个参数分别是“速度迭代次数”和“位置迭代次数”--你应该设置他们的范围在8-10之间。(译者:这里的数字越小,精度越小,但是效率更高。数字越大,仿真越精确,但同时耗时更多。8一般是个折中,如果学过数值分析,应该知道迭代步数的具体作用)

在这个方法中,世界对象调用step方法,开始执行物理世界的模拟。


2、添加触摸事件处理

//处理触摸事件
- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//Add a new body/atlas sprite at the touched location
for( UITouch *touch in touches ) {
CGPoint location = [touch locationInView: [touch view]];

location = [[CCDirector sharedDirector] convertToGL: location];

[self addNewSpriteAtPosition: location];
}
}

将触摸点位置转换成Cocos2d中的位置,然后调用 addNewSpriteAtPosition: 方法,添加小盒子。


3、

//在特定的位置添加新的精灵对象
-(void) addNewSpriteAtPosition:(CGPoint)p
{
CCLOG(@"Add sprite %0.2f x %02.f",p.x,p.y);
// Define the dynamic body.
//Set up a 1m squared box in the physics world
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody; //创建一个动态物体
bodyDef.position.Set(p.x/PTM_RATIO, p.y/PTM_RATIO);
b2Body *body = world->CreateBody(&bodyDef);

// Define another box shape for our dynamic body.
b2PolygonShape dynamicBox;
dynamicBox.SetAsBox(.5f, .5f);//These are mid points for our 1m box

// Define the dynamic body fixture. 创建一个夹具定义
b2FixtureDef fixtureDef;
//将夹具定义的形状设置为刚刚所定义的形状
fixtureDef.shape = &dynamicBox;
//设置夹具定义的 密度 摩擦力 弹力
fixtureDef.density = 1.0f;
fixtureDef.friction = 0.3f;
fixtureDef.restitution = 0.8f;
//使用物体的夹具工厂方法来创建夹具
body->CreateFixture(&fixtureDef);


CCNode *parent = [self getChildByTag:kTagParentNode];

//We have a 64x64 sprite sheet(精灵表单) with 4 different 32x32 images. The following code is
//just randomly picking one of the images 随机获取方块
int idx = (CCRANDOM_0_1() > .5 ? 0:1);
int idy = (CCRANDOM_0_1() > .5 ? 0:1);

CCPhysicsSprite *sprite = [CCPhysicsSprite spriteWithTexture:spriteTexture_ rect:CGRectMake(32 * idx,32 * idy,32,32)];
//添加到精灵表单中
[parent addChild:sprite];

[sprite setPTMRatio:PTM_RATIO];
[sprite setB2Body:body];//设置精灵对象对应的物体
[sprite setPosition: ccp( p.x, p.y)];

}

  • Density 就是单位体积的质量(密度)。因此,一个对象的密度越大,那么它就有更多的质量,当然就会越难以移动.
  • Friction 就是摩擦力。它的范围是0-1.0, 0意味着没有摩擦,1代表最大摩擦,几乎移不动的摩擦。
  • Restitution 回复力。它的范围也是0到1.0. 0意味着对象碰撞之后不会反弹,1意味着是完全弹性碰撞,会以同样的速度反弹。
4、
//初始化物理世界
-(void) initPhysics
{

CGSize s = [[CCDirector sharedDirector] winSize];

b2Vec2 gravity;
gravity.Set(0.0f, -10.0f);
//设置重力加速度
world = new b2World(gravity);


// Do we want to let bodies sleep?
//SetAllowSleeping()设置该属性以说明物体处于静止状态时是否会进入休眠状态。
//处于休眠状态的物体不会耗费系统处理时间,直到其他物体撞上它,该物体才会苏醒过来。
world->SetAllowSleeping(true);

//设置世界是否支持连续碰撞机制
world->SetContinuousPhysics(true);

//DebugDraw通常是在测试阶段使用的,注释掉相关代码语句(连同下面的draw方法)也可以正常运行。
m_debugDraw = new GLESDebugDraw( PTM_RATIO );
world->SetDebugDraw(m_debugDraw);

//绘制模式
/*
enum
{
e_shapeBit = 0x0001, ///< draw shapes
e_jointBit = 0x0002, ///< draw joint connections
e_aabbBit = 0x0004, ///< draw axis aligned bounding boxes
e_pairBit = 0x0008, ///< draw broad-phase pairs
e_centerOfMassBit = 0x0010 ///< draw center of mass frame
};
*/
uint32 flags = 0;
flags += b2Draw::e_shapeBit;
//flags += b2Draw::e_jointBit;
//flags += b2Draw::e_aabbBit;
//flags += b2Draw::e_pairBit;
//flags += b2Draw::e_centerOfMassBit;
m_debugDraw->SetFlags(flags);


// Define the ground body. 创建一个物体定义,并初始化相关的属性
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0, 0); // bottom-left corner 左下角

// Call the body factory which allocates memory for the ground body
// from a pool and creates the ground box shape (also from a pool).
// The body is also added to the world.

//根据物体定义使用世界的工厂方法创建物体
b2Body* groundBody = world->CreateBody(&groundBodyDef);

// Define the ground box shape. 为物体定义一个形状
b2EdgeShape groundBox;//定义了一个简单的四边形 即程序运行中的边框

//void Set(const b2Vec2& v1, const b2Vec2& v2);
/**************************************************************************
* 功能描述: 设置独立的边缘
* 参数说明: v1 : 第一个顶点
v2 : 第二个顶点
* 返 回 值: (void)
***************************************************************************/

// bottom
groundBox.Set(b2Vec2(0,0), b2Vec2(s.width/PTM_RATIO,0));

//使用物体的工程方法创建夹具
groundBody->CreateFixture(&groundBox,0);

// top
groundBox.Set(b2Vec2(0,s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO));
groundBody->CreateFixture(&groundBox,0);

// left
groundBox.Set(b2Vec2(0,s.height/PTM_RATIO), b2Vec2(0,0));
groundBody->CreateFixture(&groundBox,0);

// right
groundBox.Set(b2Vec2(s.width/PTM_RATIO,s.height/PTM_RATIO), b2Vec2(s.width/PTM_RATIO,0));
groundBody->CreateFixture(&groundBox,0);
}

这个默认的项目大致就是这样子了,开始接触Box2d......