cocos2d-x 十三(简单的碰撞检测)

cocos2d-x里有两个物理引擎分别是box2d和chipmunk(金花鼠,不知道为什么叫这名字……),如果想看看基础的常识及这两个物理引擎的区别请到以下网址:http://bsr1983.iteye.com/blog/1672032 其实本人的物理知识也都快忘记光了……只能一点点补了……

这两个物理引擎可以构建一个属于自己的物理世界,而这个物理世界是基于现实中的物理特性建立的,所以能赋予在这个世界中的对象(即“刚体”,在任何力的作用下,体积和形状都不发生改变的物体叫做“刚体”(Rigid body))现实中的物理属性如重量、密度、摩擦力……因此也可以模拟现实中的物理运动特性如惯性、弹性、阻力……

游戏中常用这样的物理引擎来检测碰撞,即用来检测两个或者多个刚体在一定作用力下相互接触及接触后所产生的相互作用(影响)。当然,其实很多时候只是单纯地检测是否产生了碰撞事件……因此很少去设定碰撞后产生的实际物理作用。

检测碰撞时需要为刚体定义一个shape用来作为碰撞检测的基础轮廓,当两个图形轮廓产生接触时就会产生碰撞事件……

在官方原例中用的chipmunk比较直接的反映了精灵刚体的碰撞事件及作用,但是精灵并非刚体,在写程序时是将精灵与生成的物理世界中的刚体相关联,而后将实现的碰撞效应反馈给精灵。这里是一篇对chipmunk的使用介绍:http://article.ityran.com/archives/3313

在写涉及到物理引擎chipmunk的程序时需要需要在预处理器里定义CC_ENABLE_CHIPMUNK_INTEGRATION=1,步骤是:右击项目->属性->C/C++->预处理器->预处理器定义->添加前面说的参数。下面是官方原例中的代码:

class HelloWorld : public cocos2d::CCLayer
{
public:
	 HelloWorld();
    ~HelloWorld();
    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
    virtual bool init();  

    // there's no 'id' in cpp, so we recommand to return the exactly class pointer
    static cocos2d::CCScene* scene();
    
    // a selector callback
    void menuCloseCallback(CCObject* pSender);

	void onEnter();
	//初始化物理系统
    void initPhysics();
	//在触屏的位置添加精灵
    void addNewSpriteAtPosition(cocos2d::CCPoint p);
	//精灵与刚体的更新事件
    void update(float dt);
	//程序运行的模式定义菜单
    void toggleDebugCallback(CCObject* pSender);
	//触屏事件
    virtual void ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
	//重力感应,功能开启后用于根据手机晃动的三维坐标来计算重力加速度
    virtual void didAccelerate(cocos2d::CCAcceleration* pAccelerationValue);

private:
	//精灵纹理
    cocos2d::CCTexture2D* m_pSpriteTexture; // weak ref
#if CC_ENABLE_CHIPMUNK_INTEGRATION    
	//调试节点
    CCPhysicsDebugNode* m_pDebugLayer; // weak ref
#endif
	//物理世界(空间)
    cpSpace* m_pSpace; // strong ref
	//刚体形状定义
    cpShape* m_pWalls[4];
    // implement the "static node()" method manually
    CREATE_FUNC(HelloWorld);
};
//获取可视区的位置参数
class VisibleRect
{
public:
    static cocos2d::CCRect getVisibleRect();

    static cocos2d::CCPoint left();
    static cocos2d::CCPoint right();
    static cocos2d::CCPoint top();
    static cocos2d::CCPoint bottom();
    static cocos2d::CCPoint center();
    static cocos2d::CCPoint leftTop();
    static cocos2d::CCPoint rightTop();
    static cocos2d::CCPoint leftBottom();
    static cocos2d::CCPoint rightBottom();
private:
    static void lazyInit();
    static cocos2d::CCRect s_visibleRect;
};
实现代码:
using namespace cocos2d;

enum {
	//设定一个父节点的tag
    kTagParentNode = 1,
};

enum {
    Z_PHYSICS_DEBUG = 100,
};

HelloWorld::HelloWorld(){


}


CCScene* HelloWorld::scene()
{
    CCScene * scene = NULL;
    do 
    {
        // 'scene' is an autorelease object
        scene = CCScene::create();
        CC_BREAK_IF(! scene);

        // 'layer' is an autorelease object
        HelloWorld *layer = HelloWorld::create();
        CC_BREAK_IF(! layer);

        // add layer as a child to scene
        scene->addChild(layer);
    } while (0);

    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    bool bRet = false;
    do 
    {
        //////////////////////////////////////////////////////////////////////////
        // super init first
        //////////////////////////////////////////////////////////////////////////

        CC_BREAK_IF(! CCLayer::init());

        //////////////////////////////////////////////////////////////////////////
        // add your codes below...
        //////////////////////////////////////////////////////////////////////////

        // 1. Add a menu item with "X" image, which is clicked to quit the program.

        // Create a "close" menu item with close icon, it's an auto release object.
        CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
            "CloseNormal.png",
            "CloseSelected.png",
            this,
            menu_selector(HelloWorld::menuCloseCallback));
        CC_BREAK_IF(! pCloseItem);

        // Place the menu item bottom-right conner.
        pCloseItem->setPosition(ccp(CCDirector::sharedDirector()->getWinSize().width - 20, 20));

        // Create a menu with the "close" menu item, it's an auto release object.
        CCMenu* pMenu = CCMenu::create(pCloseItem, NULL);
        pMenu->setPosition(CCPointZero);
        CC_BREAK_IF(! pMenu);

        // Add the menu to HelloWorld layer as a child layer.
        this->addChild(pMenu, 1);

        // 2. Add a label shows "Hello World".

        // Create a label and initialize with string "Hello World".
        CCLabelTTF* pLabel = CCLabelTTF::create("Hello World", "Arial", 24);
        CC_BREAK_IF(! pLabel);

        // Get window size and place the label upper. 
        CCSize size = CCDirector::sharedDirector()->getWinSize();
        pLabel->setPosition(ccp(size.width / 2, size.height - 50));

        // Add the label to HelloWorld layer as a child layer.
        this->addChild(pLabel, 1);

        // 3. Add add a splash screen, show the cocos2d splash image.
        CCSprite* pSprite = CCSprite::create("HelloWorld.png");
        CC_BREAK_IF(! pSprite);

        // Place the sprite on the center of the screen
        pSprite->setPosition(ccp(size.width/2, size.height/2));

        // Add the sprite to HelloWorld layer as a child layer.
        this->addChild(pSprite, 0);


#if CC_ENABLE_CHIPMUNK_INTEGRATION      
    // enable events
    setTouchEnabled(true);
    setAccelerometerEnabled(true);

    // title
    CCLabelTTF *label = CCLabelTTF::create("Multi touch the screen", "Arial", 36);
    label->setPosition(ccp( VisibleRect::center().x, VisibleRect::top().y - 30));
    this->addChild(label, -1);

    // reset button


    // init physics 物理引擎初始化
    initPhysics();

#if 1
    // Use batch node. Faster
    CCSpriteBatchNode *parent = CCSpriteBatchNode::create("grossini_dance_atlas.png", 100);
    m_pSpriteTexture = parent->getTexture();
#else
    // doesn't use batch node. Slower
    m_pSpriteTexture = CCTextureCache::sharedTextureCache()->addImage("grossini_dance_atlas.png");
    CCNode *parent = CCNode::create();
#endif
	//添加一个父节点
    addChild(parent, 0, kTagParentNode);
	//初始化时添加一个精灵
    addNewSpriteAtPosition(ccp(200,200));

    // menu for debug layer 设定物理引擎运行的模式用的菜单按钮
    CCMenuItemFont::setFontSize(24);
    CCMenuItemFont *item = CCMenuItemFont::create("Toggle debug", this, menu_selector(HelloWorld::toggleDebugCallback));

    CCMenu *menu = CCMenu::create(item, NULL);
    this->addChild(menu);
    menu->setPosition(ccp(VisibleRect::right().x-100, VisibleRect::top().y-60));
	//调用计时器,此类计时器只认update方法,也就是层里定义的那个
    scheduleUpdate();
#else
    CCLabelTTF *pLabel = CCLabelTTF::create("Should define CC_ENABLE_CHIPMUNK_INTEGRATION=1\n to run this test case",
                                            "Arial",
                                            18);
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    pLabel->setPosition(ccp(size.width/2, size.height/2));
    
    addChild(pLabel);
    
#endif

        bRet = true;
    } while (0);

    return bRet;
}
//调试模式下可以看到为刚体定义的形状,否则形状会不可见
void HelloWorld::toggleDebugCallback(CCObject* pSender)
{
#if CC_ENABLE_CHIPMUNK_INTEGRATION
    m_pDebugLayer->setVisible(! m_pDebugLayer->isVisible());
#endif
}

HelloWorld::~HelloWorld()
{
    // manually Free rogue shapes
    for( int i=0;i<4;i++) {
        cpShapeFree( m_pWalls[i] );
    }

    cpSpaceFree( m_pSpace );

}

void HelloWorld::initPhysics()
{
#if CC_ENABLE_CHIPMUNK_INTEGRATION    
    // init chipmunk
    //cpInitChipmunk();
	//创建一个物理空间
    m_pSpace = cpSpaceNew();
	//设定空间的重力点
    m_pSpace->gravity = cpv(0, -100);

    //
    // rogue shapes
    // We have to free them manually
    //这里创建了四个形状用于创建一个矩形的墙体,其对应的刚体是静态刚体,也就是保持静止不动的刚体
	//cpSegmentShapeNew用来建立一个段状形状,其它定义请看源码
    // bottom
    m_pWalls[0] = cpSegmentShapeNew( m_pSpace->staticBody,
        cpv(VisibleRect::leftBottom().x,VisibleRect::leftBottom().y),
        cpv(VisibleRect::rightBottom().x, VisibleRect::rightBottom().y), 9.0f);

    // top
    m_pWalls[1] = cpSegmentShapeNew( m_pSpace->staticBody, 
        cpv(VisibleRect::leftTop().x, VisibleRect::leftTop().y),
        cpv(VisibleRect::rightTop().x, VisibleRect::rightTop().y), 0.0f);

    // left
    m_pWalls[2] = cpSegmentShapeNew( m_pSpace->staticBody,
        cpv(VisibleRect::leftBottom().x,VisibleRect::leftBottom().y),
        cpv(VisibleRect::leftTop().x,VisibleRect::leftTop().y), 0.0f);

    // right
    m_pWalls[3] = cpSegmentShapeNew( m_pSpace->staticBody, 
        cpv(VisibleRect::rightBottom().x, VisibleRect::rightBottom().y),
        cpv(VisibleRect::rightTop().x, VisibleRect::rightTop().y), 0.0f);
	//为形状设定弹性及摩擦系数并加入到物理空间
    for( int i=0;i<4;i++) {
        m_pWalls[i]->e = 1.0f;
        m_pWalls[i]->u = 1.0f;
        cpSpaceAddStaticShape(m_pSpace, m_pWalls[i] );
    }

    // Physics debug layer
    m_pDebugLayer = CCPhysicsDebugNode::create(m_pSpace);
    this->addChild(m_pDebugLayer, Z_PHYSICS_DEBUG);
#endif
}

void HelloWorld::update(float delta)
{
    // Should use a fixed size step based on the animation interval.
    int steps = 2;
    float dt = CCDirector::sharedDirector()->getAnimationInterval()/(float)steps;
	//为物理空间赋予时间,这里是按全局的动画间隔时间来设定的
    for(int i=0; i<steps; i++){
        cpSpaceStep(m_pSpace, dt);
    }
}





void HelloWorld::addNewSpriteAtPosition(CCPoint pos)
{
#if CC_ENABLE_CHIPMUNK_INTEGRATION    
    int posx, posy;
	//获取节点
    CCNode *parent = getChildByTag(kTagParentNode);
	//随机取重心位置坐标
    posx = CCRANDOM_0_1() * 200.0f;
    posy = CCRANDOM_0_1() * 200.0f;

    posx = (posx % 4) * 85;
    posy = (posy % 3) * 121;

	//设定刚体形状,这里定义了四个矢量点,所以形状为矩形
    int num = 4;
    cpVect verts[] = {
        cpv(-24,-54),
        cpv(-24, 54),
        cpv( 24, 54),
        cpv( 24,-54),
    };
	//创建刚体
    cpBody *body = cpBodyNew(1.0f, cpMomentForPoly(1.0f, num, verts, cpvzero));
	//设定刚体的重心点,然后将刚体加入到物理空间
    body->p = cpv(pos.x, pos.y);
    cpSpaceAddBody(m_pSpace, body);
	//为刚体定义一个物理形状,cpPolyShapeNew用于建立多边形,其它定义请看源码
    cpShape* shape = cpPolyShapeNew(body, num, verts, cpvzero);
	//定义弹性及摩擦系数,然后加入到物理空间
    shape->e = 0.5f; shape->u = 0.5f;
    cpSpaceAddShape(m_pSpace, shape);
	//添加一个物理精灵
    CCPhysicsSprite *sprite = CCPhysicsSprite::createWithTexture(m_pSpriteTexture, CCRectMake(posx, posy, 85, 121));
    parent->addChild(sprite);
	//将精灵关联到刚体并为其设定位置坐标
    sprite->setCPBody(body);
    sprite->setPosition(pos);
#endif
}

void HelloWorld::onEnter()
{
    CCLayer::onEnter();
}

void HelloWorld::ccTouchesEnded(CCSet* touches, CCEvent* event)
{
    //Add a new body/atlas sprite at the touched location
	//根据触屏位置添加刚体精灵
    CCSetIterator it;
    CCTouch* touch;

    for( it = touches->begin(); it != touches->end(); it++) 
    {
        touch = (CCTouch*)(*it);

        if(!touch)
            break;

        CCPoint location = touch->getLocation();

        addNewSpriteAtPosition( location );
    }
}

void HelloWorld::didAccelerate(CCAcceleration* pAccelerationValue)
{
    static float prevX=0, prevY=0;

#define kFilterFactor 0.05f

    float accelX = (float) pAccelerationValue->x * kFilterFactor + (1- kFilterFactor)*prevX;
    float accelY = (float) pAccelerationValue->y * kFilterFactor + (1- kFilterFactor)*prevY;

    prevX = accelX;
    prevY = accelY;

    CCPoint v = ccp( accelX, accelY);
    v = ccpMult(v, 200);
	//重新设定重力点
    m_pSpace->gravity = cpv(v.x, v.y);
}

//取得可视区的原坐标和尺寸
CCRect VisibleRect::s_visibleRect;

void VisibleRect::lazyInit()
{
    if (s_visibleRect.size.width == 0.0f && s_visibleRect.size.height == 0.0f)
    {
        CCEGLView* pEGLView = CCEGLView::sharedOpenGLView();
        s_visibleRect.origin = pEGLView->getVisibleOrigin();
        s_visibleRect.size = pEGLView->getVisibleSize();
    }
}

//计算出可视区
CCRect VisibleRect::getVisibleRect()
{
    lazyInit();
    return CCRectMake(s_visibleRect.origin.x, s_visibleRect.origin.y, s_visibleRect.size.width, s_visibleRect.size.height);
}

//获取左边的中心坐标
CCPoint VisibleRect::left()
{
    lazyInit();
    return ccp(s_visibleRect.origin.x, s_visibleRect.origin.y+s_visibleRect.size.height/2);
}

//获取右边的中心坐标
CCPoint VisibleRect::right()
{
    lazyInit();
    return ccp(s_visibleRect.origin.x+s_visibleRect.size.width, s_visibleRect.origin.y+s_visibleRect.size.height/2);
}
//获取顶点的中心坐标
CCPoint VisibleRect::top()
{
    lazyInit();
    return ccp(s_visibleRect.origin.x+s_visibleRect.size.width/2, s_visibleRect.origin.y+s_visibleRect.size.height);
}
//获取底部的中心坐标
CCPoint VisibleRect::bottom()
{
    lazyInit();
    return ccp(s_visibleRect.origin.x+s_visibleRect.size.width/2, s_visibleRect.origin.y);
}
//获取可视区的中心坐标
CCPoint VisibleRect::center()
{
    lazyInit();
    return ccp(s_visibleRect.origin.x+s_visibleRect.size.width/2, s_visibleRect.origin.y+s_visibleRect.size.height/2);
}
//获取可视区的左上坐标
CCPoint VisibleRect::leftTop()
{
    lazyInit();
    return ccp(s_visibleRect.origin.x, s_visibleRect.origin.y+s_visibleRect.size.height);
}
//获取可视区的右上坐标
CCPoint VisibleRect::rightTop()
{
    lazyInit();
    return ccp(s_visibleRect.origin.x+s_visibleRect.size.width, s_visibleRect.origin.y+s_visibleRect.size.height);
}
//获取可视区的左下坐标
CCPoint VisibleRect::leftBottom()
{
    lazyInit();
    return s_visibleRect.origin;
}
//获取可视区的右下坐标
CCPoint VisibleRect::rightBottom()
{
    lazyInit();
    return ccp(s_visibleRect.origin.x+s_visibleRect.size.width, s_visibleRect.origin.y);
}

void HelloWorld::menuCloseCallback(CCObject* pSender)
{
    // "close" menu item clicked
    CCDirector::sharedDirector()->end();
}

其中CCPhysicsSprite继承于CCSprite,它去掉了缩放、扭曲属性。Chipmunk属于轻量级的物理引擎,简单易用,回头再研究下box2d。

Chipmunk碰撞源代码:http://download.csdn.net/detail/cyistudio/5507643