最新文章
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.2制作三消类游戏《万圣大作战》5:消除特效以及音效的添加
在完成前期工作之后,我们已经接近完成这款三消游戏。现在,让我们进行最后的几步操作,很快就可以玩上自己制作的游戏了,是不是有点小期待呢?
1. 四消精灵的出现
只有三消的游戏可能会显得有些单调,因此我们需要添加四消甚至更多消除方式。这里我们先添加四消功能,其他消除方式大家可以根据此思路进行扩展。
四消精灵的原理
所谓四消,即当一次消除4个精灵时,会出现一个类似“彩蛋”的特殊精灵。当这个特殊精灵被消除时,将会消除一整行或者一整列。
特殊精灵有三种状态:普通精灵、可以横向消除的精灵、可以纵向消除的精灵。我们可以通过枚举类型来定义这些状态:
// GameDefine.h
// 精灵的显示模式,Hor 横向消除一行,Ver纵向消除一列
enum DisplayMode {
DISPLAY_MODE_NORMAL = 0,
DISPLAY_MODE_HORIZONTAL,
DISPLAY_MODE_VERTICAL
};
在实现四消精灵之前,不要忘记将6种精灵各自横向或纵向的图片载入到游戏中。
设置精灵状态函数
我们需要在精灵类中添加一个设置精灵状态的函数:
// SpriteShape.cpp
// 设置精灵状态
void SpriteShape::setDisplayMode(DisplayMode mode) {
m_displayMode = mode;
SpriteFrame *frame;
switch (mode) {
case DISPLAY_MODE_VERTICAL:
frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(sprVertical[m_imgIndex]);
break;
case DISPLAY_MODE_HORIZONTAL:
frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(sprHorizontal[m_imgIndex]);
break;
default:
return;
}
setDisplayFrame(frame);
}
四消特殊精灵的出现
四消特殊精灵的出现逻辑应放在消除函数中。以下是GameScene类中的checkAndRemoveSprite函数的实现:
// 检测是否有精灵可以移除
void GameScene::checkAndRemoveSprite() {
SpriteShape *spr;
// 设定寿司的忽视检查,之前可能有精灵设置忽视检查,但这次检查要将之前所有的检查都不能忽视
for (int r = 0; r < ROWS; ++r) {
for (int c = 0; c < COLS; ++c) {
spr = map[r][c];
if (!spr) {
continue;
}
spr->setIgnoreCheck(false);
}
}
for (int r = 0; r < ROWS; ++r) {
for (int c = 0; c < COLS; ++c) {
spr = map[r][c];
// 如果该位置没有精灵
if (!spr) {
continue;
}
// 如果该精灵需要被移除
if (spr->getIsNeedRemove()) {
continue;
}
// 如果该精灵是新生成的精灵
if (spr->getIgnoreCheck()) {
continue;
}
// 纵向相同精灵List
std::list<SpriteShape *> colChainList;
getColChain(spr, colChainList);
// 横向相同精灵List
std::list<SpriteShape *> rowChainList;
getRowChain(spr, rowChainList);
std::list<SpriteShape *> longerList;
if (colChainList.size() >= rowChainList.size()) {
if (colChainList.size() < 3) {
continue;
}
longerList = colChainList;
isRow = false;
} else if (rowChainList.size() > colChainList.size()) {
if (rowChainList.size() < 3) {
continue;
}
longerList = rowChainList;
isRow = true;
}
std::list<SpriteShape *>::iterator itList;
// 标志 是否需要设定忽视检查的精灵
bool isSetedIgnoreCheck = false;
for (itList = longerList.begin(); itList != longerList.end(); ++itList) {
spr = *itList;
if (!spr) {
continue;
}
if (longerList.size() > 3) {
if (spr == staSprite || spr == endSprite) {
isSetedIgnoreCheck = true;
spr->setIgnoreCheck(true);
spr->setIsNeedRemove(false);
spr->setDisplayMode(isRow ? DISPLAY_MODE_HORIZONTAL : DISPLAY_MODE_VERTICAL);
continue;
}
}
markRemove(spr);
}
// 如何是自由掉落产生的4消, 取最后一个变化为特殊精灵
if (!isSetedIgnoreCheck && longerList.size() > 3) {
spr->setIgnoreCheck(true);
spr->setIsNeedRemove(false);
spr->setDisplayMode(isRow ? DISPLAY_MODE_HORIZONTAL : DISPLAY_MODE_VERTICAL);
}
}
}
// 消除标记了的精灵
removeSprite();
}
运行游戏后,我们可以看到在消除后会出现一些特殊的精灵,但此时特效还未实现。
2. 精灵的消除效果
单个精灵消除效果
我们先实现单个精灵消除的效果。在explodeSprite函数中进行改动,除了之前的精灵缩小动作,再添加一个圆环放大的效果:
// GameScene.cpp explodeSprite函数
// 精灵的爆炸移除
void GameScene::explodeSprite(SpriteShape* spr) {
float time = 0.2;
// 精灵的动作
spr->runAction(Sequence::create(
ScaleTo::create(time, 0.0),
CallFuncN::create(CC_CALLBACK_1(GameScene::actionEndCallback, this)),
NULL
));
// 爆炸效果 圆圈的动作
auto circleSprite = Sprite::create("circle.png");
addChild(circleSprite, 10);
circleSprite->setPosition(spr->getPosition());
circleSprite->setScale(0); // start size
circleSprite->runAction(Sequence::create(
ScaleTo::create(time, 1.0),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, circleSprite)),
NULL
));
}
横向或纵向消除效果
接下来,我们实现横向和纵向的消除效果,分别定义两个函数:
// GameScene.cpp explodeSpriteH、explodeSpriteV函数
// 精灵的横向消除
void GameScene::explodeSpecialH(Point point) {
// 先设置相应的变量
float scaleX = 4;
float scaleY = 0.7;
float time = 0.3;
Point startPosition = point;
float speed = 0.6f;
auto colorSpriteRight = Sprite::create("colorHRight.png");
addChild(colorSpriteRight, 10);
Point endPosition1 = Point(point.x - GAME_SCREEN_WIDTH, point.y);
colorSpriteRight->setPosition(startPosition);
colorSpriteRight->runAction(Sequence::create(
ScaleTo::create(time, scaleX, scaleY),
MoveTo::create(speed, endPosition1),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteRight)),
NULL
));
auto colorSpriteLeft = Sprite::create("colorHLeft.png");
addChild(colorSpriteLeft, 10);
Point endPosition2 = Point(point.x + GAME_SCREEN_WIDTH, point.y);
colorSpriteLeft->setPosition(startPosition);
colorSpriteLeft->runAction(Sequence::create(
ScaleTo::create(time, scaleX, scaleY),
MoveTo::create(speed, endPosition2),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteLeft)),
NULL
));
}
// 精灵的纵向消除
void GameScene::explodeSpecialV(Point point) {
float scaleY = 4;
float scaleX = 0.7;
float time = 0.3;
Point startPosition = point;
float speed = 0.6f;
auto colorSpriteDown = Sprite::create("colorVDown.png");
addChild(colorSpriteDown, 10);
Point endPosition1 = Point(point.x, point.y - GAME_SCREEN_HEIGHT);
colorSpriteDown->setPosition(startPosition);
colorSpriteDown->runAction(Sequence::create(
ScaleTo::create(time, scaleX, scaleY),
MoveTo::create(speed, endPosition1),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteDown)),
NULL
));
auto colorSpriteUp = Sprite::create("colorVUp.png");
addChild(colorSpriteUp, 10);
Point endPosition2 = Point(point.x, point.y + GAME_SCREEN_HEIGHT);
colorSpriteUp->setPosition(startPosition);
colorSpriteUp->runAction(Sequence::create(
ScaleTo::create(time, scaleX, scaleY),
MoveTo::create(speed, endPosition2),
CallFunc::create(CC_CALLBACK_0(Sprite::removeFromParent, colorSpriteUp)),
NULL
));
}
消除时的判断
在消除精灵时,需要根据精灵的状态调用不同的消除效果函数:
// GameScene.cpp removeSprite函数
// 移除精灵
void GameScene::removeSprite() {
// 做一套移除的动作
isAction = true;
for (int r = 0; r < ROWS; ++r) {
for (int c = 0; c < COLS; ++c) {
SpriteShape* spr = map[r][c];
if (!spr) {
continue;
}
if (spr->getIsNeedRemove()) {
isFillSprite = true;
if (spr->getDisplayMode() == DISPLAY_MODE_HORIZONTAL) {
explodeSpecialH(spr->getPosition());
} else if (spr->getDisplayMode() == DISPLAY_MODE_VERTICAL) {
explodeSpecialV(spr->getPosition());
}
explodeSprite(spr);
}
}
}
}
完成上述代码后,运行游戏即可看到精灵的消除效果。
3. 最高分的记录
我们可以使用本地存储来记录游戏的最高分。如果对如何实现本地存储不太了解,可以先查看Cocos2d-x 之 简单数据存储——Userdefault。
基本设定
首先,在GameDefine.h中进行相关的基本设定:
// GameDefine.h
// 分数存储
#define userDefault CCUserDefault::sharedUserDefault()
在游戏主界面显示最高分
在GameScene类的init函数中,我们将最高分显示在游戏主界面:
// GameScene.cpp init函数
if (!userDefault->getIntegerForKey("Int")) {
userDefault->setIntegerForKey("Int", 0);
}
// 最高分
auto labelHScore = Label::createWithTTF(config, "Highest: 0");
labelHScore->setPosition(Vec2(
GAME_SCREEN_WIDTH - labelHScore->getContentSize().width,
GAME_SCREEN_HEIGHT - labelHScore->getContentSize().height
));
labelHScore->setString(StringUtils::format("Highest: %d", userDefault->getIntegerForKey("Int")));
this->addChild(labelHScore);
在结束界面处理新纪录
在游戏结束界面,如果玩家创造了新的最高分,我们将显示一个动画并更新最高分:
// GameOverScene.cpp setScore函数
void GameOver::setScore(int sc) {
auto labelScore = (Label *)this->getChildByTag(13);
labelScore->setString(StringUtils::format(" %d ", sc));
if (userDefault->getIntegerForKey("Int") < sc) {
// 新纪录
auto newRecord = Sprite::create("sprite_newRecord.png");
newRecord->setPosition(Point(
GAME_SCREEN_WIDTH / 3.05,
GAME_SCREEN_HEIGHT / 1.22
));
newRecord->setScale(10.0f); // start size
newRecord->runAction(ScaleTo::create(1.2f, 1.0));
this->addChild(newRecord);
userDefault->setIntegerForKey("Int", sc);
}
}
4. 音乐、音效的添加
音乐和音效对于游戏来说非常重要,我们可以在游戏中添加背景音乐和音效。一些基础的知识可以参考Cocos2d-x 3.0 背景音乐与音效。
基本设定
在GameDefine.h中进行相关设定:
// GameDefine.h
// 音乐音效
#include "SimpleAudioEngine.h"
#define SOUND_KEY "sound_key"
#define MUSIC_KEY "music_key"
重写虚函数
在每个需要背景音乐的场景中,重写onEnterTransitionDidFinish和cleanup虚函数。在精灵消除、横向纵向消除、游戏结束界面出现新纪录等场景中,都需要添加相应的音效,具体代码可查看源码。
结语
至此,我们完成了这款三消游戏的所有开发工作,现在可以尽情享受自己制作的游戏了。从之前的2048游戏到现在的三消游戏,每一个项目都是一次学习和成长的过程。虽然在撰写这些文章时有很多话想在结语中表达,但此刻却不知从何说起。总之,让我们继续努力,永远保持对技术的热情,不断前行。套用之前的一句话:永远年轻,永远热泪盈眶。