最新文章
Cocos2d-x游戏开发实例详解7:对象释放时机
03-25 13:59
Cocos2d-x游戏开发实例详解6:自动释放池
03-25 13:55
Cocos2d-x游戏开发实例详解5:神奇的自动释放
03-25 13:49
Cocos2d-x游戏开发实例详解4:游戏主循环
03-25 13:44
Cocos2d-x游戏开发实例详解3:无限滚动地图
03-25 13:37
Cocos2d-x游戏开发实例详解2:开始菜单续
03-25 13:32
Cocos2d-x 3.x《飞机大战》教程5:敌我碰撞处理、分数计算、音乐播放
这一章是教程的重点内容,将详细介绍本课程需要使用到的相关Cocos2d-x技术,包括粒子特效、数据持久化、音乐播放等,以及飞机大战游戏中敌我碰撞的处理逻辑。
一、粒子特效
粒子特效是一种能够呈现出酷炫视觉效果的技术。在Cocos2d-x中,粒子特效本质上是一个Node,其创建和使用方式与精灵类似。通常,粒子特效使用一个以.plist为后缀的XML文件来描述粒子的特性,开发者可以根据需求自定义粒子。在创建粒子特效时,只需加载这个.plist文件即可。以下是创建粒子特效的示例代码:
auto * particleSystem = ParticleSystemQuad::create("particle_texture.plist"); // 使用plist文件创建粒子特效
particleSystem->setPosition(vec1); // 设置粒子特效显示的位置
this->addChild(particleSystem); // 添加到当前的层
二、数据持久化
在Cocos2d-x中,有多种数据存储方式。其中,SQLite是一种轻量级数据库,但对于数据量较小的情况,使用UserDefault会更加方便。UserDefault本质上类似于一个map,它使用XML文件来存储数据,使用方式也与map极为相似。当第一次调用UserDefault::getInstance()时,系统会创建一个UserDefault.xml文件。不过需要注意的是,该文件是用户可见且可操作的,因此将用户的敏感数据存储在此处存在安全风险。例如,游戏的最高分数据如果被用户修改,会导致数据造假,使游戏追求高分的意义丧失。虽然在系统文件目录下可能无法找到该文件,但这并不影响我们的使用。以下是使用UserDefault的示例代码:
UserDefault * userDefault = UserDefault::getInstance(); // 第一次调用即创建UserDefualt.xml文件
userDefault->setIntegerForKey("topScore", score); // 存value,索引是key,每种类型都有对应的get、set方法,跟map一样,有一个key和value
userDefault->getIntegerForKey("topScore"); // 根据key来拿value
三、播放音乐
Cocos2d-x为音乐播放提供了便捷的支持,开发者只需一行代码即可实现音乐播放。在实际开发中,需要考虑的是在何处播放音乐以及在何处停止音乐,这与游戏逻辑密切相关,需要在游戏中进行调试。建议音乐使用wav格式,因为mp3格式可能会出现播放延迟的问题。为了避免播放音乐时卡顿,需要在游戏开始前对音乐进行缓冲,通常在AppDelegate::applicationDidFinishLaunching()中进行预加载操作。在编写代码前,需要包含头文件#include "SimpleAudioEngine.h",并使用命名空间CocosDenshion。以下是音乐播放的示例代码:
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;
// 预读背景音乐
SimpleAudioEngine::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中的敌机和子弹。 - 从层中移除敌机和子弹。
我机与敌机碰撞
当我机与敌机碰撞时,需要执行以下操作:
- 播放我机被击爆的音乐。
- 修改游戏状态为结束状态。
- 停止所有的定时器,实现全屏停止的效果。
- 播放两者爆炸效果。
- 从层中移除敌机和我机。
- 使用
UserDefault存储游戏的分数并刷新最高纪录。 - 跳转到游戏结束场景。
代码实现
声明变量和函数
在GameScene.h中声明以下代码:
int score; // 游戏所得分数
virtual void onEnter(); // 层进入时调用的方法,碰撞事件监听在此处声明定义
void planeBomb(cocos2d::Vec2 vec, int tag); // 飞机爆炸动画
void bombRemove(Node * sprite); // 飞机爆炸动画播放完之后的回调函数
void stopAllSchedule(); // 停止所有的定时器
void gameOver(); // 游戏结束逻辑
碰撞事件处理
// 层进入的时候会调用该函数,进行物理世界的碰撞监听
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 = 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;
Vector<SpriteFrame*> animationframe;
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(); // 游戏结束逻辑
}
}
效果查看与后续内容
完成上述代码后,我们可以查看游戏效果。游戏结束后,应该跳转到显示分数和最高分,以及提供继续游戏、退出游戏选项的场景,下一节将实现这部分内容。