今天花了一下午的时间做了个新手引导,用到的知识就是上篇的博客CCClippingNode遮罩解析,还不明白CCClippingNode是怎么回事的就读一下上篇文章吧。先说一下整体的思路吧,游戏正常的界面代码都不改动,逻辑还是原来的逻辑,我们只是在正常的场景上加上一个层,这个层来负责完成新手引导的功能,使用完这个层的时候就将其从场景中清除掉。需要解决的一个重要的问题就是触摸,我们要让我们后添加的这个层注册触摸事件,当点击非模板区域的时候,将触摸事件吞噬掉,这样的话下层就接受不到了,给用户点击错误的感官,当点击了模板区域的时候我们记录一下当前引导的步数,然后向下层传递,下层按他们自己的逻辑处理触摸。最后完成了所有的引导步数后,将注册的触摸事件清除,将引导层清除。下面先看看我们要为哪个场景添加引导层,没添加之前的效果,具体代码实现如下。

利用CCClippingNode做个新手引导

bool HelloWorld::init()
{
    if ( !CCLayer::init() )
    {
        return false;
    }

    CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();

	//添加背景图片
	CCSprite * background = CCSprite::create("background.png");
	background->setPosition(ccp(visibleSize.width/2,visibleSize.height/2));
	this->addChild(background);

	//添加如下的俩个按钮
	CCControlButton * first = this->addButton("first",ccp(visibleSize.width/4,visibleSize.height/3));
	//为按钮添加响应事件
	first->addTargetWithActionForControlEvents(this,
		cccontrol_selector(HelloWorld::first),CCControlEventTouchDown);

	CCControlButton * second = this->addButton("second",ccp(visibleSize.width*3/4,visibleSize.height/3));
	second->addTargetWithActionForControlEvents(this,
		cccontrol_selector(HelloWorld::second),CCControlEventTouchDown);

	//添加一个精灵,点击精灵的时候跟随手指移动
	this->m_sprite = CCSprite::create("sprite.png");
	m_sprite->setPosition(ccp(visibleSize.width/2,visibleSize.height/2));
	this->addChild(m_sprite);

	//添加新手引导层,到时候这里可以做一个判断,如果玩家首次完游戏就添加进这个新手引导,否则不添加
	BeginnerGuide * gudie = BeginnerGuide::create();
	this->addChild(gudie);

	//开启触摸
	this->setTouchEnabled(true);
    return true;
}
//向helloworld层中添加按钮
CCControlButton * HelloWorld::addButton(std::string str,CCPoint point)
{
	//创建俩张九妹图片做为底图
	CCScale9Sprite * normalButton = CCScale9Sprite::create("buttonBackground.png");
	CCScale9Sprite * selectedButton = CCScale9Sprite::create("buttonHighlighted.png");

	CCLabelTTF * label = CCLabelTTF::create(str.c_str(),"",30);
	CCControlButton * button = CCControlButton::create(label,normalButton);
	//设置按钮被选中时候的九图
	button->setBackgroundSpriteForState(selectedButton,CCControlStateSelected);
	button->setPosition(point);
	this->addChild(button);

	return button;
}
//单击按钮时候的事件响应函数
void HelloWorld::first(CCObject * object,CCControlEvent evt)
{
	CCLabelTTF * text = CCLabelTTF::create("first button clicked!","",24);
	text->setPosition(ccp(400,400));
	text->setColor(ccc3(0,255,255));
	this->addChild(text);
}
//同上
void HelloWorld::second(CCObject * object,CCControlEvent evt)
{
	CCLabelTTF * text = CCLabelTTF::create("second button clicked!","",24);
	text->setPosition(ccp(400,200));
	text->setColor(ccc3(0,255,255));
	this->addChild(text);
}
//注册触摸
void HelloWorld::registerWithTouchDispatcher()
{
	CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,0,true);
}

//以下俩个函数实现拖动精灵移动的效果
bool HelloWorld::ccTouchBegan(CCTouch * touch,CCEvent * evt)
{
	//获得手指点击的点的坐标
	CCPoint point = touch->getLocation();
	//获得精灵所在的区域,CCRect包括x,y,width,height
	CCRect rect = this->m_sprite->boundingBox();

	//判断手指点击的点是否点在了精灵上
	if(rect.containsPoint(point))
	{
		//返回true则会接受其他的协议消息
		return true;
	}

	return false;
}

void HelloWorld::ccTouchMoved(CCTouch * touch,CCEvent * evt)
{
	//分别获得了手指现在的点击点和手指上次的点击点位置
	CCPoint point = touch->getLocation();
	CCPoint pointPre = touch->getPreviousLocation();
	//ccpSub将俩个点相减,获得一个移动方向的向量
	CCPoint direction = ccpSub(point,pointPre);

	CCPoint spritePoint = m_sprite->getPosition();
	//ccpAdd将精灵现在的位置点和移动方向的向量相加,获得精灵将要移动到的位置点
	CCPoint spriteDirection = ccpAdd(spritePoint,direction);
	m_sprite->setPosition(spriteDirection);
}

上边的代码就是用来做测验的,我们要说明的是下面的触摸层如何实现,接下来就看一下吧!

#ifndef __BEGINNER_GUIDE_SCENE_H__
#define __BEGINNER_GUIDE_SCENE_H__

#include "cocos2d.h"

using namespace cocos2d;

//创建枚举类型,记录用户单击到多少步
enum STEP
{
	STEP_FIRST,
	STEP_SECOND,
	STEP_THIRD,
	STEP_FORTH //最后一步,有特殊作用
};

class BeginnerGuide : public cocos2d::CCLayer
{
public:
    virtual bool init();
    static cocos2d::CCScene* scene();
    CREATE_FUNC(BeginnerGuide);
	//添加触摸事件
	void registerWithTouchDispatcher();
	bool ccTouchBegan(CCTouch * touch,CCEvent * evt);
	//设置当前新手引导的步数,根据不同的步数使用不同的模板和坐标
	void setStep(enum STEP);
	//当新手引导退出的时候在onExit()中处理一些事情
	void onExit();
private:
	CCSprite * m_sprite;
	//裁剪节点
	CCClippingNode * clippingNode;
	CCSize visibleSize;
	//记录当前的步数
	enum STEP m_step;
	//创建一个手型精灵用来指导用户点击
	CCSprite * m_hand;
};

#endif
bool BeginnerGuide::init()
{
    if ( !CCLayer::init() )
    {
        return false;
    }

    visibleSize = CCDirector::sharedDirector()->getVisibleSize();

	//创建一个遮罩层
	CCLayerColor * layer = CCLayerColor::create(ccc4(0,0,0,150));

	//创建一个裁剪节点
	clippingNode = CCClippingNode::create();
	this->addChild(clippingNode);
	//设置一些属性
	clippingNode->addChild(layer);
	clippingNode->setInverted(true);
	clippingNode->setAlphaThreshold(0);

	//创建一个手型精灵用来指导用户点击
	this->m_hand = CCSprite::create("hand.png");
	this->addChild(m_hand);

	//设置当前新手引导为第一步
	this->m_step = STEP_FIRST;
	this->setStep(m_step);

	//开启触摸
	this->setTouchEnabled(true);
    return true;
}

void BeginnerGuide::registerWithTouchDispatcher()
{
	//CCMenu的优先级是-128,而CCControlButton的优先级是0,这里的优先级至少要和helloWorld场景层中的
	//优先级一样大,也可以设置为0,因为新手引导层是最后添加上去的,所以最先收到触摸的消息
	CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this,0,true);
}

bool BeginnerGuide::ccTouchBegan(CCTouch * touch,CCEvent * evt)
{
	//获取当前的模板
	CCNode * stencil = clippingNode->getStencil();
	CCRect rect = stencil->boundingBox();
	CCPoint point = touch->getLocation();
	//判断手指点击的位置是否为模板的位置
	if(rect.containsPoint(point))
	{
		if(m_step == STEP_FORTH)
		{
			//this->m_sprite->setVisible(false);
			this->removeFromParent();
			return false;
		}
		//改变步骤
		this->setStep((enum STEP)((int)m_step+1));
		return false;
	}

	return true;
}

//设置当前新手引导的步骤,根据不同的步骤使用不同的模板和坐标
void BeginnerGuide::setStep(enum STEP step)
{
	CCNode * stencil = NULL;
	//创建手型精灵的动作,一下动作写的相对的恶心,就是为了实现效果,大家就别看了
	CCScaleTo * scale1 = CCScaleTo::create(0.8f,0.8f);
	CCScaleTo * scale2 = CCScaleTo::create(0.8f,0.7f);
	CCSequence * sequence = CCSequence::create(scale1,scale2,NULL);
	CCRepeatForever * repeat = CCRepeatForever::create(sequence);

	CCMoveTo * move1 = CCMoveTo::create(3.0f,ccp(visibleSize.width*4/5,visibleSize.height/4));
	CCRepeat * repeat1 = CCRepeat::create(sequence,50);
	CCSequence * second = CCSequence::create(move1,repeat1,NULL);

	CCMoveTo * move2 = CCMoveTo::create(2.0f,ccp(visibleSize.width*0.55,visibleSize.height/3));
	CCRepeat * repeat2 = CCRepeat::create(sequence,2);
	CCMoveTo * move2_2 = CCMoveTo::create(2.0f,ccp(visibleSize.width/3,visibleSize.height/3));
	CCSequence * third = CCSequence::create(move2,repeat2,move2_2,NULL);

	m_hand->stopAllActions();

	switch(step)
	{
	case STEP_FIRST:
		//设置模板和模板的位置
		stencil = CCSprite::create("1.png");
		stencil->setPosition(ccp(visibleSize.width/4,visibleSize.height/3));
		//手型精灵执行动作
		this->m_hand->runAction(repeat);
		m_hand->setPosition(ccp(visibleSize.width/3,visibleSize.height/4));
		break;
	case STEP_SECOND:
		//与上边的相同
		stencil = CCSprite::create("2.png");
		stencil->setPosition(ccp(visibleSize.width*3/4,visibleSize.height/3));
		this->m_step = STEP_SECOND;
		//手型精灵执行动作
		this->m_hand->runAction(second);
		break;
	case STEP_THIRD:
		this->m_sprite = CCSprite::create("sprite.png");
		stencil = m_sprite;
		stencil->setPosition(ccp(visibleSize.width/2,visibleSize.height/2));
		this->m_step = STEP_THIRD;
		//手型精灵执行动作
		this->m_hand->runAction(third);
		break;
	case STEP_FORTH:
		this->m_sprite = CCSprite::create("sprite.png");
		this->m_sprite->setPosition(ccp(visibleSize.width/3,visibleSize.height/2));
		stencil = m_sprite;
		this->m_step = STEP_FORTH;
		break;
	}

	//添加模板
	clippingNode->setStencil(stencil);
}

//将当前新手引导层从父节点上移除的时候记住要remove掉触摸
void BeginnerGuide::onExit()
{
	CCLayer::onExit();
	CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
}

利用CCClippingNode做个新手引导效果如图所示,当然程序还是有bug的,比如用户快速点击的时候动作能否跟的上,最主要的问题是当用户点击了最后一步的时候如何进行判断用户是否点击完成,在我的程序中,我需要用户再次点击一下才能退出新手引导。还有就是如果用户没有按照要求点击的话,CCClippingNode的模板应该回到上一步的位置处才对,这里没有实现,不过项目中用的时候就需要根据你自己的需要去做了,这里只是为了说明原理。