cocos2d-x十六(简单的碰撞检测 实践篇 中)

这一篇就说说导弹发射与碰撞部分的代码,总体来说就和一个微型的飞行射击游戏差不多,但是在这部分内容里,游戏运用了一些碰撞后的特效代码如:背景闪烁、地面出现弹坑……不过貌似cocos2d-x没有完全封装chipmunk,所以示例中有些功能实现起来过于麻烦,如地面的碰撞线段按边缘附加本来可用的marchAllWithBorder,这样的方法就得替换成手动设置……因此像弹坑这样的效果就不打算再做了,反正弹坑效果也就是混合效果,主要是混合图形后产生新的碰撞线段没什么办法,可以在日后学习box2d后再自己玩下……这里只演示导弹撞击墙体后的爆炸效果:

1.首先需要定义一个炮弹的物理精灵,在原例中,通过自定义导弹对象的实现代码赋予了导出isTracking追踪重力点的属性,使得导弹可以在触摸移动时跟着移动的坐标飞行。这部分代码的实现方式我改用了前面用过的代码,把刚体、形状、精灵关联等代码全部封装进了自定义的导弹精灵类里:

//自定义一个物理精灵类,用于定制导弹的自有属性
class Missile :
	public cocos2d::extension::CCPhysicsSprite
{
public:
	Missile(void);
	~Missile(void);
	//自定义创建导弹精灵,这里自己加了三个参数,分别是乌云精灵的位置,触屏坐标对应的矢量点和加入到的物理空间
	static Missile* create(const char *pszFileName,cocos2d::CCPoint pos,cpVect target,cpSpace* m_pSpace);

	//导弹加速度的回调函数
	static void
	MissileVelocityUpdate(cpBody *cBody, cpVect gravity, cpFloat damping, cpFloat dt);

	//是否追踪触屏移动点
	bool isTracking;
	//触屏点对应的重力矢量点,用于决定导弹发射运行的角度
	cpVect target;

};
导弹需要在物理世界里,所以上面继承的是物理精灵类,下面是实现文件:
#include "Missile.h"
#define MISSILE_SIZE 32.0
#define MISSILE_SPEED 200.0
#define MISSILE_TURN_SPEED 6.0

Missile::Missile(void)
{
	isTracking=false;
}


Missile::~Missile(void)
{

}
//计算带追踪属性的导弹发射角度对应的矢量点
static cpVect
Turn(cpVect v1, cpVect v2, cpFloat limit)
{
	// Angle between the two vectors
	cpFloat angle = cpfacos(cpfclamp01(cpvdot(cpvnormalize(v1), cpvnormalize(v2))));
	if(angle){
		// Performs an nlerp() between two direction vectors.
		cpVect direction = cpvnormalize(cpvlerp(v1, v2, cpfmin(limit, angle)/angle));
		return cpvmult(direction, MISSILE_SPEED);
	} else {
		return v1;
	}
}

//导弹加速度的回调函数
void Missile::MissileVelocityUpdate(cpBody *cBody, cpVect gravity, cpFloat damping, cpFloat dt)
{
	cpBody *abody = cBody;
	Missile *missile = (Missile*)abody->data;
	//根据导弹是否处于追踪状态决定是否刷新矢量点
	if(missile->isTracking){
		cpVect targetVelocity = cpvmult(cpvnormalize(cpvsub(missile->target, abody->p)), MISSILE_SPEED);
		abody->v = Turn(abody->v, targetVelocity, MISSILE_TURN_SPEED*dt);
	}
	//根据当前矢量点计算出导弹运行的角度
	abody->a = cpvtoangle(abody->v) - M_PI/2.0;
}

//重写创建的过程,这里用到的是前两节使用的代码
Missile* Missile::create(const char *pszFileName,cocos2d::CCPoint pos,cpVect target,cpSpace* m_pSpace)
{
    Missile* pRet = new Missile();
    if (pRet && pRet->initWithFile(pszFileName))
    {
#if CC_ENABLE_CHIPMUNK_INTEGRATION    
    int posx, posy;
	pRet->target=target;
	//随机取重心位置坐标
    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(-10,-30),
        cpv(-10, 20),
        cpv( 10, 20),
        cpv( 10,-30),
    };
	//创建刚体
    cpBody *body = cpBodyNew(1.0f, cpMomentForPoly(1.0f, num, verts, cpvzero));
	body->data=pRet;
	//设定刚体的重心点,然后将刚体加入到物理空间
    body->p = cpv(pos.x, pos.y);
	body->v = cpvmult(cpvnormalize(cpvsub(pRet->target, body->p)), MISSILE_SPEED);
	body->velocity_func = MissileVelocityUpdate;
    cpSpaceAddBody(m_pSpace, body);
	//为刚体定义一个物理形状,cpPolyShapeNew用于建立多边形,其它定义请看源码
    cpShape* shape = cpPolyShapeNew(body, num, verts, cpvzero);
	//定义弹性及摩擦系数,然后加入到物理空间
    shape->e = 0.5f; shape->u = 0.5f;
	//设定碰撞类型标识
	shape->collision_type=5;

    cpSpaceAddShape(m_pSpace, shape);
	//cpSpaceAddCollisionHandler(m_pSpace,2,1,beginCollision,NULL,NULL,NULL,NULL);

	shape->data = pRet;

	//将精灵关联到刚体并为其设定位置坐标
    pRet->setCPBody(body);
    pRet->setPosition(pos);
#endif

        pRet->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(pRet);
    }

    return pRet;
}
一些计算、换算方法就自己琢磨下好了……下面是主层的类:
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();
	//触摸时的回调函数
	virtual void ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
	//触摸移动时的回调函数,用于引导导弹
	virtual void ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
	//触屏结束时的回调函数,用于清除导弹的跟踪属性
	virtual void ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
		//初始化物理系统
    void initPhysics();
	//更新地面的碰撞影响
	void updateTerrain();

		//物理系统运行模式的定义菜单
    void toggleDebugCallback(CCObject* pSender);

	#if CC_ENABLE_CHIPMUNK_INTEGRATION    
	//调试节点,可用来查看定义的形状
    CCPhysicsDebugNode* m_pDebugLayer; // weak ref
	#endif
    
    // a selector callback
    void menuCloseCallback(CCObject* pSender);

		//精灵与刚体的更新事件
    void update(float dt);
	//乌云精灵
	cloud* cloudSprite;

		//物理世界(空间)
    cpSpace* m_pSpace;

		//刚体形状定义
    cpShape* m_pWalls[4];

		//碰撞开始时的回调
	static int beginCollision(cpArbiter *arb, cpSpace *space, void *data);

		//碰撞结束后的对象销毁回调函数
	static void postStepRemove(cpSpace *space, cpShape *shape, void *unused);

	//加入爆炸效果
	static void addExplotion(cocos2d::CCPoint pos);

    // implement the "static node()" method manually
    CREATE_FUNC(HelloWorld);

};
然后是创建四面墙体,代码稍微改了下:

//物理系统初始化
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;
  //设定碰撞类型标识
  m_pWalls[i]->collision_type=i;
  //添加墙体到物理空间
        cpSpaceAddStaticShape(m_pSpace, m_pWalls[i] );
  //设定碰撞的回调函数,5是导弹的碰撞类型标识
  cpSpaceAddCollisionHandler(m_pSpace,5,i,beginCollision,NULL,NULL,NULL,NULL);
    }

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

 然后是触屏部分的回调代码:

void HelloWorld::ccTouchesBegan(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;
		
		//根据触屏点创建一个导弹
		MissileSprite=Missile::create("Missile.png",cloudSprite->getPosition(),cpv(this->getParent()->convertTouchToNodeSpace(touch).x,this->convertTouchToNodeSpace(touch).y),m_pSpace);
		MissileSprite->setScale(0.5f);
		addChild(MissileSprite);
		
		//更新乌云眼神的角度
		cloudSprite->lookAt=touch->getLocation();
	}
}

	//触摸移动时的回调函数,用于引导导弹
void HelloWorld::ccTouchesMoved(CCSet* touches, CCEvent* event){
    CCSetIterator it;
    CCTouch* touch;
	    for( it = touches->begin(); it != touches->end(); it++) 
    {

	touch = (CCTouch*)(*it);
	        if(!touch)
            break;

		}

		if(MissileSprite){
		//开启导弹追踪属性并设置重力矢量点到当前触屏移动到的位置
			MissileSprite->isTracking=true;
			MissileSprite->target=cpv(this->getParent()->convertTouchToNodeSpace(touch).x,this->convertTouchToNodeSpace(touch).y);
		}
		//更新乌云眼神的角度
		cloudSprite->lookAt=touch->getLocation();
}

//触摸结束时的回调
void HelloWorld::ccTouchesEnded(CCSet* touches, CCEvent* event){
	//去除导弹的跟踪属性
	if(MissileSprite){
	MissileSprite->isTracking=false;
	}
}
最后是碰撞回调的代码:
int HelloWorld::beginCollision(cpArbiter *arb, cpSpace *space, void *data){
	//获取碰撞的shape,a代表用cpSpaceAddCollisionHandler添加碰撞回调时的碰撞对象类型a,b亦然
	CP_ARBITER_GET_SHAPES(arb, a, b);
	CCLog("begin collision p_x:%1.1f p_y:%1.1f",a->body->p.x,a->body->p.y);
	//设置物理空间在结束当前动作时的回调函数,用于安全释放对象,此调用只会在完成当前动作后调用一次
	cpSpaceAddPostStepCallback(space,(cpPostStepFunc)postStepRemove,a,NULL);
	//addExplotion(a->body->v);
	//返回值为false时,碰撞对象会只在初次碰撞时执行回调函数,两个碰撞对象会重叠在一起
	return true;
}

//移除导弹精灵
void HelloWorld::postStepRemove(cpSpace *space, cpShape *shape, void *unused){
//CCLog("postStep p_x:%1.f p_y:%1.f",shape->body->p.x,shape->body->p.y);
	//取出data中的对应数据
	CCPhysicsSprite *sprite = (CCPhysicsSprite*)shape->data;
		//CCLog("",sprite->getTag());

	//触发爆炸
	addExplotion(ccp(shape->body->p.x,shape->body->p.y));

//释放碰撞的对象,这里释放时要注意顺序
//先释放形状
	cpSpaceRemoveShape(space, shape);
	cpShapeFree(shape);
//释放刚体
	cpSpaceRemoveBody(space, sprite->getCPBody());
	cpBodyFree(sprite->getCPBody());
//移除精灵
	sprite->removeFromParentAndCleanup(true);
//释放消失的导弹指针
	MissileSprite=NULL;
	
}

	//加入爆炸效果
void HelloWorld::addExplotion(CCPoint pos){
	
	//在撞击点加入爆炸效果
    CCParticleSystemQuad *sparkFlare = CCParticleSystemQuad::create("kaboom.plist");
    sparkFlare->retain();
	sparkFlare->setAutoRemoveOnFinish(true);
	//将爆炸位置设置到导弹所在的坐标
	sparkFlare->setPosition(pos);
	layer->addChild(sparkFlare,100);

}
通过上面的代码就可以实现乌云按照触屏点发射导弹,并在与墙体碰撞时产生爆炸……因为有点懒,所以物理空间和形状的一些属性还是按照以前的代码设定,天空的闪烁动画也没做……这个自己有兴趣的话自己修改着玩就行了。有空再看看最后一节看能不能实现碰撞的撞击力,即可以在爆炸后把周围的箱子炸飞,目测是有cpBodyApplyImpulse这个函数……

源代码:http://download.csdn.net/detail/cyistudio/5553235