19、Cocos2dx 3.0游戲開發找小三之Action:流動的水沒有形狀,漂流的風找不到蹤跡、、、
重開發者的勞動成果,轉載的時候請務必注明出處:http://blog.csdn.net/haomengzhu/article/details/30478985
流動的水沒有形狀,漂流的風找不到蹤跡、、、
直睡的陵遷谷變,石爛松枯,斗轉星移,整個宇宙在不停的運動著、、、
前面詳細介紹了游戲的基本組成元素--場景、 層、 精靈和渲染樹等, 也詳細介紹了 Node 提供的定時器。
為了讓整個世界運動起來,讓每一個精靈運動,可以利用定時器,不斷修改節點的屬性,實現簡單的動態效果。
然而,這種方法會導致為了實現簡單的動態效果,十分煩 瑣地維護一批定時器。
Cocos2d-x 為了解決這個問題,引入了動作機制。
Action是動作類的基類
所有的動作都派生自這個類,它創建的一個對象代表了一個動作。
動作作用于Node,因此, 任何一個動作都需要由 Node 對象來執行。
以下代碼實現了一個精靈用 10秒鐘的時間移動到了點(100, 100):
auto sprite = Sprite::create("sprite.png"); auto action = MoveTo::create(1.0f, Point(0, 0)); sprite->runAction(action);
需要注意的是,一個 Action 只能使用一次,
這是因為動作對象不僅描述了動作,還保存了這個動作持續過程中不斷改變的一些中間參數。
對于需要反復使用的動作對象,可以通過 copy 方法復制使用。
Action 作為一個基類,其實質是一個接口(即抽象類),
由它派生的實現類(如運動和轉動等)才是我們實際使用的動作。
Action 的絕大多數實現類都派生自 FiniteTimeAction,這個類定義了在有限時間內可以完成的動作。
FiniteTimeAction定義了 reverse 方法,通過這個方法可以獲得一個與原動作相反的動作(稱作逆動作),例如隱藏一個精靈后,用逆轉動作再顯示出來。
當然,并非所有的動作都有對應的逆動作,
例如類似"放大到"等設置屬性為常量的動作不存在逆動作,而設置屬性為相對值的動作則往往存在相應的逆動作。
由 FiniteTimeAction 派生出的兩個主要類分別是瞬時動作(ActionInstant)和持續性動作(ActionInterval),
這兩類動作與復合動作配合使用,能得到復雜生動的動作效果。
瞬時動作
瞬時動作是指能立刻完成的動作,是 FiniteTimeAction 中動作持續時間為 0 的特例。
更準確地說,這類動作是在下一幀會立刻執行并完成的動作,如設定位置、設定縮放等。
這些動作原本可以通過簡單地對 Node 賦值完成,但是把它們包裝
為動作后,可以方便地與其他動作類組合為復雜動作。
一些常用的瞬時動作:
Place
該動作用于將節點放置到某個指定位置,其作用與修改節點的 Position 屬性相同。
例如,將精靈放置到屏幕坐標(100, 100) 處,再執行曲線運動 curveMove 的代碼如下:
FiniteTimeAction* placeAction = Place::create(Point(100, 100)); Action* action = Sequence::create(placeAction, curveMove, NULL);
其中 Sequence 又稱為動作序列,是一種復合動作,它在初始化時,會接受多個動作,當它被執行時,這些動作會按順序逐個執行,形成有序的一列動作。
FlipX 和 和 FlipY
這兩個動作分別用于將精靈沿 X 和 Y 軸反向顯示,其作用與設置精靈的 FlipX 和 FlipY 屬性相同。
將其包裝為動作類也是為了便于與其他動作進行組合。
例如,我們想使精靈從屏幕的一端游動到另一端,然后按原路返回。
為了更自然一點,我們設置一個序列,精靈先執行移動的動作,在精靈到達另一端時反向顯示,然后再執行移動回起點的動 作,相關代碼如下:
FiniteTimeAction* flipXAction = FlipX::create(true); Action* action = Sequence::create(curveMove, flipXAction, curveMove->reverse(), NULL);
其中 reverse 的作用是取得原動作的逆動作。在這個例子中,精靈沿 X 軸翻轉后將會沿原路返回起點。
Show 和 和 Hide
這兩個動作分別用于顯示和隱藏節點,其作用與設置節點的 Visible 屬性的作用一樣。
例如,為了使精靈完成運動之后隱藏 起來,我們使用如下代碼:
FiniteTimeAction* hideAction = Hide::create(); Action* action = Sequence::create(curveMove, hideAction, NULL);
CallFunc
CallFunc 系列動作包括 CallFunc、CallFuncN、__CCCallFuncND,以及 __CCCallFuncO四個動作,
用來在動作中進行方法的調用(之所以不是函數調用,是因為它們只能調用某個類中的實例方法,而不能調用普通的 C 函數)。
當某個對象 執行 CallFunc 系列動作時,就會調用一個先前被設置好的方法,以完成某些特別的功能。
在CallFunc 系列動作的 4 個類中:
CallFunc 調用的方法不包含參數,
CallFuncN 調用的方法包含一個 Node*類型的參數, 表示執行動作的對象。
__CCCallFuncND調用的方法包含兩個參數, 不僅有一個節點參數, 還有一個自定義參數 (Node*與 void*)。__CCCallFuncO調用的方法則只包含一個 Ref*類型的參數。
實際上,CallFunc 系列動作的后綴"N"表示 Node 參數,指的是執行動作的對象,
"D"表示 Data 參數,指的是用戶自定義的數據,"O"表示對象,指的是一個用戶自定義的 Ref參數。
在不同的情況下,我們可以根據不同的需求來選擇不同的 CallFunc 動作。
考慮一種情況,我們創建了許多會在屏幕中移動的精靈,希望精靈在移動結束之后就從游戲中刪除。
為了實現這個效果,我們可以創建一系列動作:首先讓精靈移動,然后調用一個 removeSelf(Node* nodeToRemove)方法來刪除 nodeToRemove 對象。在 removeSelf 方法中需要訪問執行此動作的精靈,因此我們就采用 CallFuncN 來調用removeSelf 方法。
持續性動作
持續性動作是在持續的一段時間里逐漸完成的動作,如精靈從一個點連續地移動到另一個點。
與瞬時動作相比,持續性動作的種類更豐富。
由于這些動作將持續一段時間,所以大多數的持續性動作都會帶有一個用于控制動作執行時間的實型參數duration。
每一種持續性動作通常都存在兩個不同的變種動作,分別具有 To 和 By 后綴:
后綴為 To 的動作描述了節點屬性值的絕對變化,例如 MoveTo 將對象移動到一個特定的位置;
而后綴為 By 的動作則描述了屬性值相對的變化,如 MoveBy 將對象移動一段相對位移。
根據作用效果不同,可以將持續性動作劃分為以下 4 大類:
位置變化動作
屬性變化動作
視覺特效動作
控制動作
位置變化動作
針對位置(position)這一屬性,引擎為我們提供了 3 種位置變化動作類型。
MoveTo 和 MoveBy:用于使節點做直線運動。設置了動作時間和終點位置后,節點就會在規定時間內,從當前位置直線移動到設置的終點位置。它們的初始化方法分別為:
static MoveTo* create(float duration, const Point& position); static MoveBy* create(float duration, const Point& deltaPosition);
其中,duration 參數表示動作持續的時間,position 參數表示移動的終點或距離。
對于 MoveTo,節點會被移動到 position對應的 位置;
對于 MoveBy,節點會相對之前的位置移動 position的距離。
JumpTo 和 JumpBy:使節點以一定的軌跡跳躍到指定位置。它們的初始化方法如下:
static JumpTo* create(float duration, const Point& position, float height, int jumps); static JumpBy* create(float duration, const Point& position, float height, int jumps);
其中 position 表示跳躍的終點或距離,height 表示最大高度,jumps 表示跳躍次數。
BezierTo 和 BezierBy: 使節點進行曲線運動, 運動的軌跡由貝塞爾曲線描述。
static BezierTo* create(float t, const ccBezierConfig& c); static BezierBy* create(float t, const ccBezierConfig& c);
貝塞爾曲線是描述任意曲線的有力工具,
在許多軟件(如 Adobe Photoshop)中,鋼筆工具就是貝塞爾曲線的應用。
每一條貝塞爾曲線都包含一個起點和一個終點。
在一條曲線中,起點和終點都各自包含一個控制點,而控制點到端點的連線稱作控制線。
控制線決定了從端點發出的曲線的形狀,包含角度和長度兩個參數:角度決定了它所控制的曲線的方向,
即這段曲線在這一控制點的切線方向;長度控制曲線的曲率。控制線越長,它所控制的曲線離控制線越近。
使用時我們要先創建 ccBezierConfig 結構體, 設置好終點 endPosition 以及兩個控制點controlPoint_1 和controlPoint_2后,再把結構體傳入 BezierTo 或 BezierBy 的初始化方法中:
ccBezierConfig bezier; bezier.controlPoint_1 = Point(20, 150); bezier.controlPoint_2 = Point(200, 30); bezier.endPosition = Point(160, 30); FiniteTimeAction * beizerAction = BezierTo::create(actualDuration / 4, bezier);
屬性變化動作
屬性變化動作的特點是通過屬性值的逐漸變化來實現動畫效果。
例如, 下面要介紹的第一個動作 ScaleTo, 它會在一段時間內不斷地改變游戲元素的 scale 屬性,
使屬性值平滑地變化到一個新值,從而使游戲元素產生縮放的動畫 效果。
ScaleTo 和 ScaleBy:產生縮放效果,使節點的縮放系數隨時間線性變化。對應的初始化方法為:
static ScaleTo* create(float duration, float s); static ScaleBy* create(float duration, float s);
其中,s 為縮放系數的最終值或變化量。
RotateTo 和 RotateBy:產生旋轉效果。對應的初始化方法為:
static RotateTo* create(float duration, float deltaAngle); static RotateBy* create(float duration, float deltaAngle);
其中 deltaAngle的單位是角度,正方向為順時針方向。
FadeIn 和 FadeOut:產生淡入淡出效果,其中前者實現了淡入效果,后者實現了淡出效果。對應的初始化方法為:
static FadeIn* create(float d); static FadeOut* create(float d);
以下介紹的幾個類似的動作。
FadeTo:用于設置一段時間內透明度的變化效果。其初始化方法為:
static FadeTo* create(float duration, GLubyte opacity);
參數中的 Glubyte 是 8 位無符號整數,因此,opacity 可取 0 至 255 中的任意整數。
與透明度相關的動作只能應用在精靈(Sprite)上,且子節點不會受到父節點的影響。
TintTo 和 TintBy:設置色調變化。這個動作較為少用,其初始化方法為:
static TintTo* create(float duration, GLubyte red, GLubyte green, GLubyte blue); static TintBy* create(float duration, GLshort deltaRed, GLshort deltaGreen, GLshort deltaBlue);
與 FadeTo 類似,red、green 和 blue的取值范圍也為 0~255。
視覺特效動作
用于實現一些特殊的視覺效果
Blink:使目標節點閃爍。其初始化方法為:
static Blink* create(float duration, int blinks);
其中,blinks是閃爍次數。
Animation:播放幀動畫,用幀動畫的形式實現動畫效果。
控制動作
控制動作是一類特殊的動作,用于對一些列動作進行精細控制。
利用這一類動作可以實現一些實用的功能,因此它們是十分常用的。
這類動作包括 DelayTime、 Repeat 和RepeatForever 等。
DelayTime可以將動作延時一定的時間,
Repeat可以把現有的動作重復一定次數,
RepeateForever可以使一個動作不斷重復下去。
事實上,控制動作與復合動作息息相關。
復合動作
簡單動作顯然不足以滿足游戲開發的要求, 在這些動作的基礎上,
Cocos2d-x 為我們提供了一套動作的復合機制,允許我們組合各種基本動作,產生更為復雜和生動的動作效果。
復合動作是一類特殊的動作,因此它也需要使用 Node 的runAction 方法執行。
而它的特殊之處在于,作為動作容器,復合動作可以把許多動作組合成一個復雜的動作。
因此,我們通常會使用一個或多個動作來創建復合動作,再把動作交給節點執行。
復合動作十分靈活,這是由于復合動作本身也是動作,因此也可以作為一個普通的動作嵌套在其他復合動作中。
重復( Repeat/RepeatForever )
有的情況下,動作只需要執行一次即可,但我們還常常遇到一個動作反復執行的情況。
對于一些重復的動作,我們可以通過 Repeat 與 RepeatForever 這兩個方式重復執行:
static Repeat* create(FiniteTimeAction *action, unsigned int times); static RepeatForever* create(ActionInterval *action);
其中,action參數表示需要重復的動作,第一個方法允許指定動作的重復次數,第二個方法使節點一直重復該動
作直到動作被停止。
并列( Spawn )
指的是使一批動作同時執行。Spawn 從 ActionInterval 派生而來的,它提供了兩個工廠方法:
static Spawn* create(FiniteTimeAction *action1, ...) CC_REQUIRES_NULL_TERMINATION; static Spawn* createWithTwoActions(FiniteTimeAction *action1, FiniteTimeAction *action2);
其中第一個靜態方法可以將多個動作同時并列執行,參數表中最后一個動作后需要緊跟 NULL 表示結束。第二個則只能指定兩個動作復合, 不需要在最后一個動作后緊跟 NULL。
此外, 執行的動作必須是能夠同時執行的、 繼承自 FiniteTimeAction的動作。
組合后,Spawn 動作的最終完成時間由其成員中最大執行時間的動作來決定。
序列( Sequence )
除了讓動作同時并列執行,我們更常遇到的情況是順序執行一系列動作。
Sequence 提供了一個動作隊列,它會順序執行一系列動作。
Sequence 同樣派生自 ActionInterval。
與 Spawn 一樣,Squence 也提供了兩個工廠方法:
static Sequence* create(FiniteTimeAction *action1, ...) CC_REQUIRES_NULL_TERMINATION; static Sequence* createWithTwoActions(FiniteTimeAction *actionOne, FiniteTimeAction *actionTwo);
它們的作用分別是建立多個和兩個動作的順序執行的動作序列。
同樣要注意復合動作的使用條件,部分的非延時動作(如RepeatForever)并不被支持。
在實現 Sequence 和 Spawn 兩個組合動作類時,有一個非常有趣的細節:
成員變量中并沒有定義一個可變長的容器來容納每一個動作系列, 而是定義了m_pOne和m_pTwo兩個動作成員變量。 如果我們創建了兩個動作的組合,
那么m_pOne與m_pTwo就分別是這兩個動作本身;
當我們創建更多動作的組合時,引擎會把動作分解為兩部分來看待,
其中后一部分只包含最后一個動作,而前一部分包含它之前的所有動作,
引擎把 m_pTwo 設置為后一部分的動作,把 m_pOne 設置為其余所有動作的組合。
例如,
語句
sequence = Sequence::create(action1, action2, action3, action4, NULL);
就等價于:
Sequence s1 = Sequence::createWithTwoActions(action1, action2); Sequence s2 = Sequence::createWithTwoActions(s1, action3); sequence = Sequence::createWithTwoActions(s2, action4);
Spawn 與 Sequence 所采用的機制類似,在此就不再贅述了。
采用這種遞歸的方式,而不是直接使用容器來定義組合動作,實際上為編程帶來了極大的便利。
維護多個動作的組合是一個復雜的問題,現在我們只需要考慮兩個動作組合的情況就可以了。
下面是 Spawn 的一個初始化方法,就是利用遞歸的思想簡化了編程的復雜度:
Spawn* Spawn::create(const Vector<FiniteTimeAction*>& arrayOfActions) { Spawn* ret = nullptr; do { auto count = arrayOfActions.size(); CC_BREAK_IF(count == 0); auto prev = arrayOfActions.at(0); if (count > 1) { for (int i = 1; i < arrayOfActions.size(); ++i) { prev = createWithTwoActions(prev, arrayOfActions.at(i)); } } else { // If only one action is added to Spawn, make up a Spawn by adding a simplest finite time action. prev = createWithTwoActions(prev, ExtraAction::create()); } ret = static_cast<Spawn*>(prev); }while (0); return ret; }
眾所周知,遞歸往往會犧牲一些效率,但能換來代碼的簡潔。
在這兩個復合動作中,細節處理得十分優雅,
所有的操作都只需要針對兩個動作實施,多個動作的組合會被自動變換為遞歸
void Spawn::update(float time) { if (_one) { _one->update(time); } if (_two) { _two->update(time); } } Spawn* Spawn::reverse() const { return Spawn::createWithTwoActions(_one->reverse(), _two->reverse()); }
延時( DelayTime )
DelayTime 是一個"什么都不做"的動作,類似于音樂中的休止符,
用來表示動作序列里一段空白期,通過占位的方式將不同的動作段串接在一起。
實際上,這與一個定時期實現的延遲沒有區別,
但相比之下,使用 DelayTime 動作來延時就可以方便地利用動作序列把一套動作連接在一起。
DelayTime 只提供了一個工程方法,如下所示:
static DelayTime* create(float d);
其中僅包含一個實型參數d,表示動作占用的時間。
變速動作
大部分動作的變化過程是與時間成線性關系的,即一個動作經過相同時間產生的變化相同,
例如,MoveBy 會使節點在同樣長的時間內經過同樣的位移。
這是因為 Cocos2d-x 把動作的速度變化控制抽離了出來,形成一個獨立的機制。
普通動作配合 變速動作,可以構造出很有趣的動作效果。
與復合動作類似,變速動作也是一種特殊的動作,它可以把任何一種動作按照改變后的速度執行。
因此,在初始化變速動作時,需要傳入一個動作。
變速動作包括 Speed 動作與 Ease 系列動作,下面來詳細介紹這些動作。
Speed
Speed 用于線性地改變某個動作的速度,因此,可以實現成倍地快放或慢放功能。
static Speed* create(ActionInterval* action, float speed);
為了改變一個動作的速度,首先需要將
目標動作包裝到 Speed 動作中:
RepeatForever* repeat = RepeatForever::create(animation); Speed* speed = Speed::create(repeat, 1.0f); speed->setTag(action_speed_tag); sprite->runAction(speed);
在上面的代碼中, 我們創建了一個 animation 動作的 CCRepeatForever 復合動作 repeat, 使動畫被不斷地重復執行。 然后,我們又使用 repeat 動作創建了一個 CCSpeed 變速動作。
create 初始化方法中的兩個參數分別為目標動作與變速比率。
設置變速比率為 1,目標動作的速度將不會改變。
最后,我們為 speed 動作設置了一個 tag 屬性,并把動作交給精靈,讓精靈執行變速動作。
此處設置的 tag 屬性與 Node 的 tag 屬性類似,用于從節點中方便地查找動作。
接下來,在需要改變速度的地方,我們通過修改變速動作的 speed 屬性來改變動作速度。
下面的代碼將會把上面設置的動畫速度變為原來的兩倍:
Speed * speed = sprite->getActionByTag(action_speed_tag); speed->setSpeed(2.0f);
ActionEase
雖然使用 Speed 能夠改變動作的速度,然而它只能按比例改變目標動作的速度。
如果我們要實現動作由快到慢、速度隨時間改變的變速運動,
需要不停地修改它的speed屬性才能實現, 顯然這是一個很煩瑣的方法。
下面將要介紹的ActionEase 系列動作通過使用內置的多種自動速度變化來解決這一問題。
ActionEase 系列包含 15 個動作,
它們可以被概括為 5 類動作:指數緩沖、Sine 緩沖、彈性緩沖、跳躍緩沖和回震緩沖。
每一類動作都有 3 個不同時期的變換:In、Out 和 InOut。
下面使用時間變換圖像表示每組 ActionEase 動作的作用效果,
其中橫坐標表示實際動畫時間,縱坐標表示變換后的動畫時間。
因此,線性動作的圖像應該是一條自左下角到右上角的直線。
ActionEase 的使用方法與 Speed 類似。以 Sine 緩沖為例,以下代碼實現了 InSine 變速運動:
EaseSineIn* sineIn = EaseSineIn::create(action); sineIn->setTag(action_sine_in_tag); sprite->runAction(sineIn);
創建自定義動作
為了抽象出獨立的旋轉跟蹤動作,根據精靈的移動路徑設置合適的旋轉角度。
Action 包含兩個重要的方法:step 與 update。
step 方法會在每一幀動作更新時觸發,該方法接受一個表示調用時間間隔的參數 dt,dt 的積累即為動作運行的總時間。
引擎利用積累時間來計算動作運行的進度(一個從 0 到 1 的實數),并調用 update 方法更新動作。
update 方法是 Action 的核心,它由 step 方法調用,接受一個表示動作進度的參數,
每一個動作都需要利用進度值改變目標節點的屬性或執行其他指令。
自定義動作只需要從這兩個方法入手即可,我們通常只需要修改 update 方法就可以實現簡單的動作。
Action的step和update方法定義:
//! called every frame with it's delta time. DON'T override unless you know what you are doing. virtual void step(float dt); /** called once per frame. time a value between 0 and 1 For example: - 0 means that the action just started - 0.5 means that the action is in the middle - 1 means that the action is over */ virtual void update(float time);
讓動作更平滑流暢
如何讓動作看起來更加自然并優雅,實際上,這是一個涉及玩家注意力的問題。
對于新出現的變化效果,玩家需要時間轉移注意力適應這個變化,
而后如果效果持續穩定、變化不明顯,則會降低玩家的注意力,使玩家感覺疲憊。
在這種情況下,一個冗長的勻速動作效果就會造成游戲不自然不優雅。
不妨為動作添加一些變速效果,將玩家有限的注意力集中到我們希望玩家關注的效果上。
進場動作:由快到慢,快速進入后緩慢停下,在停止前給玩家足夠的視覺時間分辨清楚進入的圖像。
出場動作:先慢后快,展示了出場趨勢和方向后快速移出屏幕,不拖泥帶水。
這個變速效果就很自然地交給前面提到的 Ease 系列動作實現了。
針對具體的需求,我們選擇了 EaseExponential 動作來實現變速效果。
以暫停游戲時彈出的菜單為例:
點擊暫停游戲后,菜單從屏幕頂端向下滑出;
點擊恢復游戲后,菜單向上收起。
彈出菜單的代碼如下:
Menu* menu = Menu::create(item0, item1, item2, item3, NULL); menu->alignItemsVerticallyWithPading(5.0f); menu->setPosition(ccp(size.width/2, size.height)); menu->setTag(menu_pause_tag); this->addChild(menu, 5); MoveTo* move = MoveTo::create(0.5f, Point(size.width/2, size.height/2)); Action* action = EaseExponentialOut::create(move); menu->runAction(action);
收起菜單的代碼如下:
Size size = Director::getInstance()->getWinSize(); Menu* menu = (Menu*)this->getChildByTag(menu_pause_tag); Point point = Point (size.width/2, size.height + menu->getContentSize().height/2); MoveTo* move = MoveTo::create(0.5f, point); Action* action = EaseExponentialIn::create(move); menu->runAction(action);
郝萌主友情提示:
優雅自然的動作,能加強游戲的表現性,能吸引更多的玩家、、、