cocos2dx 3.2中的物理引擎初探(一)

  cocos2dx在设计之初就集成了两套物理引擎,它们是box2d和chipmunk。我目前使用的是最新版的cocos2dx 3.2。引擎中默认使用的是chipmunk,如果想要改使用box2d的话,需要修改对应的android工程或者是ios工程的配置文件。

  在2.x版本的cocos中,使用物理引擎的步骤十分繁琐。但在3.x版本中变得非常方便了。我这次的学习目标是制作一个打砖块的小游戏。

  首先,现在的Scene类提供了一个静态工厂方法,用以创造一个集成物理引擎的场景。  

 Scene::initWithPhysics()

  这个方法能让你的场景具备创建物理世界的基本条件,接下来我们设置这个屋里世界的重力条件,因为打砖块游戏中不需要重力的影响,所以我们把该场景里的重力设置为0:

PhysicsWorld* world = getPhysicsWorld();
world->setGravity(Vec2(0,0));

  然后我们要给这个物理世界创造一个边界,便于我们观察效果,我的做法是把物理世界的scene和游戏逻辑的实现分开,新建一个继承自layer的类来写游戏逻辑:

  这是头文件:

 1 #include "Util.h"
 2
 3 #ifndef __FristGame__GameLayer__
 4 #define __FristGame__GameLayer__
 5
 6 class GameLayer:public Layer
 7 {
 8 public:
 9     Sprite* ball;
10     Sprite* paddle;
11     Sprite* edgeSp;
12     Sprite* prop;
13     PhysicsBody* ballBody;
14     TMXTiledMap* map;
15
16     CREATE_FUNC(GameLayer);
17     bool init();
18
19     void loadPhysicsBody();//加载物理世界的边界
20     void loadTileMap();//加载使用tiledmap地图编辑器制作的地图
21     void loadProp();//加载碰撞特定砖块会掉落的道具
22     void update1(float dt);//打开一个定时器
23     void contact();//碰撞事件注册
24 };
25 #endif /* defined(__FristGame__GameLayer__) */

  小提示:在3.2版本的物理世界中我们不能使用scheduleupdate()函数,似乎body(刚体)的运动是在update里处理的,一旦我们重写了这个函数,物理世界中的小球就不再运动了。所以我们另外设置一个定时器update1来使用。

  这是cpp文件:

  1 #include "GameLayer.h"
  2 bool GameLayer::init()
  3 {
  4     loadTileMap();
  5     loadPhysicsBody();
  6     return true;
  7 }
  8 void GameLayer::loadPhysicsBody()
  9 {
 10     auto visibleSize = Director::getInstance()->getVisibleSize();//取得当前屏幕的尺寸size
 11     auto origin = Director::getInstance()->getVisibleOrigin();
 12
 13     edgeSp = Sprite::create();//创建一个精灵
 14     auto boundBody = PhysicsBody::createEdgeBox(visibleSize,PhysicsMaterial(0.0f,1.0f,0.0f),3);//edgebox是不受刚体碰撞影响的一种刚体,我们用它来设置物理世界的边界
 15     edgeSp->setPosition(visibleSize.width/2, visibleSize.height/2);//位置设置在屏幕中央
 16     edgeSp->setPhysicsBody(boundBody);//将精灵容纳的刚体设置为boundbody。注意这里不能确定刚体和精灵是不是父子节点的关系。有兴趣的朋友请自行研究。
 17     addChild(edgeSp);//加入渲染树
 18
 19     ball = Sprite::create("game_ball_a.png");//创建小球的精灵
 20     ball->setPosition(100,100);//设定位置在屏幕中下部
     //PhysicsMaterial是设置刚体属性的类,三个参数分别对应三个属性:1、density(密度)2、restiution(弹性)3、friction(摩擦力),在这个游戏中我们需要小球无限碰撞,因此摩擦力和密度都设为1,弹力设为1。
 21     ballBody = PhysicsBody::createCircle(ball->getContentSize().width/2,PhysicsMaterial(0.0f,1.0f,0.0f));
 22     ballBody->setContactTestBitmask(0xFFFFFFFF);//接触掩码值---------标注1------------(见代码后)
 23     Vect force = Vect(1000.0f,1000.0f);
 24     ballBody->applyImpulse(force);//这个方法不会产生力,但是会让一个速度与body的速度叠加 产生新的速度(通过这个方法我们让小球匀速运动)
 25     ballBody->setVelocity(Vec2(150,150));//设置小球速度
 26     ball->setPhysicsBody(ballBody);
 27     addChild(ball);
 28
 29     Sprite* batSprite = Sprite::create("game_av_d.png");//创建打砖块游戏中的砖块
 30     PhysicsBody* batBody = PhysicsBody::createEdgeBox(batSprite->getContentSize(),PhysicsMaterial(0.0f,1.0f,0.0f));//创建相应的刚体并设置材质 32     batSprite->setPhysicsBody(batBody);
 33     batSprite->setPosition(winSize.width/2,50);
 34     addChild(batSprite);
 35     batBody->setContactTestBitmask(0xFFFFFFFF);//设置接触掩码值
 36     EventListenerTouchOneByOne* ev1 = EventListenerTouchOneByOne::create();//3.x版本之后对触摸事件做了全盘的修改,这里不作详细描述。这是创建一个单点触摸事件。
 37     ev1->onTouchBegan = [](Touch* touch,Event* ev){return true;};//touchbegin不作任何处理,跳过
 38     ev1->onTouchMoved = [=](Touch* touch,Event* ev){
 39         float x = touch->getDelta().x;
 40         batSprite->setPositionX(batSprite->getPositionX()+x);
 41     };//在touchmove中移动挡板,按照触摸滑动的距离来移动挡板。
 42     _eventDispatcher->addEventListenerWithSceneGraphPriority(ev1, this);//将触摸事件加入监听器
 43     contact();//调用注册碰撞事件的函数
 44     schedule(schedule_selector(GameLayer::update1));//打开定时器
 45
 46 }
 47 void GameLayer::contact()
 48 {
 49     EventListenerPhysicsContact* evContact = EventListenerPhysicsContact::create();//创建一个物理世界的碰撞事件
 50     evContact->onContactBegin = [](PhysicsContact& contact){return true;};
 51     evContact->onContactSeperate = [=](PhysicsContact& contact)//该函数在两个碰撞的刚体分离后调用
 52     {
 53         auto bodyA = (Sprite*)(contact.getShapeA()->getBody()->getNode());//两个碰撞刚体相对应的节点之A
 54         auto bodyB = (Sprite*)(contact.getShapeB()->getBody()->getNode());//两个相碰撞刚体对应节点之B
 55         if(!bodyA||!bodyB)//按理说碰撞发生之后不会发生有一个刚体的节点不存在的情况,但是实际测试时发现bodyA或bodyB有为NULL的情况,因此我们在这里做一个判断排除节点为空的情况
 56             return;
 57         int tagA = bodyA->getTag();
 58         int tagB = bodyB->getTag();
 59         if(tagA == 3)//如果碰撞双方刚体有一个是砖块,则把这个砖块连同节点一同删掉
 60         {
 61             bodyA->removeFromParentAndCleanup(true);
 62         }
 63         if(tagB == 3)
 64         {
 65             bodyB->removeFromParentAndCleanup(true);
 66         }
 67         prop->setVisible(true);
 68     };
 69     _eventDispatcher->addEventListenerWithSceneGraphPriority(evContact,this);//注册碰撞事件
 70 }
 71 void GameLayer::loadTileMap()
 72 {
 73     map = TMXTiledMap::create("textmap.tmx");//从tmx创建一个TMXTileMap类
 74     map->setPositionX(getPositionX() + 34);//设置位置 76     addChild(map);
 77     TMXLayer* layer = map->getLayer("bricks");//从map中取出“bricks”图层
     //这个循环嵌套是为了给每个砖块精灵设置一个刚体
 78     for(int x=0;x<13;x++)
 79     {
 80         for(int y=0;y<18;y++)
 81         {
 82             int gid = layer->getTileGIDAt(Vec2(x,y));//为了提高点效率,我们没必要给每个tile加上刚体,在创建地图时我们设定了空白处的gid值为12,因此我们只对非12的tile加上刚体
 83             if(gid != 12)
 84             {
 85                 Sprite* sprite = layer->getTileAt(Vec2(x,y));//从tile的坐标取出对应的精灵
 86                 if(!sprite)//防止sprite为NULL
 87                     continue;
 88                 PhysicsBody* body = PhysicsBody::createEdgeBox(sprite->getContentSize(),PhysicsMaterial(1.0f,1.0f,0.0f));//给精灵设置一个刚体
 89                 sprite->setTag(3);//加入tag,方便碰撞时的判断
 90                 body->setContactTestBitmask(0xFFFFFFFF);//设置接触掩码值
 91                 sprite->setPhysicsBody(body);
 92             }
 93         }
 94     }
 95     loadProp();
 96 }
 97 void GameLayer::loadProp()
 98 {
 99     TMXObjectGroup* objects = map->getObjectGroup("prop");//从地图中取出对象层prop
100     CCASSERT(NULL != objects, "‘Objects‘ object group not found");//防止为空
101     auto spawnPoint = objects->getObject("pop");//从对象层中取出对象pop,在创建地图时设置好的
102     CCASSERT(!spawnPoint.empty(), "spawnPoint object not found");
103     int x = spawnPoint["x"].asInt();//spwanPoint应该是一个map型容器,这方面我理解不深,不多描述了。
104     int y = spawnPoint["y"].asInt();
105     prop = Sprite::create("game_energy_b.png");//按照从地图中取出的坐标创建一个精灵
106     prop->setPosition(x,y);
107     prop->setVisible(false);//一开始设置为不可见,当碰撞发生时设置为可见,并开始向下运动
108     prop->setZOrder(10);//防止被地图掩盖
109     map->addChild(prop);
110 }
111 void GameLayer::update1(float dt)
112 {
     //-----------标注2----------------
113     float x = ballBody->getVelocity().x;
114     float y = ballBody->getVelocity().y;
115     if(x!=150&&x!=-150)
116     {
117         if(x<0)
118             x = -150;
119         else
120             x = 150;
121     }
122     if(y!=150&&y!=-150)
123     {
124         if(y<0)
125             y = -150;
126         else
127             y = 150;
128     }
129     ballBody->setVelocity(Vec2(x,y));
130
131 }

  这里对代码中的标注进行一些解释:

  标注1:关于接触掩码值。在3.0中的事件分发机制都由事件派发器管理,所以物理引擎的碰撞事件也不例外。 下面代码注册碰撞响应事件和回调函数

1 auto contactListener = EventListenerPhysicsContact::create();
2 contactListener->onContactBegin = CC_CALLBACK_1(HelloWorld::onContactBegin, this);
3 _eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);

  每一次碰撞检测事件是有EventListenerPhysicsContact来进行监听的。监听到碰撞事件时,会回调响应事件onContactBegin()来进行碰撞事件的处理。_eventDispatcher是事件派发器,由它管理所有的注册事件。 EventListenerPhysicsContact是碰撞检测中的一种,也可以运用EventListenerPhysicsContactWithBodies,EventListenerPhysicsContactWithShapes,EventListenerPhysicsContactWithGroup 来进行碰撞事件的注册,对你你感兴趣的bodys,shape和group事件进行监听。   

  在上面说了这么多的东西,最重要的东西就是下面的,没有下面的东西,碰撞事件根本不起作用,这就是我第一次运用碰撞时遇到的问题。也就是设置物理接触相关的位掩码值,默认的接触事件不会被接受,需要设置一定的掩码值来使接触事件响应。 接触掩码值有三个值,分别是:

  1、CategoryBitmask,默认值为0xFFFFFFFF

  2、ContactTestBitmask,默认值为 0x00000000

  3、CollisionBitmask,默认值为0xFFFFFFFF 这三个掩码值都有对应的set/get方法来设置和获取。

  这三个掩码值由逻辑与来进行操作测试。
  一个body的CategoryBitmask和另一个body的ContactTestBitmask的逻辑与的结果不等于0时,接触事件将被发出,否则不发送。
  一个body的CategoryBitmask和另一个body的CollisionBitmask的逻辑与结果不等于0时,他们将碰撞,否则不碰撞
  默认情况下的body属性会进行物理碰撞,但不会发送碰撞检测的信号,也就不会响应碰撞回调函数,这个可以看下默认情况下的掩码值的逻辑与

  CategoryBitmask = 0xFFFFFFFF;
  ContactTestBitmask = 0x00000000;
  CategoryBitmask & ContactTestBitmask = 0,所以不会发送碰撞信号
 
  CollisionBitmask = 0xFFFFFFFF;
  CategoryBitmask & CollisionBitmask = 0xFFFFFFFF,所以物体会碰撞,但是不会响应碰撞回调函数。

  上面介绍的掩码值是碰撞检测回调中最重要的,没有上面的掩码值,所有的碰撞回调函数都不会发生。 EventListenerPhysicsContact有四个接触回调函数:

  1、onContactBegin,在接触开始时被调用,仅调用一次,通过放回true或者false来决定两个物体是否有碰撞。同时可以使用PhysicsContact::setData()来设置接触操作的用户数据。当返回false时,onContactPreSolve和onContactPostSolve将不会被调用,但是onContactSeperate将被调用一次。

  2、onContactPreSlove ,会在每一次被调用,通过放回true或者false来决定两个物体是否有碰撞,同样可以用ignore()来跳过后续的onContactPreSolve和onContactPostSolve回调函数。(默认返回true)

  3、onContactPostSolve,在两个物体碰撞反应中的每个步骤中被处理调用。可以在里面做一些后续的接触操作。如销毁body

  4、onContactSeperate,在两个物体分开时被调用,在每次接触时只调用一次,和onContactBegin配对使用。 上述中最重要的就是碰撞检测事件的讲解,这是游戏中用到碰撞经常要用到的。

  这里附上一篇博文,详细的讲解了3.x的碰撞机制:http://www.tuicool.com/articles/2eI7Nv

  标注2:据我这两天的实验来看,在mac下和windows下使用物理引擎产生的效果有巨大的差别。很多博客上的代码都是在windows上能够流畅运行,但是在mac上跑就会有很多问题。

  比如,25行的ballBody->applyImpulse(force)。在windows下只要使用applyForce就可以了,但是在mac下使用applyForce函数会让球的运动越来越快,没多久便会因为速度超过帧数飞出我们设置的边界。

  再比如,update1中的代码,这是为了保证能一直保持匀速运动而写的,在windows下完全不需要这些代码,但是在mac下如果没有,小球会在某次碰撞时候损失速度(随机的,有时不会损失),直至停下来。

  这篇博文就到这里,下面附上截图:

  

cocos2dx 3.2中的物理引擎初探(一)

时间: 2024-10-11 13:48:58

cocos2dx 3.2中的物理引擎初探(一)的相关文章

实例介绍Cocos2d-x中Box2D物理引擎:HelloBox2D

我们通过一个实例介绍一下,在Cocos2d-x 3.x中使用Box2D物理引擎的开发过程,熟悉这些API的使用.这个实例运行后的场景如图所示,当场景启动后,玩家可以触摸点击屏幕,每次触摸时候,就会在触摸点生成一个新的精灵,精灵的运行自由落体运动. HelloBox2D实例 使用Box2D引擎进行开发过程,如图12-15所示.下面我们就按照这个步骤介绍一下代码部分.首先看一下看HelloWorldScene.h文件,它的代码如下: [html] view plaincopy #ifndef __H

实例介绍Cocos2d-x中Box2D物理引擎:使用关节

下面我们将使用Box2D物理引擎技术进行重构.使得关节能够掌握如何在Box2D使用关节约束.HelloWorldScene.cpp中与使用关节的相关代码如下: [html] view plaincopy void HelloWorld::addNewSpriteAtPosition(Vec2 p) { log("Add sprite %0.2f x %02.f",p.x,p.y); //创建物理引擎精灵对象A auto spriteA = Sprite::create("Bo

实例介绍Cocos2d-x中Box2D物理引擎:碰撞检测

在Box2D中碰撞事件通过实现b2ContactListener类函数实现,b2ContactListener是Box2D提供的抽象类,它的抽象函数:virtual void BeginContact(b2Contact* contact).两个物体开始接触时会响应,但只调用一次.virtual void EndContact(b2Contact* contact).分离时响应.但只调用一次.virtual void PreSolve(b2Contact* contact, const b2Ma

实例介绍Cocos2d-x中Box2D物理引擎:碰撞检測

在Box2D中碰撞事件通过实现b2ContactListener类函数实现,b2ContactListener是Box2D提供的抽象类,它的抽象函数:virtual void BeginContact(b2Contact* contact).两个物体開始接触时会响应,但仅仅调用一次. virtual void EndContact(b2Contact* contact).分离时响应. 但仅仅调用一次. virtual void PreSolve(b2Contact* contact, const

cocos2d-js中Chipmunk物理引擎相关(1)

最近看些cocos2d-js的东西,用到其中的Chipmunk的一些东西,因为相关的资料也不是很详细,所以看到一些东西有用就记录下来. 1. chipmunk是cocos2d的一个一个物理引擎,用来模拟重力,碰撞等等的现实场景. 一个chipmunk body代表虚拟空间中的一个物体,他可能会包含一个或者多个chipmunk shape从而构成它的几何形状. 比如将一个动态的chipmunk物体加入到场景中: 初始化: this.body = new cp.Body(1, cp.momentFo

[原][osg][osgEarth]关于在OE中使用物理引擎的调研

关于物理引擎旋转的一些整理 参考文档 http://blog.wolfire.com/2010/03/Comparing-ODE-and-Bullet 介绍ODE和bullet的利弊 http://stackoverflow.com/questions/6408198/bullet-vs-newton-game-dynamics-vs-ode-physics-engines 讨论作者想要哪款物理引擎 https://www.ibm.com/developerworks/cn/opensource

Cocos2d-x 3.0 简捷的物理引擎

Cocos2d-x 3.0 开发(九)使用Physicals取代Box2D和chipmunk http://www.cocos2d-x.org/docs/manual/framework/native/physics/physics-integration/zh  -- 官网+Demo 水墨鱼的专栏 http://www.cocos2d-x.org/docs/catalog/zh --- 官方 搭"server" 须要哪些知识 -- 看搭建什么server了. 9秒论坛,有非常多游戏s

使用cocos2d-js-3.0RC1中的物理引擎chipmunk模拟的“别碰钉子”源码分享(含碰撞检测)

分别用box2d和chipmunk实现了一下,不过box2d没整理,也懒得整理了.chipmunk整理了一下,分享给大家吧. 刚开始研究,抛砖引玉 简要说明:1.初始化物理环境,增加边界 initPhysics: function () { var space = this.space ; var staticBody = space.staticBody; //开启物体形状测试 //this.initDebugMode(); // Gravity space.gravity = cp.v(0,

实例介绍Cocos2d-x物理引擎:HelloPhysicsWorld

我们通过一个实例介绍一下,在Cocos2d-x 3.x中使用物理引擎的开发过程,熟悉这些API的使用.这个实例的运行后的场景,当场景启动后,玩家可以触摸点击屏幕,每次触摸时候,就会在触摸点生成一个新的精灵,精灵的运行是自由落体运动. HelloPhysicsWorld实例 使用物理引擎的一般步骤,如下图所示. 这个过程与上帝创建世界的过程类似,上帝首先创建了世界,然后为世界指定了边界,否则万物就会掉到世界之外的混沌里去了,最后上帝创建了万物.当然这只是一个最基本的步骤,有的时候还需要碰撞检测和使