热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

box2d初级教程之一cocos2dbox2d制作弹球游戏

更新5182012:Cocos2d版本Cocos2D2.0-rc0a.这个教程的目的就是让你们熟悉在cocos2d里面如何使用box2d,所采用的例子就是制作一个简单的应用,里面有一个篮球,

更新 5/18/2012:Cocos2d版本 Cocos2D 2.0-rc0a.

这个教程的目的就是让你们熟悉在cocos2d里面如何使用box2d,所采用的例子就是制作一个简单的应用,里面有一个篮球,你可以通过旋转你的iPhone来改变重力的方向,同时篮球碰到屏幕边界可以反弹。

这个教程是基于
iphoneDev.net上的由Kyle写的经典例子,
但教程更新到Cocos2d的最新版本,并加了很多细节的解释。教程里也有一些
在Cocos2d和Box2d应用模板中包含的部分,但我们一步一步解释了他们的工作流程。

这个教程假设你已经学过前面的教程

《如何使用cocos2d来制作一个简单的iphone游戏》, 或者有同等相关经验也可以。

好了,让我们开始学习Box2d物理引擎吧!

创建一个空的工程

打开Xcode,选择 cocos2d-0.99.1 Box2d Application template来创建一个新的工程,并且命名为Box2D.如果你直接编译并且运行的话,你将会看到一个很酷的例子,里面展示了Box2d的许多内容。然后,这个教程的目的,我们将从0开始,创建一个篮球反弹的应用,这样我们就可以更好地理解那个范例的具体原理。
  因此,让我们把HelloWorld模板里面的内容都删除掉,因为我们要从0开始。把HelloWorldScene.h里面的内容替换成下面的代码:

#import "cocos2d.h"
 
#define PTM_RATIO 32.0
 
@interface HelloWorldLayer : CCLayer {
}
 
+ (id) scene;
 
@end

同时修改HelloWorldScene.mm文件:(为什么后缀是.mm,因为box2d是c++写的,而objective-c++的实现文件必须是.mm后缀,否则你编译会出n个错误!)

#import "HelloWorldLayer.h"
 
@implementation HelloWorldLayer
 
+ (id)scene {
 
CCScene *scene = [CCScene node];
HelloWorldLayer *layer = [HelloWorldLayer node];
[scene addChild:layer];
return scene;
 
}
 
- (id)init {
 
if ((self=[super init])) {
}
return self;
}
 
@end

最后一步—-验证一下,你的Classes分组下面的所有文件(比如HelloWorldScene)是以.mm文件结尾的,如果是.m,那么请改成.mm,否则等下使用Box2d的时候,编译器会报出一大堆莫名其妙的错误!
  如果你编译并运行,你应该看到一个黑色的屏幕。好了,现在让我们开始创建Box2d场景吧。

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的位置也更新,并且和物理仿真中的位置相同的话,那么你也需要周期性地更新精灵的位置。
  好了,现在有一些基本的了解了,还是先看看代码吧!

Box2d World 实战

好了,下载我制作的 篮球图片 并且把它添加到工程里去吧。下载完后,直接拖到Resources文件夹下,同时确保 “Copy items into destination group’s folder (if needed)” 被复选中。

接下来,在HelloWorldScene.mm文件顶部添加下面的代码:

#define PTM_RATIO 32.0

这里定义了一个“像素/米”的比率。当你在cocos2d里面指定一个body在哪个位置时,你使用的单位要是米。
但是,我们之前使用的都是像素作为单位,那样的话,位置就会不正确。根据Box2D参考手册,Box2d在处理大小在0.1到10个单元的对象的时候做了一些优化。这里的0.1米大概就是一个杯子那么大,10的话,大概就是一个箱子的大小。
  因此,我们并不直接传递像素,因为一个很小的对象很有60×60个像素,那已经大大超过了box2d优化时所限定的大小。因此,如果我们有一个64像素的对象,我们可以把它除以PTM_RATIO,得到2米—这个长度,box2d刚好可以很好地用来做物理仿真。
  好了,现在来点有意思的东西。在HelloWorldScene.h文件顶部添加下列代码:

#import "Box2D.h"

同时在HelloWorld类中添加以下成员变量:

b2World *_world;
b2Body *_body;
CCSprite *_ball;

然后,在HelloWorldScene.mm的init方法中加入下面的代码

CGSize winSize = [CCDirector sharedDirector].winSize;
 
// Create sprite and add it to the layer
_ball = [CCSprite spriteWithFile:@"Ball.jpg" rect:CGRectMake(0, 0, 52, 52)];
_ball.position = ccp(100, 100);
[self addChild:_ball];
 
// Create a world
b2Vec2 gravity = b2Vec2(0.0f, -30.0f);
_world = new b2World(gravity);
 
// Create edges around the entire screen
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0,0);
b2Body *groundBody = _world->CreateBody(&groundBodyDef);
b2EdgeShape groundEdge;
b2FixtureDef boxShapeDef;
boxShapeDef.shape = &groundEdge;
groundEdge.Set(b2Vec2(0,0), b2Vec2(winSize.width/PTM_RATIO, 0));
groundBody->CreateFixture(&boxShapeDef);
groundEdge.Set(b2Vec2(0,0), b2Vec2(0, winSize.height/PTM_RATIO));
groundBody->CreateFixture(&boxShapeDef);
groundEdge.Set(b2Vec2(0, winSize.height/PTM_RATIO),
b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO));
groundBody->CreateFixture(&boxShapeDef);
groundEdge.Set(b2Vec2(winSize.width/PTM_RATIO,
winSize.height/PTM_RATIO), b2Vec2(winSize.width/PTM_RATIO, 0));
groundBody->CreateFixture(&boxShapeDef);
 
// Create ball body and shape
b2BodyDef ballBodyDef;
ballBodyDef.type = b2_dynamicBody;
ballBodyDef.position.Set(100/PTM_RATIO, 100/PTM_RATIO);
ballBodyDef.userData = _ball;
_body = _world->CreateBody(&ballBodyDef);
 
b2CircleShape circle;
circle.m_radius = 26.0/PTM_RATIO;
 
b2FixtureDef ballShapeDef;
ballShapeDef.shape = &circle;
ballShapeDef.density = 1.0f;
ballShapeDef.friction = 0.2f;
ballShapeDef.restitution = 0.8f;
_body->CreateFixture(&ballShapeDef);
 
[self schedule:@selector(tick:)];

呃,这里有很多陌生的代码。我们一点点来解释一下。下面,我会一段段地重复上面的代码,那样可以解释地更加清楚一些。

CGSize winSize = [CCDirector sharedDirector].winSize;
 
// Create sprite and add it to the layer
_ball = [CCSprite spriteWithFile:@"Ball.jpg" rect:CGRectMake(0, 0, 52, 52)];
_ball.position = ccp(100, 100);
[self addChild:_ball];

首先,我们往屏幕中间加入一个精灵。如果你看了前面的教程的话,这里应该没有什么问题。

// Create a world
b2Vec2 gravity = b2Vec2(0.0f, -30.0f);
_world = new b2World(gravity);

接下来,我们创建了world对象。当我们创建这个对象的时候,需要指定一个初始的重力向量。这里,我们设置y轴方向为-30,因此,所有的body都会往屏幕下面下落。同时,我们还指定了一个值,用以指明对象不参与碰撞时,是否可以“休眠”。一个休眠的对象将不会花费处理时间,直到它与其实对象发生碰撞的时候才会“醒”过来。

// Create edges around the entire screen
b2BodyDef groundBodyDef;
groundBodyDef.position.Set(0,0);
b2Body *groundBody = _world->CreateBody(&groundBodyDef);
b2EdgeShape groundEdge;
b2FixtureDef boxShapeDef;
boxShapeDef.shape = &groundEdge;
groundEdge.Set(b2Vec2(0,0), b2Vec2(winSize.width/PTM_RATIO, 0));
groundBody->CreateFixture(&boxShapeDef);
groundEdge.Set(b2Vec2(0,0), b2Vec2(0, winSize.height/PTM_RATIO));
groundBody->CreateFixture(&boxShapeDef);
groundEdge.Set(b2Vec2(0, winSize.height/PTM_RATIO),
b2Vec2(winSize.width/PTM_RATIO, winSize.height/PTM_RATIO));
groundBody->CreateFixture(&boxShapeDef);
groundEdge.Set(b2Vec2(winSize.width/PTM_RATIO,
winSize.height/PTM_RATIO), b2Vec2(winSize.width/PTM_RATIO, 0));
groundBody->CreateFixture(&boxShapeDef);

接下来,我们为整个屏幕创建了一圈不可见的边。具体的步骤如下:

  • 首先创建一个body定义结构体,并且指定它应该放在左下角。
  • 然后,使用world对象来创建body对象。(注意,这里一定要使用world对象来创建,不能直接new,因为world对象会做一些内存管理操作。)
  • 接着,为屏幕的每一个边界创建一个多边形shape。这些“shape”仅仅是一些线段。注意,我们把像素转换成了“meter”。通过除以之前定义的比率来实现的。
  • 再创建一个fixture定义,指定shape为polygon shape。
  • 再使用body对象来为每一个shape创建一个fixture对象
  • 注意:一个body对象可以包含许许多多的fixture对象
// Create ball body and shape
b2BodyDef ballBodyDef;
ballBodyDef.type = b2_dynamicBody;
ballBodyDef.position.Set(100/PTM_RATIO, 100/PTM_RATIO);
ballBodyDef.userData = _ball;
_body = _world->CreateBody(&ballBodyDef);
 
b2CircleShape circle;
circle.m_radius = 26.0/PTM_RATIO;
 
b2FixtureDef ballShapeDef;
ballShapeDef.shape = &circle;
ballShapeDef.density = 1.0f;
ballShapeDef.friction = 0.2f;
ballShapeDef.restitution = 0.8f;
_body->CreateFixture(&ballShapeDef);

接下来,我们创建篮球的body。这个步骤和之前创建地面的body差不多,但是有下面一些差别需要注意一下:

  • 我们指定body的类型为dynamic body。默认值是static body,那意味着那个body不能被移动也不会参与仿真。很明显,我们想让篮球参与仿真。
  • 设置body的user data属性为篮球精灵。你可以设置任何东西,但是,你设置成精灵会很方便,特别是当两个body碰撞的时候,你可以通过这个参数把精灵对象取出来,然后做一些逻辑处理。
  • 这里使用了一个不同的shape类型–circle shape。
  • 在这里,我们需要为这个fixture指定一些参数,因此,我们没有使用便捷方法来创建fixture。后面我们会讲到这些参数的具体意义。
[self schedule:@selector(tick:)];

最后一件事情就是调度一个tikck方法,这个方法默认是0.1秒回调一次。注意,这并不是最好的处理方式—最好的方式应该是让tick方法有固定的频率(比如每秒60次)。然后,这个教程我们就先这样了。
  因此,让我们来实现tick方法。在init方法之后加入下面的代码:

- (void)tick:(ccTime) dt {
 
_world->Step(dt, 10, 10);
for(b2Body *b = _world->GetBodyList(); b; b=b->GetNext()) {
if (b->GetUserData() != NULL) {
CCSprite *ballData = (CCSprite *)b->GetUserData();
ballData.position = ccp(b->GetPosition().x * PTM_RATIO,
b->GetPosition().y * PTM_RATIO);
ballData.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
}
}
 
}

第一件事情就是调用world对象的step方法,这样它就可以进行物理仿真了。这里的两个参数分别是“速度迭代次数”和“位置迭代次数”–你应该设置他们的范围在8-10之间。(译者:这里的数字越小,精度越小,但是效率更高。数字越大,仿真越精确,但同时耗时更多。8一般是个折中,如果学过数值分析,应该知道迭代步数的具体作用)。
  接下来,我们要使我们的精灵匹配物理仿真。因此,我们遍历world对象里面的所有body,然后看body的user data属性是否为空,如果不为空,就可以强制转换成精灵对象。接下来,就可以根据body的位置来更新精灵的位置了。
  最后一件事—清理内存!因此,在文件的末尾加入下面的代码:

- (void)dealloc {    
delete _world;
_body = NULL;
_world = NULL;
[super dealloc];
}

编译并运行,你应该可以看到球会往下掉,并且会从屏幕底部往上面弹起来.

Bouncing Ball Screenshot

关于仿真的一些注意事项

前面我们说后面会讨论density,friction和restitution参数的意义。

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

建议多去改一改这些参数,看看具体会给小球带来什么影响。一定要去试哦!

完成加速计控制

如果我们可以通过倾斜屏幕让球朝着屏幕的某个方向运行,那将会很棒。首先,我们需要在init方法里面加入下面的代码:

self.isAccelerometerEnabled = YES;

然后,在文件的某个位置加入下面的方法:

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
 
// Landscape left values
b2Vec2 gravity(-acceleration.y * 15, acceleration.x *15);
_world->SetGravity(gravity);
 
}

 这里就是设置加速计的向量乘以某个数,然后再设置为world对象的重力向量。编译并运行(最好编译到设备上面,只有设备上面才有加速计),看看效果吧


推荐阅读
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • 学习SLAM的女生,很酷
    本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
author-avatar
zhengping4476
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有