【嘮叨】
在遊戲的實現過程中,我們有時會需要在某個遊戲對象上的運動軌跡上實現拖尾漸隱效果,這種感覺就好像是類似飛機拉線似的拖尾巴,使我們的遊戲在視覺上感覺很好。
比如:刀光、子 彈的運動軌跡、流星劃痕等等。
cocos2d-x提供了一種內置的拖尾漸隱效果的實現方法:MotionStreak。
偷了幾張圖,呵呵呵呵呵。。。
當然要做出酷炫的拖尾效果,都是需要與 粒子特效Particle 相結合的。
如下再附一張唯美的拖尾效果(MotionStreak + 粒子特效),增加大家學習的激情。
雖然我不會弄愛心,但是我覺得你學完 MotionStreak,你肯定就會弄下面的愛心了。。。
【致謝】
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秒內,漸漸隱去(慢慢變透明),從而形成了拖尾的效果。
如下如所示:
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的位置,來形成拖尾的。
2、《流星劃痕效果》(精靈移動 + MotionStreak + 粒子效果)
代碼實現可參見:http://blog.csdn.net/dionysos_lai/article/details/39083031
流星的尾巴,是在schedule/update中不斷改變MotionStreak的位置,來形成拖尾的。
以下,再讓我介紹一個MotionStreak的簡單實現的“酷炫”例子吧。
3、通過觸摸事件,實現拖尾效果
圖片資源偷自《流星劃痕效果》。
[PIC_XX.png]
[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 。
(2)設置minSeg = 1 、stroke = 10、color = RED 、紋理爲steak.png 。
(3)關於streak->reset() 函數。
細心的小夥伴,肯定發現了我在上面的兩個GIF圖片的一開始,鼠標到處點了好幾下。
爲什麼我會做這種“無用”操作呢?因爲接下來我要講的就是我爲什麼亂點的原因。
我在 onTouchBegan 中寫了這麼一句話:streak->reset() 。
如果去掉這句話。那麼每次觸摸開始時,流星直接移動到觸摸點,streak也跟隨改變位置,就會出現如下的現象:沒有觸摸拖動,只是點擊鼠標,也會出現拖尾劃痕。
而 streak->reset() 的作用就是:清除所有活動條帶段。
3.5、分析與總結
> 如果minSeq和stroke設置較大,即每一段條帶都比較長或寬,視覺上就會看到明顯“一節一節”的那種效果。
> 如果拖尾效果不與粒子特效組合使用的話,就會像上面的例子那樣,比較單調。
> MotionStreak只要改變位置,就會形成拖尾效果。無論是setPosition,還是執行動作(MoveTo、JumpTo)。
> reset() 函數可以清除當前所有存在的漸隱條帶,即所謂的重置。
我只能講到這裏了,MotionStreak能發揮多大的作用,就看大家怎麼用了。
希望大家都能組合成各種“酷炫”的拖尾效果。