zoukankan      html  css  js  c++  java
  • Cocos2d-x 3.1.1 学习日志13--物理引擎登峰造极之路

          cocos2dx在设计之初就集成了两套物理引擎,它们是box2d和chipmunk。我眼下使用的是最新版的cocos2dx 3.1.1。引擎中默认使用的是chipmunk。假设想要改使用box2d的话,须要改动相应的androidproject或者是iosproject的配置文件。

      在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 2.x和3.x的源代码淘宝地址(欢迎大家光顾):https://shop141567464.taobao.com/?spm=a313o.7775905.1998679131.d0011.pzUIU4
  • 相关阅读:
    字体填充
    通过浏览器直接打开Android应用程序
    IE能够打开网页 可是chrome和火狐打不开网页解决的方法
    openGl学习之加入颜色
    站点变为黑白
    POJ 3169 Layout (图论-差分约束)
    自己定义控件-GifView
    标准C函数库的使用方法
    MyEclipse中jsp的凝视报错解决
    Surrounded Regions
  • 原文地址:https://www.cnblogs.com/mfmdaoyou/p/6755218.html
Copyright © 2011-2022 走看看