2014年11月20日 星期四

利用Cocos2dx-3.0新物理特性模拟弹珠迷宫

http://cocos2d.9tech.cn/news/2014/0219/39868.html

看到这张图,不知道你会不会想到些什么?儿时的玩物,满满的童年的味道。那时候没有太多玩具,这些小玩意足以让我兴奋很久。有那么一段时光是“弹珠迷宫”陪我度过的,课后亦或放学的时候,总是捧着这么一个巴掌大小的塑料盒在掌心摇摇晃晃,小心翼翼的掌控着弹珠的轨迹。严肃认真,又夹杂着紧张与激动的 心情,也许这就是童真吧。
好了,赶紧把思绪扯回来。偶然想起这么一个小玩具,又买不到实物,怎么办呢?果断自己写一个装到爪机里,走哪玩哪。
教程中将详细介绍如何使用cocos2d-x搭配box2d物理引擎以及重力感应来模拟弹珠迷宫的效果。
创建项目
硬件环境: MacOS X 10.9.1
开发工具:Xcode5, VetexHelper
引擎版本:Cocos2d-x-3.0beta
以上是我的开发环境,你大可根据你的开发环境做相应操作。直接将路径 /cocos2dx-master/tools/project-creator 中的 create_project.py 拖到终端,然后会跳出对话框如下(这是3.0之后才有的,使用之前版本的小伙伴请参考 cocos2d-x环境搭建(基于win7以及mac)):
createProject
填写好相应的信息,项目名称、包名、项目存放路径以及开发语言后点击下方的create按钮。创建成功之后,去你的路径打开你的项目进行接下来的操作。
创建RollingBall类
个人的怪癖,不喜欢在项目自带的HelloWorldScene文件上进行修改,所以新添文件 “RollingBall.h” 和 “RollingBall.cpp”。当然了,你大可直接在HelloWorldScene.h和HelloWorldScene.cpp做相应修改。
头文件中声明了DebugDraw的开关控制方法btnDebugDrawCallback,物理世界的创建方法setPhyWorld,物理世界的布局方法demoLayout等等。完整的源码放在GitHub仓库中供参考。
1
2
3
4
5
6
//turn on or off the DebugDraw
void btnDebugDrawCallback(Object* pSender);
//create a physics world
void setPhyWorld(PhysicsWorld* world){_world = world;}
//the layout of the physics world
void demoLayout();
在“AppDelegate.cpp”中引入头文件,并在applicationDidFinishLaunching()方法中作如下修改:
1
autoscene=HelloWorld::createScene();
改为
1
autoscene=RollingBall::createScene();
创建物理世界
cocos2d-x-3.0中对物理系统进行了封装,开发过程中可不用再纠结与box2d和chipmunk的接口。Physics integration大大方便了物理系统的使用,有兴趣的话可以去看看这篇文章.
言归正传,使用物理效果必然得有一个物理世界。在box2d中需要我们做如下操作,先设定一个重力系数,然后根据这个重力系数创建一个世界:
1
2
b2Vec2 gravity=b2Vec2(0.0f, -30.0f);
b2World* m_world=new b2World(gravity);
但现在,我们可以通过createWithPhysics()方法创建一个带有物理效果的Scene,然后将需要添加物理效果的层加入其中:
1
2
3
4
auto scene = Scene::createWithPhysics();
auto layer = RollingBall::create();
layer->setPhyWorld(scene->getPhysicsWorld());
scene->addChild(layer);
DebugDraw
开启DebugDraw
DebugDraw对需要使用物理系统的我们来说是个很有用的方法。它可将碰撞体的形状、关节等等全部绘制出来,方便我们观察物体及整个场景的可碰撞区域。
1
2
//choose whitch part need to draw, Joint, Shape, Contact, None or All
scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
你可以修改setDebugDrawMask(int mask)方法的参数mask,来开启相应的绘制,本示例中开启了绘制所有可碰撞体。
不必太惊讶,仅仅只需要这么一行代码就可以打开DebugDraw。
然而,之前在cocos2d-x-2.x版本中使用box2d的时候却不得不做大量的工作去实现它,例如导入相关源文件、初始化绘制参数、调用绘制方法等等很多很繁琐的步骤。同样有个小笔记《cocos2d-x中使用DebugDraw提高box2d开发效率》可供想要了解的朋友参考。

关闭DebugDraw

通过
1
_world->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_NONE)
即可关闭DebugDraw。
为了方便切换,我在程序中添加了一个按钮,参加代码中的toggleOfDebugDraw()方法,当按钮被触发即调用相应的回调方法btnDebugDrawCallback(),开启或关闭 DebugDraw。
1
2
3
4
5
6
7
8
void RollingBall::btnDebugDrawCallback(Object* pSender)
{
if(_world->getDebugDrawMask() != PhysicsWorld::DEBUGDRAW_NONE) {
_world->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_NONE);
} else {
_world->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
}
}
添加刚体弹珠
现在为我们的物理世界添加一个圆形刚体:
首先创建一个精灵,并添加到场景中:
1
2
3
4
auto spBall = Sprite::create("ball.png");
spBall->setTag(0);
spBall->setPosition(Point(50,visibleSize.height-50));
this->addChild(spBall);
spBall
这是简单的创建一个精力,弹珠现在并没有被赋予刚体属性。
接下来为弹珠添加刚体属性:首先定义一个刚体body,并为其创建了一个半径为弹珠宽度1/2的圆形碰撞体。
1
autobody=PhysicsBody::createCircle(spBall->getContentSize().width/2);
然后我们将此 body 加在精灵 spspBall 上:
1
spBall->setPhysicsBody(body);
spBallBody

运行项目,会发现小球在不断坠落,证明成功为其添加了刚体属性。同前一张图片进行对比,由于DebugDraw的作用,弹珠被绘制有红色边框。
创建物理边界
程序运行后,上面所创建的弹珠会受到重力的作用不断的下落,以至于会落置显示屏之外。但是这并非我们所需要的效果,大多时候我们都希望所创建的刚体能活动在屏幕的可见范围内。那么这个时候我们就需要为我们的场景添加一个EdgeBox。
1
2
3
4
5
6
Size visibleSize = Director::getInstance()->getVisibleSize();
auto body = PhysicsBody::createEdgeBox(visibleSize, PHYSICSBODY_MATERIAL_DEFAULT, 3);
auto edgeNode = Node::create();
edgeNode->setPosition(Point(visibleSize.width/2,visibleSize.height/2));
edgeNode->setPhysicsBody(body);
scene->addChild(edgeNode);
上述代码通过createEdgeBox方法创建了一个使用默认材质,大小为visibleSize,且边框宽带为3的碰撞盒子。此时当弹珠下落时就会和盒子边缘产生碰撞,而不会落处屏幕之外了。
创建迷宫地图
在上面的代码中我们创建了圆形刚体弹珠和矩形的碰撞盒子,这些都是规则的形状,但是如下图所示的迷宫地图是不规则的。那么如何创建不规则的刚体呢?
map
那肯定就是创建多边形刚体了。但是问题又来了,我们怎么才能知道多边形的顶点数据呢?接下来将解答这个问题。
这里需要借助一个外部工具VertexHelper,网上很多提供源码下载的地方,下载后用Xcode自行编译成可执行文件,打开这个工具:
VertexHelper
将事先准备好的迷宫地图(注意我的图片分辨率:480*320,图片是多大,分辨率就是多少)拖拽到深灰色区域:
VertexData
注意:右边的红色边框里将显示我们勾选的顶点的坐标数据;左边的红色框中则是一些配置信息(我的配置同图中所示),分别是选择物理系统类型,还有顶点数据的显示方式,以及顶点数组的名称。
获取顶点数据:
useVertexHelper
首先点选右上方的Edit Mode,然后给地图绘制边框(我把地图分为了两个部分,上部即绿色区域)。
绘制完之后将会在右下方的数据区域显示顶点数组,将数据复制到代码中,并将默认生成的数组的数据类型CGPoint和顶点的数据类型cpv均改为2dx支持的Point类型。顶点数据少的话倒是修改方便,如果数据很多这样去修改确实就太麻烦了点,下面提供一个一劳永逸的方法—修改VertexHelper源码:
打开VertexDocument.m文件,将updateResultTextField中我注释部分改为下方的代码:
1
2
3
case VHTYPE_CHIPMUNK:
//itemString = [NSString stringWithFormat:@"cpv(%.1ff, %.1ff)", point.x, point.y];
itemString = [NSString stringWithFormat:@"Point(%.1ff, %.1ff)", point.x, point.y];
1
2
3
4
5
case VHSTYLE_INIT:
if (p == 0) {
//result = [result stringByAppendingFormat:@"CGPoint %@[] = {\n", variableName];
result = [result stringByAppendingFormat:@"Point %@[] = {\n", variableName];
}
修改好,重新编译。此时生成的数据就会变成一下格式:
correctVertexHelper
将数据复制到程序中,此处以迷宫地图上半部分为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Point verts1[] = {
Point(-146.5f, 155.1f),
Point(-146.5f, -87.6f),
Point(-140.9f, -88.1f),
Point(-140.8f, 155.5f),
Point(162.8f, 154.6f),
Point(162.9f, -27.7f),
Point(12.0f, -29.0f),
Point(12.0f, -33.9f),
Point(167.6f, -34.6f),
Point(168.7f, 154.4f),
Point(235.0f, 155.1f),
Point(235.3f, -91.6f),
Point(238.8f, -93.2f),
Point(239.8f, -91.5f),
Point(239.1f, 159.2f),
Point(-238.3f, 159.0f),
Point(-238.7f, 155.0f),
Point(-147.4f, 154.9f)
};
通过createEdgePolygon()创建多边形刚体,第一个参数为顶点数组名,第二个为数组元素个数,其余几句代码的理解可参考创建弹珠部分。
1
2
3
4
5
6
auto spEdgePolygon1 = Sprite::create("bg1.png");
spEdgePolygon1->setTag(1);
auto borderUpper = PhysicsBody::createEdgePolygon(verts1,18);
spEdgePolygon1->setPhysicsBody(borderUpper);
spEdgePolygon1->setPosition(Point(visibleSize.width/2,visibleSize.height/2));
this->addChild(spEdgePolygon1);
开启重力感应
打开重力感应,当摇晃设备时动态刚体会根据重力左右偏移:
1
2
//open the gravity sensor
layer->setAccelerometerEnabled(true);
写在后面
至此,整个demo的创建已经完成。欢迎指正与交流。
来自:ityran