如何用cocos2dx box2dD Library制作2D物理游戏

& &最近打算做一个2D投篮游戏,由于对于BOX2D等物理引擎并不熟悉,加之一开始低估了游戏所需要的碰撞检测复杂度,认为仅仅涉及4面墙,篮球,篮板,篮筐,篮网的碰撞检测并不复杂。因此决定自己实现所需要的碰撞检测。结果实际开始做时磕磕碰碰遇到了许多问题。
& & 1、如何实现像素级碰撞检测。
& &as3原生的hitTestObject只能检测矩形,对于圆形等其他形状就不适用了;打算用hitTestPoint来检测篮球与篮板四个边角,篮筐前后框点,在实践尝试中发现是有问题的,在涉及物体旋转的情况下检测就不精确了。因此只能自己实现像素级碰撞检测。在网上查找了许多资料,终于找到比较好的解决思路:利用叠加模式检测两物体不透明像素部分。
 2、计算碰撞物体的反弹速度及方向。
& &我们可以常见的使用BOX2D等物理引擎实现的游戏,如桌球游戏,球与球相撞、球与桌的相撞,根据碰撞角度不同,反弹速度和方向也是不同的。把事情交给物理引擎解决,当然是高枕无忧,如果想自己计算出反弹速度及方向。那就不仅是写写代码的问题,而是要通过数学、物理来解决。基本该问题分为两部分解决:
  a)恢复现场:由于物体的移动是按帧改变的,所以物体位置变化不是连续的,而是离散的。当碰撞发生时,两物体已经相交重叠了,而实际计算反弹速度和方向,需要在物体刚刚相碰撞一刻的情况下进行。因此需要将两物体合理地恢复到刚刚碰撞的位置。实际物理引擎的处理非常复杂,而本游戏则是采用简单方法处理。将球按反速度一点点回退,直到球与碰撞点的距离刚好为球的半径。
  b)利用向量原理计算反弹速度及方向。实话说,自己凭空想的话怎么也想不到是通过向量的思路得出答案。刚开始做时琢磨了很久,查了一些资料,终于在一个帖子找到答案,这不就是高中学的空间向量嘛?赤果果的数学题啊~~
我们现在的任务是:已知物体的速度向量S和边界向量b,求它的反射向量F。我们先来看一下在碰撞过程中都有哪些向量关系:
设b是障碍向量,S是入射速度向量,F是反射速度向量,也就是我们要计算的向量。A是入射角度,A'是反射角度,A=A'。N是b的法向量,即N垂直于b。n是与N共线的向量,n'是N方向的单位向量。T是垂直于N的向量。根据向量加法,现在有关系:
(1) S + n = T(2) n + T = F
合并,得F = 2*T - S&
又已知T = S + n,
最终得 F = 2(S + n) - S = S - 2n
实际写代码是,Sx,Sy,nx,ny都是可以计算出来的,通过公式最终求出Fx,Fy。
3、画出瞄准线。
  许多主流投篮类游戏都能在篮球投出前画出瞄准线。说下实现思路。根据鼠标按下时的位置和当前位置可计算出初始速度和方向。既然知道了速度,就可根据时间计算距离。因此可计算投出后每隔一段时间后(如每隔0.1s),小球的舞台坐标,并依次draw出一个个点,看起来就是一条瞄准线。有个必须要注意的细节。画点的时候,要考虑重力对于垂直速度的改变。并且改变值要与实际小球渲染时的速度改变相同。如渲染时每隔0.1s计算一次重力因素对垂直速度的改变。在瞄准画线时也需要每隔0.1s改变一次垂直速度。只有这样,瞄准线才是正确的。
实现过程折腾了很久,解决了以上问题后终于使投篮过程显得比较真实。尽管如此,这样一个自己现实的简单碰撞检测,使用很局限,游戏玩法稍微深挖,像要在场景加入新的物体,就无法胜任了。最终学习了下BOX2D发现也不复杂,还是改成用Box2d物理引擎来做,通过笔者一番蛋疼的折腾后也证实,如果想要认认真真做一个物理游戏,用物理引擎才是正确的做法,把专业任务讲给专业的系统去处理。
这里分享自己做这个小游戏的经验,存当抛砖引玉,希望有人碰到某些特定需求,可以用上这些技巧。
游戏源码下载地址:
阅读(...) 评论()《Box2D物理游戏编程基础》试读.pdf
扫描二维码,下载文件到手机
当前文件信息
浏览:4226次
保存:56次
下载:760次
您的VIP会员已过期,是否续费?
用户应遵守著作权法,尊重著作权人合法权益,不违法上传、存储并分享他人作品。举报邮箱:
京网文[0号 京ICP证100780号2207人阅读
游戏开发(1)
unity的博客好久都没有更新了,今天带了的绝对是劲爆的干货。。。
大家使用unity开发游戏估计有一大部分是开发2D的游戏,可能会用到物理系统。但是unity的物理系统计算了z轴有时候就会感觉不是那么顺手….
———————–这里隔开(上面是废话)————
Box2D是一款免费的开源二维物理引擎,由Erin Catto使用C++编写,在zlib授权下发布。它已被用于蜡笔物理学、愤怒的小鸟、地狱边境、Rolando、Fantastic Contraption、Incredibots、Tiny Wings、Transformice、Happy Wheels等游戏的开发,对于iPhone、iPad和Android游戏,可以使用Cocos2d游戏引擎和Corona Framework。
box2d用来做2d物理游戏很给力,把它用到unity上开发2D游戏并且跨平台不是更给力么
下面介绍一款插件Farseer Physics,几乎是c#版的box2d,作者移植了box2d的核心并且写了一些基础组件
先下载这个Unity package:
导入包,新建一个场景,设置相机为正交相机orthographic,视口大小size为27(当然可大可小的)
新建一个空物体,附加脚本FSWorldComponent,在这个脚本上设置重力的加速度,一般不用改的
新建四个方块把屏幕内包围起来,确保屏幕内的物体不会弹出屏幕外,然后附加FSBodyComponent组件到每个方块上,设置body type为static.
添加FSShapeComponent组件,use Unity colliders = true.
新建一些方块来测试,附加FSBodyComponent和FSShapeComponent组件,选择FSShapeComponent组件的Use Unity Collider. SType为Polygon.
到这里点击播放按钮测试可以看到基础的物理碰撞效果,在相机上附加FSDebugDrawComponent组件,可以看到物理的调试信息.
下面我们来试试另外一种生成物体形状的方法,复制出刚才的小方块,删除物体上FSShapeComponent组件和碰撞体组件
创建一个空物体,作为当前物体的子物体,重置子物体的Transform组件的所有属性,附加FSShapeComponent组件,去掉Use Unity collider,然后我们使用点连线来绘制物体碰撞体的形状,命名为(P00, P01, P02, P03)
点击播放测试看看
用这种方法定义多边形更灵活,能定义你想要的任何形状,性能上也肯定比MeshCollider好.
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1095044次
积分:10191
积分:10191
排名:第1277名
原创:113篇
转载:238篇
评论:146条
(2)(1)(7)(24)(9)(8)(20)(6)(17)(59)(52)(51)(58)(4)(1)(1)(2)(9)(1)(3)(2)(4)(8)(2)[转]怎样在cocos2d里面如何使用物理引擎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 {
同时修改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];
- (id)init {
if ((self=[super init])) {
最后一步—-验证一下,你的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在处理大小在0.1到10个单元的对象的时候做了一些优化。这里的0.1米大概就是一个杯子那么大,10的话,大概就是一个箱子的大小。
  因此,我们并不直接传递像素,因为一个很小的对象很有60&60个像素,那已经大大超过了box2d优化时所限定的大小。因此,如果我们有一个64像素的对象,我们可以把它除以PTM_RATIO,得到2米—这个长度,box2d刚好可以很好地用来做物理仿真。
  好了,现在来点有意思的东西。在HelloWorldScene.h文件顶部添加下列代码:
#import "Box2D.h"
同时在HelloWorld类中添加以下成员变量:
b2World *_
CCSprite *_
然后,在HelloWorldScene.mm的init方法中加入下面的代码
CGSize winSize = [CCDirector sharedDirector].winS
// 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 groundBodyD
groundBodyDef.position.Set(0,0);
b2Body *groundBody = _world-&CreateBody(&groundBodyDef);
b2EdgeShape groundE
b2FixtureDef boxShapeD
boxShapeDef.shape = &groundE
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 ballBodyD
ballBodyDef.type = b2_dynamicB
ballBodyDef.position.Set(100/PTM_RATIO, 100/PTM_RATIO);
ballBodyDef.userData = _
_body = _world-&CreateBody(&ballBodyDef);
circle.m_radius = 26.0/PTM_RATIO;
b2FixtureDef ballShapeD
ballShapeDef.shape = &
ballShapeDef.density = 1.0f;
ballShapeDef.friction = 0.2f;
ballShapeDef.restitution = 0.8f;
_body-&CreateFixture(&ballShapeDef);
[self schedule:@selector(tick:)];
呃,这里有很多陌生的代码。我们一点点来解释一下。下面,我会一段段地重复上面的代码,那样可以解释地更加清楚一些。
CGSize winSize = [CCDirector sharedDirector].winS
// 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 groundBodyD
groundBodyDef.position.Set(0,0);
b2Body *groundBody = _world-&CreateBody(&groundBodyDef);
b2EdgeShape groundE
b2FixtureDef boxShapeD
boxShapeDef.shape = &groundE
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
再使用body对象来为每一个shape创建一个fixture对象。
注意:一个body对象可以包含许许多多的fixture对象。
// Create ball body and shape
b2BodyDef ballBodyD
ballBodyDef.type = b2_dynamicB
ballBodyDef.position.Set(100/PTM_RATIO, 100/PTM_RATIO);
ballBodyDef.userData = _
_body = _world-&CreateBody(&ballBodyDef);
circle.m_radius = 26.0/PTM_RATIO;
b2FixtureDef ballShapeD
ballShapeDef.shape = &
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-&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 {
_body = NULL;
_world = NULL;
[super dealloc];
编译并运行,你应该可以看到球会往下掉,并且会从屏幕底部往上面弹起来.
关于仿真的一些注意事项
前面我们说后面会讨论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对象的重力向量。编译并运行(最好编译到设备上面,只有设备上面才有加速计),看看效果吧!
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 egret box2d物理引擎 的文章

 

随机推荐