【唠叨】

    在游戏的实现过程中,我们有时会需要在某个游戏对象上的运动轨迹上实现拖尾渐隐效果,这种感觉就好像是类似飞机拉线似的拖尾巴,使我们的游戏在视觉上感觉很好。

    比如:刀光、子 弹的运动轨迹、流星划痕等等。

    cocos2d-x提供了一种内置的拖尾渐隐效果的实现方法:MotionStreak

    偷了几张图,呵呵呵呵呵。。。

wKioL1TPq8jAfIB8AADmwIntX14300.jpg


wKiom1TPquXSRxlLAApmPU4wty0372.gif


    当然要做出酷炫的拖尾效果,都是需要与 粒子特效Particle 相结合的。

    如下再附一张唯美的拖尾效果(MotionStreak + 粒子特效),增加大家学习的激情。

    虽然我不会弄爱心,但是我觉得你学完 MotionStreak,你肯定就会弄下面的爱心了。。。

wKioL1TPrE3CDGN0AAIdutCiloU678.gif


【致谢】

    http://blog.csdn.net/honghaier/article/details/8600896 (源码原理深入分析)

    http://cn.cocos2d-x.org/tutorial/show?id=2225 (《水果忍者》划动刀光实现)

    http://blog.csdn.net/dionysos_lai/article/details/39083031 (流星拖尾效果实现)




【MotionStreak】


1、原理

    MotionStreak 的拖尾效果,原理实际上是:在相应距离内动态生成一段段的条带,然后一段段逐渐的消隐。

    可以指定消隐的速度fade(时间秒),一段条带最小距离minSeg,以及条带的宽度粗细(stroke),条带的颜色值(color),以及相应的纹理图片对象。

    原理说明:MotionStreak在移动的过程中(setPosition位置发生改变时),会动态生成一段段条带段,然后这些条带段会在生命周期fade秒内,渐渐隐去(慢慢变透明),从而形成了拖尾的效果。

    如下如所示:

wKioL1TQQMjSzflQAACKwye5gVk442.jpg

    PS:因为是一段段条带相连形成拖尾,所以如果条带太粗(stroke太大),在视觉上可能会出现“脱节”的效果,就像上面的图一样。所以在实际使用中,stroke的大小设置应该合理。


2、创建方式

    创建MotionStreak有两种方式:

        > 一种是用图片资源文件作为纹理创建。

        > 另一种是通过纹理图片Texture2D创建。

    各个参数的说明,在“原理”中已经给出解释。

/** 
 *	创建MotionStreak 的两种方式
 **/
	// fade         : 拖尾渐隐时间(秒)
	// minSeg       : 最小的片段长度(渐隐片段的大小)。拖尾条带相连顶点间的最小距离。
	// stroke       : 渐隐条带的宽度。
	// color        : 片段颜色值。
	// path         : 纹理图片的文件名。
	// texture      : 纹理图片的对象指针。
	static MotionStreak* create(float fade, float minSeg, float stroke, const Color3B& color, const std::string& path);

	static MotionStreak* create(float fade, float minSeg, float stroke, const Color3B& color, Texture2D* texture);


	// 使用举例
	auto motionstreak = MotionStreak::create(1.0f, 1.0f, 10.0f, Color3B(255, 0, 0), "streak.png");
//


3、相关函数

    为了实现拖尾渐隐效果,MotionStreak还对Node类的一些函数进行了重载,如update、draw、setPosition等。

    每当MotionStreak改变了位置(setPosition)后,就会形成一条拖尾。而update里则根据位置信息产生一段段新的顶点(条带段),并让之前生成的条带段渐隐或消失。

//
/**
 *	相关函数
 *	tintWithColor                   : 设置顶点颜色值。
 *	reset                           : 删除所有条带段,重置。
 *	setFastMode                     : 设置快速模式。
 *	setStartingPositionInitialized  : 不需要了解,也不需要去使用。
 **/
	// 条带段使用的颜色值
	void tintWithColor(const Color3B& colors);

	// 重置,删除所有条带段
	void reset();

	// 设置是否是快速模式, 默认为true
	// 当为快速模式时,新的顶点被更快的加入,但是新的顶点具有更小的精确值
	// PS:实践测试,请宽恕若菜的无知,若菜实在看不出有何区别。。。
	inline bool isFastMode() const { return _fastMode; }
	inline void setFastMode(bool bFastMode) { _fastMode = bFastMode; }

	// 在刚创建MotionStreak的时候会置为false(表示创建后,还未移动过)
	// 一旦改变Position(即移动后),会置为true(表示拖尾效果开始了,然后移动就会有拖尾的效果了)
	// PS:一般不会手动去设置它,所以不需要了解这个函数。
	inline bool isStartingPositionInitialized() const { return _startingPositionInitialized; }
	inline void setStartingPositionInitialized(bool bStartingPositionInitialized) { _startingPositionInitialized = bStartingPositionInitialized; }
//


4、支持Action动作

    MotionStreak继承自Node类。而既然它一旦移动(位置Position发生改变)就会拉出一条拖尾,那么执行 MoveTo/MoveBy 或者 JumpTo/JumpBy 等等Action动作,自然也是可以形成拖尾效果的啦。


5、使用步骤

    (1)创建MotionStreak。MotionStreak::create()。

    (2)将MotionStreak加入到场景中。this->addChild()。

    (3)设置位置MotionStreak->setPosition();或执行移动Action动作。

    (4)一旦改变了位置之后,就会形成一段拖尾效果了。




【代码实践】


1、《水果忍者》划动刀光效果(MotionStreak + 粒子特效)

    代码实现可参见:http://cn.cocos2d-x.org/tutorial/show?id=2225

    手势划动产生的刀光效果,是利用触摸移动事件,改变MotionStreak的位置,来形成拖尾的。

wKiom1TPquXSRxlLAApmPU4wty0372.gif    


2、《流星划痕效果》(精灵移动 + MotionStreak + 粒子效果)

    代码实现可参见:http://blog.csdn.net/dionysos_lai/article/details/39083031

    流星的尾巴,是在schedule/update中不断改变MotionStreak的位置,来形成拖尾的。

wKioL1TQTWPBwjVpAAGiNb26GBE810.jpg


以下,再让我介绍一个MotionStreak的简单实现的“酷炫”例子吧。


3、通过触摸事件,实现拖尾效果

    图片资源偷自《流星划痕效果》。

wKioL1TQUUahL20lAAAErR9tYA8286.jpg  [PIC_XX.png]

wKiom1TQUF7jFOcfAAANAcgpGjo545.jpg  [steak.png]


    预期实现效果:

        > 触摸开始touchBegan,流星的位置设置为触摸点位置。

        > 触摸移动touchMoved,流星跟随触摸点移动而移动,同时MotionStreak跟随流星移动,形成拖尾效果。


    创建两种不同规格的MotionStreak,看看效果。

        > 设置minSeg = 50、stroke = 30、color = WHITE、纹理为steak.png

        > 设置minSeg = 1  、stroke = 10、color = RED    、纹理为steak.png


 3.1、在HelloWorld.h中添加如下变量与函数。

//
class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();
    virtual bool init();
    CREATE_FUNC(HelloWorld);

    // 触摸事件 回调函数
    bool onTouchBegan(Touch* pTouch, Event* pEvent);
    void onTouchMoved(Touch* pTouch, Event* pEvent);
    
private:
    Sprite* star;         // 流星精灵
    MotionStreak* streak; // 拖尾
};
//


 3.2、在HelloWorld.cpp的init()中,创建流星精灵、MotionStreak拖尾,并添加触摸监听事件。

//
bool HelloWorld::init()
{
    if ( !Layer::init() ) return false;
    
    
    // 流星精灵Sprtie
    star = Sprite::create("PIC_XX.png");
    star->setPosition(100, 100);
    this->addChild(star);
    
    
    // 流星拖尾MotionStreak
    streak = MotionStreak::create(0.5f, 50, 30, Color3B::WHITE, "steak.png");
//    streak = MotionStreak::create(0.5f, 1, 10, Color3B::RED, "steak.png");
    streak->setPosition(star->getPosition()); // 设置拖尾streak的位置
    this->addChild(streak);
    
    
    // 注册单点触摸
    auto dispatcher = this->getEventDispatcher();
    auto listener = EventListenerTouchOneByOne::create();
    listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this); // 触摸开始
    listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::onTouchMoved, this); // 触摸移动
    dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
    
    return true;
}
//


 3.3、实现触摸事件

            > 触摸开始touchBegan  :流星位置设置为触摸点。

            > 触摸移动touchMoved :流星移动,streak跟随流星移动,形成拖尾效果。

//
// 触摸开始 :设置star和streak的位置
bool HelloWorld::onTouchBegan(Touch* pTouch, Event* pEvent)
{
    // 获取触摸点位置
    Vec2 pos = pTouch->getLocation();
    
    // 设置位置
    star->setPosition(pos);
    streak->setPosition(star->getPosition());
    
    // 删除所有活动条带段
    streak->reset();
    
    return true;
}


// 触摸移动 :移动star和streak的位置
void HelloWorld::onTouchMoved(Touch* pTouch, Event* pEvent)
{
    // 触摸移动的偏移量
    Vec2 delta = pTouch->getDelta();
    
    // 设置位置
    star->setPosition(star->getPosition() + delta);
    streak->setPosition(star->getPosition());
}
//


 3.4、运行结果

    (1)设置minSeg = 50、stroke = 30、color = WHITE、纹理为steak.png 。

wKiom1TQXbShRTGdABEGdg6odoE316.gif


    (2)设置minSeg = 1  、stroke = 10、color = RED    、纹理为steak.png 。

wKiom1TQXbjDADbpAAn8suFheik669.gif


    (3)关于streak->reset() 函数。

        细心的小伙伴,肯定发现了我在上面的两个GIF图片的一开始,鼠标到处点了好几下。

        为什么我会做这种“无用”操作呢?因为接下来我要讲的就是我为什么乱点的原因。i_f07.gif

        我在 onTouchBegan 中写了这么一句话:streak->reset()

        如果去掉这句话。那么每次触摸开始时,流星直接移动到触摸点,streak也跟随改变位置,就会出现如下的现象:没有触摸拖动,只是点击鼠标,也会出现拖尾划痕。

        而 streak->reset() 的作用就是:清除所有活动条带段。

wKioL1TQXqWT3_dqAAc8Ryqn9hM065.gif


 3.5、分析与总结

        > 如果minSeq和stroke设置较大,即每一段条带都比较长或宽,视觉上就会看到明显“一节一节”的那种效果。

        > 如果拖尾效果不与粒子特效组合使用的话,就会像上面的例子那样,比较单调。

        > MotionStreak只要改变位置,就会形成拖尾效果。无论是setPosition,还是执行动作(MoveTo、JumpTo)。

        > reset() 函数可以清除当前所有存在的渐隐条带,即所谓的重置。


    我只能讲到这里了,MotionStreak能发挥多大的作用,就看大家怎么用了。

    希望大家都能组合成各种“酷炫”的拖尾效果。i_f32.gif