【问题标题】:How to know when all physics bodies have stopped moving in Cocos2d V3.0 with ChipmunkCocos2d V3.0 with Chipmunk 如何知道所有物理实体何时停止移动
【发布时间】:2014-05-24 04:20:03
【问题描述】:

我能想到的唯一方法是在每次碰撞期间检查所有物理体的速度。

- (BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair piece:(CCNode *)pieceA piece:(CCNode *)pieceB{

    float x = 0;
    float y = 0;

    for (int i = 0; i < [[_physicsWorld children] count]; i++) {

        x = x + [[[_physicsWorld children][i] physicsBody] velocity].x;
        y = y + [[[_physicsWorld children][i] physicsBody] velocity].y;
    }

    if ( x == 0 && y == 0 ) {
        NSLog(@"stopped");
    }

    return YES;

}

这会在场景首次加载时记录“停止”多次,然后不会再次记录“停止”,即使在物理物体明显开始移动和碰撞然后停止之后也是如此。

理想情况下,我想要一个委托方法,它会在所有物理实体停止移动时通知我,但我似乎找不到。

仅供参考:我使用的是 Cocos2d V3.0 中内置的标准 Chipmunk 物理引擎

【问题讨论】:

    标签: ios objective-c cocos2d-iphone chipmunk


    【解决方案1】:

    Chipmunk 有一个内部机制,如果被激活,它可以自动停用物理实体。我的方法(我使用 cocos2dx 3.11.1 而不是 -obj 版本和花栗鼠 7.0.1)是:

    1. 激活花栗鼠空闲机制(0.5 秒 - 意思是,如果物体不移动超过 0.5 秒,它将被停用):

      cpSpaceSetSleepTimeThreshold(space, 0.5f);
      

    你不需要使用

        cpSpaceSetIdleSpeedThreshold(space, <speed>);
    

    因为花栗鼠会为你计算阈值速度(根据所使用的引力)。

    1. 使用此代码确定所有物体是否都没有移动(静态和动态物体从不休眠):

      bool isAnyPhysicsBodyMoving(){
          int i = 0; bool isMoving = false;
          const Vector<PhysicsBody*>& bodies = getPhysicsWorld()->getAllBodies();
          while( i < bodies.size() && !isMoving){
             PhysicsBody *body = bodies.at(i);
             isMoving =    cpBodyGetType(body->getCPBody()) == CP_BODY_TYPE_DYNAMIC 
                        && !body->isResting();
             i++;
          }
          return isMoving;
      }
      
    2. 对墙壁使用静态(而非动态)体,以便让物体休眠:

      // wall
      Size visibleSize = Director::getInstance()->getWinSize();
      Vec2 origin = Director::getInstance()->getVisibleOrigin();
      float border = 10.0f;
      Size wallBodySize = Size(visibleSize.width+2*border, visibleSize.height+2*border);
      PhysicsBody *wallBody = PhysicsBody::createEdgeBox(wallBodySize, PhysicsMaterial(1.0f, 1.0f, 0.5f), border);
      Node* wall = Node::create();
      wall->addComponent(wallBody);
      wall->setAnchorPoint(Vec2(0.5f, 0.5f));
      wall->setPosition(Point(visibleSize.width/2+origin.x, visibleSize.height/2+origin.y));
      
      cpVect tt;
      tt.x = wall->getPosition().x; tt.y = wall->getPosition().y;
      //set position manually and BEFORE adding the object into the space
      cpBodySetPosition(wallBody->getCPBody(), tt);
      cpBodySetType(wallBody->getCPBody(), CP_BODY_TYPE_STATIC);
      addChild(wall);
      

    任何与动力体相连的动力体(例如躺着)永远不会休眠。

    1. 在激活调试的情况下对其进行测试

      getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL);
      

    框(其内容)必须变为灰色(=睡眠)而不是红色(=活动):

    为了让它工作,我有:

    1. 在 CCPhysicsWorld.h 中添加了访问方法(获取 cpSpace):

       inline cpSpace* getSpace() const { return _cpSpace; }
      
    2. 修复调用

       cpBodySetTorque(body, 0.0f);`
      

      在 CCPhysicsBody.cpp 到

       if (body->t != 0.0f){
          cpBodySetTorque(body, 0.0f);
       }
      
    3. 修复调用

       cpBodySetPosition(_cpBody, tt);`
      

      在 CCPhysicsBody.cpp 到

       if (!cpveql(tt, cpBodyGetPosition(_cpBody))){
           cpBodySetPosition(_cpBody, tt);
       }
      

    第 2 步和第 3 步是必要的,以避免设置相同的物理体属性,这会唤醒沉睡的身体。

    这种方法的优点是,花栗鼠不会对这些物理身体进行任何计算 - 节省 CPU 和电池。

    【讨论】:

      【解决方案2】:

      我发现了一些有用的东西。

      tl;dr

      基本思想是自己跟踪精灵的位置,然后定期检查它们,看看它们中的任何一个自上次检查以来是否移动过。

      加长版

      我创建了一个 CCNode 的子类,其类名为 Piece

      这些是我添加到物理世界的对象。

      @implementation Piece {
      
          float _previousX;
          float _previousY;
      }
      
      -(void)updatePreviousScreenXandY{
      
          _previousX = self.position.x;
          _previousY = self.position.y;
      }
      
      -(BOOL)hasntMoved{
      
          float currentX = self.position.x;
          float currentY = self.position.y;
      
          if ( currentX == _previousX && currentY == _previousY ) {
              return TRUE;
          }else{
              return FALSE;
          }
      }
      

      这是在我的 CCNode 中充当游戏场景

      -(void)doStuffAfterPiecesStopMoving:(NSTimer*)timer{
      
      
          BOOL noPiecesHaveMoved = TRUE;
      
          for (int i = 0;  i < [[_physicsWorld children] count]; i++) {
              if ( [[_physicsWorld children][i] hasntMoved] == FALSE ) {
                  noPiecesHaveMoved = FALSE;
                  break;
              }
          }
      
          if ( noPiecesHaveMoved ) {
              [timer invalidate];
              NSLog(“Pieces have stopped moving”);
          }else{
              NSLog(“Pieces are still moving”);
              [self updateAllPreviousPiecePositions];
          }
      
      }
      
      -(void)updateAllPreviousPiecePositions{
      
          for (int i=0; i < [[_physicsWorld children] count]; i++) {
              Piece *piece = (Piece*)[_physicsWorld children][i];
              [piece updatePreviousScreenXandY];
          }
      
      }
      

      我要做的就是

      [NSTimer scheduledTimerWithTimeInterval:TIME_BETWEEN_CHECKS
                                       target:_gamePlay
                                     selector:@selector(doStuffAfterPiecesStopMoving:)
                                     userInfo:nil
                                      repeats:YES];
      

      它会在所有 Piece 节点停止移动后运行我想要的任何代码。

      让它正常工作的关键是让 Chipmunk 空间的 sleepTimeThreshold 和上面的计时器的时间值尽可能低。

      我的实验表明以下设置可以正常工作,但任何较低的设置都会导致问题(即碰撞无法正常发生):

      sleepTimeThreshold = 0.15

      我的计时器 = 0.05

      如果有人对上述代码有不同/更好的解决方案或改进,请发布。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-11-09
        • 1970-01-01
        • 2010-12-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多