制作横版游戏KillBear第9课:暂停层+屏蔽下层监听
在上一节课程中,我们实现了添加一个带有冷却效果的技能按钮,为英雄增添了炫目的大招。若你还打算添加其他技能,依次操作即可,这里不再赘述。本课将详细讲述如何在游戏层的右上角添加一个按钮,以实现游戏暂停功能。
开发环境
- Win64 : vs2010
- Cocos2d-x v3.4Final
- TexturePackerGUI
- MapEdit
实现方法说明
当游戏暂停时,下方的按键应无法被触摸。否则,可能会出现各种不可控的 BUG,例如动画停止但角色仍可转向等异常情况。因此,我们先添加一个屏蔽层,然后在其上方添加暂停按钮。
代码实现
BarrierLayer
BarrierLayer.h
#ifndef _BARRIER_LAYER_H_
#define _BARRIER_LAYER_H_
#include "cocos2d.h"
USING_NS_CC;
class BarrierLayer : public Layer
{
public:
BarrierLayer();
~BarrierLayer();
bool init();
CREATE_FUNC(BarrierLayer);
};
#endif
此头文件的内容较为基础,无需过多解释。
BarrierLayer.cpp
#include "BarrierLayer.h"
BarrierLayer::BarrierLayer()
{
}
BarrierLayer::~BarrierLayer()
{
}
bool BarrierLayer::init()
{
if (!Layer::init())
return false;
// 添加一个半灰色的层
LayerColor* backLayerColor = LayerColor::create(Color4B(25, 25, 25, 125));
this->addChild(backLayerColor);
// 添加向下触摸屏蔽
auto callback = [](Touch * , Event *)
{
return true;
};
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = callback;
listener->setSwallowTouches(true);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
return true;
}
在这个实现中,我们首先创建了一个半透明的层,然后添加了一个触摸监听。只要在添加时设置好 ZOrder,就可以实现所需的屏蔽监听功能。这是一种易于理解和实现的方法,当然,也可以通过改变原先层的按钮监听顺序来实现,但这种方式相对繁琐。
GamePause
GamePause.h
该头文件与 BarrierLayer.h 基本类似,只是单纯地创建一个层,因此不再详细分析。
GamePause.cpp
#include "GamePause.h"
#include "GameScene.h"
#include "BarrierLayer.h"
#include "GameStartScene.h"
GamePause::GamePause()
{
}
GamePause::~GamePause()
{
}
bool GamePause::init()
{
if (!Layer::init())
return false;
// 下面是我们的按钮
Size WinSize = Director::getInstance()->getWinSize();
Vec2 CenterPoint = Vec2(WinSize.width / 2, WinSize.height / 2);
auto continuebutton = Button::create("buttonclick.png");
continuebutton->setTitleText("Continue");
continuebutton->addTouchEventListener([=](Ref * pSender, Widget::TouchEventType type)
{
switch (type)
{
case cocos2d::ui::Widget::TouchEventType::ENDED:
{
getParent()->removeFromParent();
Director::getInstance()->resume();
}
break;
}
});
auto restartbutton = Button::create("buttonclick.png");
restartbutton->setTitleText("Restart");
restartbutton->addTouchEventListener([=](Ref * pSender, Widget::TouchEventType type)
{
switch (type)
{
case cocos2d::ui::Widget::TouchEventType::ENDED:
{
// 之前就停止了所有的 scene,不恢复下就算创建新场景也是 pause 的
Director::getInstance()->resume();
Director::getInstance()->replaceScene(TransitionCrossFade::create(0.5f, GameScene::createScene()));
}
break;
}
});
auto titlebutton = Button::create("buttonclick.png");
titlebutton->setTitleText("ToTitle");
titlebutton->addTouchEventListener([=](Ref * pSender, Widget::TouchEventType type)
{
switch (type)
{
case cocos2d::ui::Widget::TouchEventType::ENDED:
{
Director::getInstance()->resume();
// Director::getInstance()->replaceScene(GameStartScene::createScene());
}
break;
}
});
continuebutton->setPosition(Vec2(0, 50) + CenterPoint);
restartbutton->setPosition(CenterPoint);
titlebutton->setPosition(Vec2(0, -50) + CenterPoint);
addChild(continuebutton);
addChild(restartbutton);
addChild(titlebutton);
return true;
}
在这个 cpp 文件中,需要注意的是,我们使用了 Cocos Studio 的 UI 控件,相较于原来创建一个 menu 更加高效。Button 使用的是 9Sprite,自带拉伸变化动画,并且能保证图像不失真(其实这样做也是为了偷懒,减少代码量)。代码中的 include 需要根据实际情况进行调整,这里把下一篇要讲的内容也包含进来了,你可以自行删改。
另外,以下也是一种按键监听方法,将回调函数直接写在后面:
continuebutton->addTouchEventListener([=](Ref * pSender, Widget::TouchEventType type)
{
switch (type)
{
case cocos2d::ui::Widget::TouchEventType::ENDED:
{
getParent()->removeFromParent();
Director::getInstance()->resume();
}
break;
}
});
需要注意的是,这里通过调用 parent 的 removeFromParent 方法,这需要结合后续的代码使用。如果单独使用,直接调用 removeFromParent 即可,否则可能会导致整个游戏退出。
在此过程中,我们会用到以下几个方法:
Director::getInstance()->pause():暂停场景Director::getInstance()->replaceScene():创建新场景代替原来的场景Director::getInstance()->resume():恢复场景
你可以尝试不调用 resume 方法,观察会出现什么情况。
PopupLayer
PopupLayer.h
#ifndef _POPUP_LAYER_H_
#define _POPUP_LAYER_H_
#include "cocos2d.h"
#include "BarrierLayer.h"
USING_NS_CC;
class PopupLayer : public Layer
{
public:
PopupLayer();
~PopupLayer();
bool init(Layer * targetLayer, int type);
static PopupLayer* create(Layer * targetLayer, const int type);
static PopupLayer* create(Layer * targetLayer)
{
return PopupLayer::create(targetLayer, 0);
};
Action * getActionWithNum(int num);
};
#endif
由于需要传入弹出的对象层,所以不能使用 CREATE_FUNC 这个宏。
PopupLayer.cpp
#include "PopupLayer.h"
PopupLayer::PopupLayer()
{
}
PopupLayer::~PopupLayer()
{
}
PopupLayer* PopupLayer::create(Layer * targetLayer, const int type)
{
PopupLayer* popupLayer = new PopupLayer();
if (popupLayer && popupLayer->init(targetLayer, type))
{
popupLayer->autorelease();
return popupLayer;
}
else
{
CC_SAFE_DELETE(popupLayer);
return nullptr;
}
}
bool PopupLayer::init(Layer * targetLayer, const int type)
{
if (!Layer::init())
return false;
addChild(BarrierLayer::create(), 0);
targetLayer->setTag(1);
addChild(targetLayer, 1);
auto popup = getChildByTag(1);
// popup->setScale(0.9f);
popup->runAction(getActionWithNum(type));
return true;
}
// 下面是回调的动画,自己通过序号创建,这里只写了几个从窗口外移入和一个抖动动画
// 默认是抖动动画,也就是 0
Action * PopupLayer::getActionWithNum(int num)
{
Size WinSize = Director::getInstance()->getWinSize();
switch (num)
{
case 1:
{
auto act1 = MoveTo::create(0.0f, Vec2(0, WinSize.height));
auto act2 = MoveTo::create(0.5f, Vec2::ZERO);
return Sequence::createWithTwoActions(act1, act2);
}
case 2:
{
auto act1 = MoveTo::create(0.0f, Vec2(WinSize.width, 0));
auto act2 = MoveTo::create(0.5f, Vec2::ZERO);
return Sequence::createWithTwoActions(act1, act2);
}
case 3:
{
auto act1 = MoveTo::create(0.0f, Vec2(0, -WinSize.height));
auto act2 = MoveTo::create(0.5f, Vec2::ZERO);
return Sequence::createWithTwoActions(act1, act2);
}
case 4:
{
auto act1 = MoveTo::create(0.0f, Vec2(WinSize.width, 0));
auto act2 = MoveTo::create(0.5f, Vec2::ZERO);
return Sequence::createWithTwoActions(act1, act2);
}
default:
{
auto scaleTo = ScaleTo::create(0.15f, 1.25f);
auto scaleTo2 = ScaleTo::create(0.1f, 0.8f);
return Sequence::createWithTwoActions(scaleTo, scaleTo2);
}
}
}
在 init 方法中,我们首先加入一个屏蔽层,然后加入需要弹出的对象层,并让对象层运行动画,默认动画序号为 0,即抖动动画。当然,你也可以根据需求创建删除时的动画,这里不再详细介绍。
Operter
operterLayer.cpp
在我们的控制层 init 方法中,添加一个按钮,实现弹出层回调。
#include "ui/CocosGUI.h"
#include "GamePause.h"
using namespace ui;
Size WinSize = Director::getInstance()->getWinSize();
auto pause = Button::create("pause.png");
pause->setPosition(Vec2(WinSize.width - pause->getContentSize().width / 2, WinSize.height - pause->getContentSize().height / 2));
pause->addTouchEventListener([=](Ref* pSender, Widget::TouchEventType type)
{
switch (type)
{
case cocos2d::ui::Widget::TouchEventType::ENDED:
{
addChild(PopupLayer::create(GamePause::create()));
Director::getInstance()->pause();
}
break;
}
});
addChild(pause);
效果与结语
本篇文章实现了添加一个按钮,用于实现游戏的暂停和重新开始功能。额外添加了一个防止触摸穿透的屏蔽层和弹出动画。
下一篇文章将介绍如何创建游戏开始画面,毕竟游戏刚打开时通常不会直接进入运行状态。