這篇文章主要是通過一步一步實現一個功能完善的跑馬燈公告來展示ClippingNode的用法並且最終深入ClippingNode的源碼,瞭解其實現原理。
首先,先介紹一下ClippingNode,ClippingNode也叫裁剪節點,能將一些內容通過使用模板裁剪出來顯示在界面上,可以實現一些很炫酷的效果。來看看今天要實現的效果
1、ClippingNode類分析
先來看看ClippingNode的聲明文件 看看其中的public方法
class CC_DLL ClippingNode : public Node
{
public:
static ClippingNode* create();
static ClippingNode* create(Node *stencil);
Node* getStencil() const;
void setStencil(Node *stencil);
virtual bool hasContent() const;
GLfloat getAlphaThreshold() const;
void setAlphaThreshold(GLfloat alphaThreshold);
bool isInverted() const;
void setInverted(bool inverted);
};
首先是create,這個方法是用於創建一個ClippingNode,這個就不多做贅述了,第二個create是創建一個帶遮罩模板的裁剪節點。
接下來的getStencil和setStencil分別是獲取和設置一個遮罩模板,裁剪物體方法就是通過這個遮罩模板的,遮罩模板只要是基於Node的對象都可以(非常重要)。
接下來的hasContent返回其是否有需要繪製的內容,如果沒有繪製的內容則返回false,有則返回true。
getAlphaThreshold和setAlphaThreshold分別是獲取和設置一個像素的透明度值,取值範圍從0-1,其中0表示都不繪製,1表示都繪製。0.5表示透明度在0.5以上的都繪製,這個函數涉及到opengl的Alpha測試的相關概念,Alpha測試的作用通過一句話解釋就是:所有像素的透明度值低於某個閥值的統統拋棄,不繪製到屏幕上。
最後的isInverted和setInverted分別表示繪製的內容是模板內的還是模板外的,其效果如下:
2、簡易跑馬燈實現
上節簡單介紹了一下ClippingNode的函數,這節就通過實現一個簡易的跑馬燈功能來直觀的瞭解。首先介紹一下製作跑馬燈的思路。
首先我們需要將跑馬燈中的一部分超出的字裁剪掉,不讓他顯示在界面上。這就需要用到ClippingNode,現在先來做第一步。實現的代碼如下:
//設置模板
auto stencil = Sprite::create();
//設置顯示區域大小
stencil->setTextureRect(Rect(0, 0, 50, 30));
//設置跑馬燈文字
auto text = Label::createWithSystemFont("-1dasdasdasd efadaewfevgds dfhrthrbgrg1-", "", 24);
//設置錨點
text->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
//創建裁剪節點
auto clippingNode = ClippingNode::create(stencil);
//設置節點位置
clippingNode->setPosition(Vec2(700, 400));
//顯示模板內的內容
clippingNode->setInverted(false);
//添加顯示內容
clippingNode->addChild(text, 2);
//加入到UI樹
addChild(clippingNode);
上述的每一句代碼都有註釋,就不再多解釋了,這一步實現出來的效果如下圖,但是跑馬燈還不能動起來,待會我們就將跑馬燈動起來。
現在我們就設計一個Action將跑馬燈動起來,跑馬燈一般需要先將文字左移,移動到文字看不見的時候再將文字移除或者隱藏,代碼如下(爲了簡便,就直接設置隱藏了):
auto sequ = Sequence::create(MoveBy::create(5.0f, Vec2(-text->getContentSize().width, 0)), Hide::create(), nullptr);
text->runAction(sequ);
現在跑馬燈的樣子就如同開篇展示的那樣了,可是這樣還不能直接使用,因爲這只是一串代碼,還需要對其進行一定的封裝,然後提供一個非常簡便的方法給別的類調用。
3、封裝
現在我們從便捷性的角度考慮如何將跑馬燈功能封裝成一個函數供其他類調用。首先提取出函數的參數,分別是:顯示區域,跑馬燈文字,字體字號,跑馬燈位置,跑馬燈的父節點。下面是初步封裝好的一套跑馬燈函數的聲明:
void showMarquee(Node* parent, const std::string& text, const std::string& font, float fontSize, const Rect& showRect, const Vec2& position);
看參數是不是有些略多,每次調用這個函數是不是非常的不方便,那麼我們現在來看看究竟有那些參數是必須要傳入的吧。每次調用跑馬燈顯示的文字都會改變,其他的參數在一個遊戲中是不會改變的。那麼就有必要做一個類來保證使用方法的便捷性了。
首先,我們簡單的構建一下一個跑馬燈類,如下
#include "cocos2d.h"
USING_NS_CC;
class Marquee : public Node
{
public:
CREATE_FUNC(Marquee);
bool init();
void show(const std::string& text);
public:
const std::string& getFont() const { return _font; }
void setFont(std::string& font) { _font = font; }
float getFontSize() const { return _fontSize; }
void setFontSize(float fontSize) { _fontSize = fontSize; }
public:
const Rect& getShowRect() const { return _showRect; }
void setShowRect(Rect& showRect) { _showRect = showRect; }
protected:
Marquee() :
_font(""),
_fontSize(24),
_showRect(Rect(0,0,200,30))
{};
~Marquee() {};
private:
std::string _font;
float _fontSize;
Rect _showRect;
};
然後是最重要的init方法和show方法的實現
bool Marquee::init()
{
//設置模板
auto stencil = Sprite::create();
//設置顯示區域大小
stencil->setTextureRect(_showRect);
//設置跑馬燈文字
_label = Label::createWithSystemFont("", _font, _fontSize);
//設置錨點
_label->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
_label->setAlignment(TextHAlignment::LEFT);
//創建裁剪節點
auto clippingNode = ClippingNode::create(stencil);
//顯示模板內的內容
clippingNode->setInverted(false);
//添加顯示內容
clippingNode->addChild(_label);
//加入到UI樹
addChild(clippingNode);
return true;
}
void Marquee::show(const std::string& text)
{
_label->setString(text);
_label->setPosition(Vec2(0, _label->getContentSize().height / 2));
auto sequ = Sequence::create(MoveBy::create(5.0f, Vec2(-_label->getContentSize().width, 0)), Hide::create(), nullptr);
_label->runAction(sequ);
}
這樣就可以通過以下的調用方法來調用跑馬燈了
<span style="white-space:pre"> </span>Marquee* m = Marquee::create();
m->show("----hhahahah veeeeee-----");
m->setPosition(Vec2(700, 300));
this->addChild(m);
4、完善
看上去,此前的步驟我們已經完成了一個跑馬燈的功能,實際上這個類距離真正能使用還差那麼一點點,因爲傳入跑馬燈的消息的傳入時機是不確定的,可能這一條消息還沒有播放完成下一條就要開始播放了。這樣就需要實現一個播放等待隊列,將需要播放的消息加入播放隊列,然後跑馬燈自動判斷是否需要顯示。下面是改進後的類聲明文件以及實現文件。
.h:
#include "cocos2d.h"
USING_NS_CC;
class Marquee : public Node
{
public:
enum class State
{
idle,
playing,
};
public:
CREATE_FUNC(Marquee);
bool init();
void addMessage(const std::string& text);
public:
const std::string& getFont() const { return _font; }
void setFont(std::string& font) { _font = font; }
float getFontSize() const { return _fontSize; }
void setFontSize(float fontSize) { _fontSize = fontSize; }
public:
const Rect& getShowRect() const { return _showRect; }
void setShowRect(Rect& showRect) { _showRect = showRect; }
public:
const State& getState() const { return _state; }
protected:
Marquee() :
_font(""),
_fontSize(24),
_showRect(Rect(0,0,200,30)),
_state(State::idle)
{};
~Marquee() {};
void show(const std::string& text);
private:
State _state;
private:
std::string _font;
float _fontSize;
Rect _showRect;
private:
Label * _label;
private:
std::queue<std::string> _texts;
};
.cpp:
#include <Marquee.h>
bool Marquee::init()
{
//設置模板
auto stencil = Sprite::create();
//設置顯示區域大小
stencil->setTextureRect(_showRect);
//設置跑馬燈文字
_label = Label::createWithSystemFont("", _font, _fontSize);
//設置錨點
_label->setAnchorPoint(Vec2::ANCHOR_MIDDLE_LEFT);
_label->setAlignment(TextHAlignment::LEFT);
//創建裁剪節點
auto clippingNode = ClippingNode::create(stencil);
//顯示模板內的內容
clippingNode->setInverted(false);
//添加顯示內容
clippingNode->addChild(_label);
//加入到UI樹
addChild(clippingNode);
stencil->setColor(Color3B::BLACK);
addChild(stencil, -1);
return true;
}
void Marquee::show(const std::string& text)
{
_state = State::playing;
_label->setString(text);
_label->setPosition(Vec2(0, 0));
auto sequ = Sequence::create(
Show::create(),
MoveBy::create(5.0f, Vec2(-(_label->getContentSize().width + _showRect.size.width / 2), 0)),
Hide::create() , DelayTime::create(1.0f),
CCCallFunc::create([&]()
{
if (_texts.size() == 0)
{
_state = State::idle;
}
else
{
show(_texts.front());
_texts.pop();
}
}), nullptr);
_label->runAction(sequ);
}
void Marquee::addMessage(const std::string& text)
{
if (text.empty())
{
return;
}
if (_state == State::idle)
{
show(text);
}
else
{
_texts.push(text);
}
}
此處將show方法隱藏,並且提供了addMessage方法,內部實現了一個有限狀態機,根據狀態來顯示剩餘的消息,其使用方法與此前相似:
m = Marquee::create();
m->addMessage("----hhahahah veeeeee-----");
m->setPosition(Vec2(700, 300));
this->addChild(m);