注:本教程主要来自《Cocos2D-x权威指南》满硕泉著 机械工业出版社,如需要更详细的内容,请支持并购买正版实体书籍
2013/11/10 更新
在上一个教程里,我们完成了主角和敌人子弹的基本实现,接下来我们看看如何完成打飞机游戏的另外一个主要部分 - 子弹的碰撞检测。
子弹 主角 敌人的碰撞检测
在编写代码之前,我们首先要搞清楚几个东西
1. 碰撞有几种情况
2. 如何判断碰撞
3. 碰撞之后需要对碰撞对象做什么处理
首先是碰撞的情况,这里主要分为三种情况
a. 主角和敌人发生碰撞
b. 主角和敌人子弹发生碰撞
c. 敌人和主角子弹发生碰撞
接下来是如何判断碰撞,这里采用了判断两个两个不同的矩形区域是否有交集来完成碰撞检测,有则发生了碰撞,没有则没有发生碰撞。
最后是碰撞之后需要对碰撞对象做什么处理
主角被碰撞后会出现短暂的闪烁,在闪烁期间内不参与任何碰撞判断
敌人被碰撞后触发爆炸效果并从游戏内消失
搞清楚以上几点之后,我们就可以开始进行实现了。
首先是基本的碰撞检测,先不管碰撞之后的处理
在GameScene.h里加入以下两个私有变量和两个公共函数
class GameMain : public CCLayer {private: bool isReduce; // when isReduce = true, hero is brinking and nothing can hurt it bool isOver; // when isOver = true, all action should be stop......public:...... // rebuild update method to cotrol looping logic virtual void update(float time); // collision method bool isCollision(CCPoint p1, CCPoint p2, int w1, int h1, int w2, int h2);......};
加入了这些变量和函数之后,我们需要先在bool init()函数中对其进行初始化和update函数的调用。
在bool init()最后加入变量初始化和调用update函数
isReduce = false;isOver = false;scheduleUpdate();
重写update(float time)函数,在每一帧都加入对碰撞的检测,bool isCollision(CCPoint p1, CCPoint p2, int w1, int h1, int w2, int h2)用来判断两个不同的矩形是否发生了碰撞。
先来看看bool isCollision(CCPoint p1, CCPoint p2, int w1, int h1, int w2, int h2)
bool GameMain::isCollision(CCPoint p1, CCPoint p2, int w1, int h1, int w2, int h2){ if (abs(p1.x - p2.x) < w1 + w2 && abs(p1.y - p2.y) < h1 + h2) { return true; } return false;}
p1, p2分别为两个矩形的坐标位置,矩形1宽w1, 高h1, 矩形2宽w2, 高h2,当两个矩形的x坐标差的绝对值小于两个矩形宽之和且两个矩形的y坐标差得绝对值小于两个矩形的高之和则说明这两个矩形有交集,即是发生了碰撞,返回true,否则返回false。
然后是重写update函数,在每一帧中加入碰撞检测-三种碰撞情况的判断
1 void GameMain::update(float time) 2 { 3 // Collision judgement 4 5 CCPoint hpos = hero -> getPosition(); 6 if (!isOver) { 7 // 1. Enemy & Hero's bullet 8 for (int i = 0; i < enemys -> capacity(); i++) { 9 GameObjEnemy * enemy = ((GameObjEnemy *) enemys -> objectAtIndex(i));10 CCPoint epos = enemy -> getPosition();11 if (enemy -> isLife) {12 for (int j =0; j < bullets -> capacity(); j++) {13 GameHeroBullet * bullet = ((GameHeroBullet *) bullets -> objectAtIndex(j));14 if (bullet -> getIsVisable()) {15 if (isCollision(bullet -> getPosition(), epos, 5, 13, 21, 28)) {16 enemy -> setdie();17 //gamemark -> addnumber(200);18 break;19 }20 }21 }22 }23 24 // 2. Enemy & Hero25 if (!isReduce && enemy -> isLife && isCollision(hpos, epos, 21, 22.5, 21, 28)) {26 enemy -> setdie();27 setherohurt();28 }29 }30 // 3. Hero and enemy bullet31 if (!isReduce) {32 for (int i = 0; i < enemybullets -> capacity(); i++) {33 GameEnemyBullet * enemybullet = ((GameEnemyBullet *) enemybullets -> objectAtIndex(i));34 CCPoint ebulletpos = enemybullet -> getPosition();35 if (isCollision(hpos, ebulletpos, 21, 22.5, 5, 13)) {36 setherohurt();37 }38 }39 }40 }41 }
先是获得主角的坐标位置hpos,进行最初的判断,游戏是否已经结束,如果没有结束(isOver = false),则
开始进行第一种情况判断 - 敌人和主角的子弹碰撞,遍历每一个敌人,获得敌人的坐标然后判断敌人是否仍然存在(这里需要忘敌人的类中加入bool isLife参数,并在onEnter函数中初始化为true),若存在,在此基础上再遍历主角的每一颗子弹,获得主角子弹的坐标,判断主角子弹是否可见,若可见,再用我们刚才的isCollision函数进行碰撞判断,返回值为真则执行碰撞后的效果。
再在敌人存在的基础上判断其和主角的碰撞,若主角不是被击中状态(isReduce = false)且碰撞判断返回真,则执行碰撞后的效果。
最后判断主角和敌人子弹的碰撞,若主角不是被击中状态(isReduce = false),则遍历每一颗敌人的子弹,若碰撞判断返回真,则执行碰撞后的效果
当然,在这里我们代码中所谓碰撞后的效果仍没被实现,所以在这里如果你想要调试你的代码,我建议先将原来那些效果部分注释掉,用CCLog("who hit who")来代替,以此检测代码是否有效或者存在某些错误。
完成了碰撞判断的逻辑部分之后,我们开始着手碰撞之后效果的处理
首先是我们的主角被敌人子弹或者敌人攻击之后的处理,在GameScene.h中加入以下两个处理主角被碰撞效果的公共函数
// method to handle situation when hero hurt void setherohurt(); void resetreduce();
在GameScene.cpp中对以上两个函数进行实现
void GameMain::setherohurt(){ // Heor is hurt, life reduce hero -> stopAllActions(); CCActionInterval * action = CCBlink::create(5, 10); hero -> runAction(action); schedule(schedule_selector(GameMain::resetreduce), 5.0f); isReduce = true;}void GameMain::resetreduce(){ isReduce = false;}
当主角收到攻击时,停止主角发射子弹的动作,生成一个Blink的动作并使之持续5秒,将isReduce赋值为true,使得主角在闪烁期间不加入到碰撞判断中去,并在5秒之后调用resetreduce,将isReduce重新赋值为false。
然后是敌人被击中后的效果处理,因为敌人被击中后的并不影响我们的碰撞判断,所以效果处理能够放到敌人对象中去实现, 这里我们采用敌人被击中之后触发一个爆炸动画然后从屏幕消失的效果。
在GameObjEnemy.h的敌人类GameObjEnemy中加入以下公共变量和函数
CCSprite * boom;// enemy die methodvoid setdie();
在onEnter函数中初始化处理爆炸动画变量
void GameObjEnemy::onEnter(){ ...... // init boom sprite boom = CCSprite::create("boom1.png"); addChild(boom); boom -> setVisible(false);......}
敌人被击中后的效果处理函数setdie()的实现,并修改restart()函数,将boom设为不可视加入restart函数中去
void GameObjEnemu::restart(){...... boom -> setVisable(false);......}void GameObjEnemy::setdie(){ isLife = false; mainbody -> setVisible(false); boom -> setVisible(true); this -> stopAllActions(); // init boom ani CCAnimation * boomAni = CCAnimation::create(); boomAni -> addSpriteFrameWithFileName("boom1.png"); boomAni -> addSpriteFrameWithFileName("boom2.png"); boomAni -> addSpriteFrameWithFileName("boom3.png"); boomAni -> addSpriteFrameWithFileName("boom4.png"); boomAni -> addSpriteFrameWithFileName("boom5.png"); boomAni -> setDelayPerUnit(0.1f); boomAni -> setRestoreOriginalFrame(true); boom -> runAction(CCSequence::create(CCAnimate::create(boomAni), CCCallFuncN::create(this, callfuncN_selector(GameObjEnemy::restart)), NULL));}
敌人被击中后,将敌人的生存参数isLife赋值为false,设置主体mainbody为不可见,爆炸动画boom为可见,停止敌人的一切动作stopAllActions(), 这里用了5张爆炸效果图来做成一个动画,被击中之后,先运行这个动画,然后调用敌人的restart函数让其重新出现在屏幕上面,这就是敌人被击中后的效果处理。
调试并运行游戏,我们就可以操纵小猫去消灭敌人了!!
到此为止,我们已经完成这个打飞机游戏的核心部分了,接下来我们要着手的是这个游戏的输赢判断以及一些基本的UI实现,请继续关注。