这一章也是重点,主要先介绍下这一课需要使用到的相关Cocos2d-x的技术
一、粒子特效
粒子特效是什么,这个就百度百度,我只理解到它就是个很酷炫的效果。在Cocos2d-x中使用粒子特效很简单,其实他也就是个Node,创建和使用都跟精灵差不多。另外,粒子特效一般用一个plist来描述,就是一个plist做后缀的xml文件,他描述了粒子的特性,可以自定义粒子,创建粒子特效的时候,加载这个plist文件即可。 下面是代码:
1auto * particleSystem = ParticleSystemQuad::create("particle_texture.plist"); //使用plist文件创建粒子特效particleSystem ->setPosition(vec1); //设置粒子特效显示的位置this->addChild(particleSystem ); //添加到当前的层
二、数据持久化
在Cocos2d-x中,数据存储有很多种方式,一种是Sqlit,轻量级的数据库,不过数据量小的话使用UserDefault是非常方便的,当然还有其他的方式,这里就不介绍了,我也不熟哈。UserDefault,在项目中我就是用他来存储用户的分数和最高分数的,存储一些游戏设置啊基本上都用他的。其实他也就是个map,只是用了xml文件来存储,使用方面跟map也是极其相似。当第一次调用UserDefault::getInstance(); 时将会在系统中创建一个UserDefault.xml文件,注意是系统中创建,也就是说用户可见,也是可以操作的。其实用户的敏感数据放在这里是非常危险的,非常危险!比如现在的最高分,用户修改了那数据就是作假,这游戏追求高分就没意义了。但是我找了半天发现系统文件目录下并没有这个文件,不知道是不是Cocos2d-x采用了些手段隐藏起来了,不过不影响我们使用,不可见本该是它的属性。 下面是使用代码。
1serDefault * userDefault = UserDefault::getInstance(); //第一次调用即创建UserDefualt.xml文件userDefault->setIntegerForKey("topScore",score); //存value,索引是key,每种类型都有对应的get、set方法,跟map一样,有一个key和valueuserDefault->getIntegerForKey("topScore"); //根据key来拿value
三、播放音乐
播放音乐Cocos2d-x也帮我们做了相关的工作,以至于我们想播放音乐时,只需一行代码,对没错,不要998,只要001!so,现在播放音乐这一块只剩下:我们该在where播放音乐,又是在where停止音乐。这个跟我们的游戏逻辑有相当大的联系。暂时还没踩到这个坑,这个只能在游戏中调试调试再调试了。注意:音乐最好是wav格式,我试着播放mp3格式,发现会延迟几秒钟才能播放音乐,所以最好是用工具转下格式。另外一个话题就是音乐缓冲,就是在我们还未开始游戏前,我们得把他缓冲好,要不然下次播放音乐的时候在加载音乐会很卡,小音乐就算了。所以一般的做法就是在游戏开始初始化的时候,即AppDelegate::applicationDidFinishLaunching()中进行preload,预先读取。对了,写这些代码前要把#include “SimpleAudioEngine.h”,还有那些类的命名空间是CocosDenshion,不想每次都用命名空间去调用类就直接using namespace CocosDenshion吧。
下面看代码
1SimpleAudioEngine::getInstance()->preloadBackgroundMusic("sounds/game_music.wav"); //预读背景音乐 SimpleAudioEngine::getInstance()->preloadEffect("sounds/game_over.wav"); //预读音效SimpleAudioEngine::getInstance()->playEffect("sounds/game_over.wav"); //播放音效,一次性,播完即止 SimpleAudioEngine::getInstance()->playBackgroundMusic("sounds/game_music.wav"); //播放背景音乐,循环播放SimpleAudioEngine::getInstance()->stopBackgroundMusic("sounds/game_music.wav"); //停止播放背景音乐
四、游戏逻辑的实现
好吧,下面就开始我们打飞机的正题。
经过之前的一番啪啪啪(敲代码的声音,别想歪了),现在我们已经可以捕捉到碰撞的事件了。这时我们只要在捕捉函数里面做工作就好了,就是万事俱备只差碰撞处理了。
处理,我们该处理些什么呢?
首先子弹与敌机碰撞时,播放敌机被击爆的音乐,计算所得的分数,引爆敌机,就是播放一个敌机爆炸的动画,再移除动画。然后移除Vector中的敌机和子弹,最后从层中移除两者。
然后是我机与敌机碰撞,先播放我机被击爆的音乐,修改游戏状态(游戏已经结束),停止所有的定时器,造成全屏停止的效果,再播放两者爆炸效果,再从层中移除敌机和我机,存储游戏的分数并刷新最高纪录,这里使用到的Cocos2d-x的UserDefualt类,等下介绍如何使用。最后就是跳转到游戏结束场景。
下面来实现这些逻辑。
先声明一切用到的变量和函数。
在GameScene.h中声明如下代码:
1int score; //游戏所得分数virtual void onEnter(); //层进入时调用的方法,碰撞事件监听在此处声明定义void planeBomb(cocos2d::Vec2 vec,int tag); //飞机爆炸动画void bombRemove(Node * sprite); //飞机爆炸动画播放完之后的回调函数void stopAllSchedule(); //停止所有的定时器void gameOver(); //游戏结束逻辑
下面是碰撞事件的处理,具体的逻辑是当两个可以互相碰撞的实体碰撞后,触发onContactBegin事件,然后通过捕捉的参数PhysicsContact类获取到相关的信息。碰撞一般是两个实体进行碰撞的,我们可以通过contact.getShapeA()->getBody()->getNode();和contact.getShapeA()->getBody()->getNode();来获取两个碰撞的实体(就是一个节点啦,游戏里是我机、敌机或子弹精灵)。然后就是判断是哪种碰撞,是我机和敌机,或者是敌机和子弹,然后进行不同的处理。
敌机和子弹碰撞处理:
先是判断碰撞的是哪种飞机,有大的小的,加分不同。
//层进入的时候会调用该函数,进行物理世界的碰撞监听void GameScene::onEnter()
{ Layer::onEnter(); //注意层的生命周期相关的函数一定要先调用父类的,否则会无效。
auto listener = EventListenerPhysicsContact::create(); //创建一个监听器
listener -> onContactBegin = [=](PhysicsContact& contact) //这里使用了个lambda,可以理解为匿名函数,具体的用法去百度吧,(>_<)
{
auto spriteA = (Sprite *)contact.getShapeA()->getBody()->getNode(); //获取到节点是我们最关心的事,获取到了他们就任我们宰割了,嘿嘿
auto spriteB = (Sprite *)contact.getShapeB()->getBody()->getNode();
int tag1 = spriteA->getTag();
int tag2 = spriteB->getTag();
Vec2 vec1 = spriteA->getPosition();
Vec2 vec2 = spriteB->getPosition(); //敌机和子弹碰撞碰撞 if(tag1 + tag2 == 210 || tag1 + tag2 == 211) //敌机105或104 子弹106 我机103
{ SimpleAudioEngine::getInstance()->playEffect("sounds/use_bomb.wav"); //加分,如果是104则是小敌机500分,105是大敌机1000分 if(tag1 == 104 || tag2 == 104)
{
score += 500;
} else
{
score += 1000;
}
auto scoreSpire = (Label *)this->getChildByTag(100); //根据tag获取分数Node,并修改其显示的分数
scoreSpire->setString(String::createWithFormat("分数:%d",score)->_string);
//启动粒子特效
//auto * system = ParticleSystemQuad::create("particle_texture.plist"); //使用自定义的粒子特效,就一个以plict为后缀的xml文件
//auto system = ParticleExplosion::create(); //使用cocos2d自带的粒子特效 if(tag1 == 104 || tag1 == 105)
{
//移除敌机和子弹 enemyList.eraseObject(spriteA); bulletList.eraseObject(spriteB); system->setPosition(vec1); //启动动画 this->planeBomb(vec1,tag1);
} else
{
enemyList.eraseObject(spriteB);
bulletList.eraseObject(spriteA); //启动动画 this->planeBomb(vec2,tag2);
system->setPosition(vec2);
} //粒子特效加入层中 //this->addChild(system);
} //敌机与我机碰撞 else if(tag1 + tag2 == 207 || tag1+tag2 == 208)
{ SimpleAudioEngine::getInstance()->playEffect("sounds/game_over.wav"); //修改游戏状态为结束状态3
status = 3;
stopAllSchedule(); //停止所有的定时器 if(tag1 == 103) //游戏结束逻辑
{ this->planeBomb(vec2,tag2); this->planeBomb(vec1,tag1);
} else
{ this->planeBomb(vec1,tag1); this->planeBomb(vec2,tag2);
} //使用UserDefault存储些用户数据分数,用法比较简单,map的使用方法,
UserDefault * userDefault = UserDefault::getInstance();
int topScore = userDefault->getIntegerForKey("topScore"); if(topScore < score)
{
userDefault->setIntegerForKey("topScore",score);
} else
userDefault->setIntegerForKey("topScore",topScore);
userDefault->setIntegerForKey("currentScore",score);
} else{} //这里处理了敌机和敌机碰撞的bug return true;
}; //注册监听器
EventDispatcher * eventDispatcher = Director::getInstance()->getEventDispatcher();
eventDispatcher ->addEventListenerWithSceneGraphPriority(listener,this);
}
游戏碰撞处理完。
下面就是处理里面调用了一些自定义的函数
//飞机爆动画 ,只需要知道是哪个飞机,和位置,即可void GameScene::planeBomb(Vec2 vec,int tag)//爆炸效果{
float timeDelay = 0.1;
Vectoranimationframe; if(tag == 104) //小敌机动画帧
{
for(int i=1;i<5;i++)
{
auto string = cocos2d::__String::createWithFormat("enemy1_down%d.png",i);
SpriteFrame * sf=SpriteFrame::create(string->getCString(),Rect(0,0,57,43));
animationframe.pushBack(sf);
}
} else if(tag == 105) //大飞机
{
for(int i=1;i<5;i++)
{
auto string = cocos2d::__String::createWithFormat("enemy2_down%d.png",i);
SpriteFrame * sf=SpriteFrame::create(string->getCString(),Rect(0,0,69,95));
animationframe.pushBack(sf);
}
} else //我机爆炸就游戏结束了,给个潇洒的特写,将动画延迟到0.5秒一帧
{
timeDelay = 0.5;
for(int i=1;i<5;i++)
{
auto string = cocos2d::__String::createWithFormat("hero_blowup_n%d.png",i);
SpriteFrame * sf=SpriteFrame::create(string->getCString(),Rect(0,0,102,126));
animationframe.pushBack(sf);
}
}
Animation * ani=Animation::createWithSpriteFrames(animationframe,timeDelay);
auto blanksprite=Sprite::create();
blanksprite->setTag(tag); //参数中的回调函数会在动画播放完后调用
Action * act=Sequence::create(Animate::create(ani),CCCallFuncN::create(blanksprite,callfuncN_selector(GameScene::bombRemove)),NULL);
this->addChild(blanksprite);
blanksprite->setPosition(vec);
blanksprite->runAction(act);
}//移除动画void GameScene::bombRemove(Node * sprite)//删除自己{
sprite->removeFromParentAndCleanup(true); if (sprite->getTag() == 103) //当移除了我机爆炸的动画,那就是游戏结束了。
{
SimpleAudioEngine::getInstance()->stopBackgroundMusic("sounds/game_music.wav");
gameOver(); //游戏结束逻辑
}
}
好,下面看看效果
游戏结束后,应该是跳到显示分数和最高分,还有继续游戏,退出游戏的场景,下一节就实现他。完结篇了哇。
相关教程:
Cocos2d-x 3.x《飞机大战》教程1:环境与创建项目
https://www.taikr.com/article/1587
Cocos2d-x 3.x《飞机大战》教程2:素材准备与游戏菜单场景
https://www.taikr.com/article/1589
Cocos2d-x 3.x《飞机大战》教程3:物理引擎的使用 https://www.taikr.com/article/1590
Cocos2d-x 3.x《飞机大战》教程4:游戏场景
Cocos2d-x 3.x《飞机大战》教程6:游戏结束场景 https://www.taikr.com/article/1593