前面的話
cocos 動畫系統支持任意組件屬性和用戶自定義屬性的驅動,再加上可任意編輯的時間曲線和移動軌跡編輯功能,就可以制作出各種動態效果
概述
Animation 組件可以以動畫方式驅動所在節點和子節點上的節點和組件屬性,包括用戶自定義腳本中的屬性
點擊屬性檢查器下面的添加按鈕,然后從添加其他組件中選擇Animation,即可添加 Animation 組件到節點上
【屬性】
Default Cilp: 默認的動畫剪輯,如果這一項設置了值,並且 Play On Load 為 true,那么動畫會在加載完成后自動播放 Default Clip 的內容
Clips: 列表類型,默認為空,在這里面添加的 AnimationClip 會反映到動畫編輯器中,用戶可以在動畫編輯器里編輯 Clips 的內容
Play On Load: 布爾類型,是否在動畫加載完成后自動播放 Default Clip 的內容
CLIP
cocos Animation是節點上的一個組件,clip 動畫剪輯是一份動畫的聲明數據,將它掛載在 Animation 組件上,就可以將這份動畫數據應用到節點上
數據中索引節點的方式是以掛載 Animation 組件的節點為根節點的相對路徑,所以在同一個父節點下的同名節點,只能夠產生一份動畫數據,並且只能應用到第一個同名節點上
【參數】
sample: 定義當前動畫數據每秒的幀率,默認為60,這個參數會影響時間軸上每兩個整數秒刻度之間的幀數量,也就是兩秒之內有多少格
speed: 當前動畫的播放速度,默認為1
duration: 當動畫播放速度為1時,動畫的持續時間
real time: 動畫從開始播放到結束,真正持續的時間
wrap mode: 循環模式
【動態創建】
var animation = this.node.getComponent(cc.Animation); // frames 這是一個 SpriteFrame 的數組. var clip = cc.AnimationClip.createWithSpriteFrames(frames, 17); clip.name = "anim_run"; clip.wrapMode = cc.WrapMode.Loop; // 添加幀事件 clip.events.push({ frame: 1, // 准確的時間,以秒為單位。這里表示將在動畫播放到 1s 時觸發事件 func: "frameEvent", // 回調函數名稱 params: [1, "hello"] // 回調參數 }); animation.addClip(clip); animation.play('anim_run');
動畫編輯器
動畫在普通模式下是不允許編輯的,只有在動畫編輯模式下,才能夠編輯動畫文件,但是在編輯模式下,無法對節點進行 增加/刪除/改名操作
動畫編輯器一共可以划分為 6 個主要部分
1、常用按鈕區域,這里負責顯示一些常用功能按鈕,從左到右依次是:開關錄制狀態、返回第一幀、上一幀、播放/暫停、下一幀、新建動畫剪輯、插入動畫事件
2、時間軸與事件,這里主要是顯示時間軸,添加的自定義事件也會在這里顯示
3、層級管理(節點樹),當前動畫剪輯可以影響到的節點數據
4、節點內關鍵幀的預覽區域,這里主要是顯示各個節點上的所有幀的預覽時間軸
5、屬性列表,顯示當前選中的節點在選中的動畫剪輯中已經包含了的屬性列表
6、關鍵幀,每個屬性相對應的幀都會顯示在這里
【時間軸】
時間軸上刻度的表示法是 01-05,該數值由兩部分組成,冒號前面的是表示當前秒數,冒號后面的表示在當前這一秒里的第幾幀
01-05 表示該刻度在時間軸上位於從動畫開始經過了 1 秒又 5 幀的時間
因為幀率可以隨時調整,因此同一個刻度表示的時間點也會隨着幀率變化而有所不同
當幀率為 30 時,01-05 表示動畫開始后 1 + 5/30 = 1.1666 秒
當幀率為 10 時,01-05 表示動畫開始后 1 + 5/10 = 1.5 秒
雖然當前刻度表示的時間點會隨着幀率變化,但一旦在一個位置添加了關鍵幀,該關鍵幀所在的總幀數是不會改變的,假如我們在幀率 30 時向 01-05 刻度上添加了關鍵幀,該關鍵幀位於動畫開始后總第 35 幀。之后把幀率修改為 10,該關鍵幀仍然處在動畫開始后第 35 幀,而此時關鍵幀所在位置的刻度讀數為 03-05,換算成時間以后正好是之前的 3 倍
【基本操作】
更改時間軸縮放比例:滾動鼠標滾輪,可以放大,或者縮小時間軸的顯示比例 移動顯示區域: 按下鼠標右鍵拖拽 更改當前選中的時間軸節點: 在時間軸區域內點擊任意位置或拖拽,或者在上圖 4 區域拖拽標示的紅線 修改 clip 屬性: 在插件底部,修改對應屬性,在輸入框失去焦點時會更新到實際的 clip 數據中
【快捷鍵】
left 向前移動一幀,如果已經在第 0 幀,則忽略當前操作 right 向后移動一幀 delete 刪除當前選中的關鍵幀 k 正向播放動畫,抬起后停止 j 反向插話動畫,抬起后停止 cmd + left 跳轉到第 0 幀 cmd + right 跳轉到有效的最后一幀
動畫剪輯
【創建 Animation 組件】
如果要在節點上創建動畫,必須為它新建一個 Animation 組件,創建的方法有兩種:
1、選中相應的節點,在屬性檢查器中點擊右上方的 +,或者下方的 添加組件, 在其他組件中選擇 Animation
2、打開動畫編輯器,然后在層級管理器中選中需要添加動畫的節點,在動畫編輯器中點擊 添加Animation組件 按鈕
【創建與掛載動畫剪輯】
動畫剪輯有兩種創建方式:
1、在資源管理器中點擊左上方的 +,或者右鍵空白區域,選擇 Animation Clip,這時會在管理器中創建一個名為 'New AnimationClip'的剪輯文件。在層級管理器中點選剛剛的節點,在屬性檢查器中找到 Animation,這時的 Clips 顯示的是 0,將它改成1,然后將剛剛在資源管理器中創建的'New AnimationClip',拖入剛剛出現的 animation-clip 選擇框內
2、如果 Animation 組件中還沒有添加動畫剪輯文件,則可以在動畫編輯器中直接點擊 新建 AnimationClip 按鈕,根據彈出的窗口創建一個新的動畫剪輯文件。要注意的是,如果選擇覆蓋已有的剪輯文件,被覆蓋的文件內容會被清空
【數據剪輯】
一個動畫剪輯內可能包含了多個節點,每一個節點上掛在多個動畫屬性,每個屬性內的數據才是實際的關鍵幀
動畫剪輯通過節點的名字定義數據的位置,本身忽略了根節點,其余的子節點通過與根節點的相對路徑索引找到對應的數據
如果在制作完成動畫后,將節點重命名,會造成動畫數據出現問題,如下圖所示
這時,要手動指定數據對應的節點,可以將鼠標移入節點,點擊節點右側出現的更多按鈕,並選擇“移動數據”
如上圖所示,New Node/test 節點沒有數據,想將 /New Node/efx_flare 上的數據移到這里
1、鼠標移動丟失的節點 /New Node/efx_flare 上
2、點擊右側出現的按鈕
3、選擇移動數據
4、將路徑改為 /New Node/test,並回車
編輯動畫序列
動畫屬性包括了節點自有的 position、rotation 等屬性,也包含了組件 Component 中自定義的屬性。組件包含的屬性會加上組件的名字,比如 cc.Sprite.spriteFrame
【添加新的屬性軌道】
先選中節點,然后在屬性區域右上角點擊 +。彈出菜單中,選中想要添加的屬性,就會對應新增一個軌道
【刪除一個屬性軌道】
將鼠標焦點移動到要刪除的屬性軌道上,右邊會顯示一個“三道杠”按鈕,點擊按鈕,在彈出菜單中選擇刪除屬性,選中后對應的屬性就會從動畫數據中刪除
【添加關鍵幀】
在屬性列表中點擊對應屬性軌道右側的“三道杠”按鈕,在彈出的菜單中選擇 插入關鍵幀 按鈕
【選擇關鍵幀】
點擊創建的關鍵幀后,關鍵幀會呈現選中狀態,此時關鍵幀由藍變白,如果需要多選,可以按住 ctrl 再次選擇其他關鍵幀,或者直接在屬性區域拖拽框選擇
【移動關鍵幀】
將鼠標移動到任意一個被選中的關鍵幀上,按下鼠標左鍵,鼠標會變換成左右箭頭,這時就可以拖拽所有被選中的節點了
【更改關鍵幀】
在時間軸上需要修改的關鍵幀,直接在屬性檢查器內修改相對應的屬性即可(要確保動畫編輯器處於編輯狀態)。例如,屬性列表中 position、x、y 三個屬性軌道,選中關鍵幀后,可以修改屬性檢查器中的 position、x、y 屬性
或者在時間軸上選擇一個沒有關鍵幀的位置,然后在屬性檢查器中修改相對應的屬性,便會自動插入一幀
【刪除關鍵幀】
選中關鍵幀后,點擊對應屬性軌道的“三道杠”按鈕,選擇刪除選中幀,或者直接按下鍵盤上的 delete 按鍵,則所有被選中的節點都會被刪除
【復制關鍵幀】
在動畫編輯器內選中關鍵幀后,可以按下 cmd + c 復制當前的關鍵幀,然后選中某一個時間軸上的點,按下 cmd + v 將剛剛復制的關鍵幀粘貼到選中的時間點上
【節點操作】
動畫是按照節點的名字來進行索引關聯的,有時會在層級管理器內改變節點的層級關系,而動畫編輯器內的動畫就會找不到當初指定對應的節點
這時我們需要手動更改一下動畫上節點的搜索路徑:
1、鼠標移動到要遷移的節點上,點擊右側出現的菜單按鈕
2、選擇移動節點數據
3、修改節點的路徑數據
編輯序列幀動畫
下面來看具體怎么創建一個幀動畫
1、首先需要讓節點正常顯示紋理,為節點增加 Sprite 組件,選中節點后在屬性檢查器中通過 添加組件 按鈕,選擇 添加渲染組件 -> Sprite
2、節點可以正常顯示紋理后,還需要為紋理創建一個動畫軌道。在動畫編輯器中點擊 add Property,然后選擇 cc.Sprite.spriteFrame
3、從資源管理器中,將紋理拖拽到屬性幀區域,放在 cc.Sprite.spriteFrame 軌道上,再將下一幀需要顯示的紋理拖到指定位置,然后點擊播放就可以預覽剛剛創建的動畫了
編輯時間曲線
有時,我們需要在兩幀之間實現 EaseInOut 等緩動效果,需要在一條軌道上創建兩個不相等的幀,比如在 position 上創建兩幀,從 0,0 到 100,100,這里兩幀之間會出現一根連接線(連接兩關鍵幀之間的藍色線段),雙擊連接線,則可以打開時間曲線編輯器
在曲線編輯器左側可以選擇預設的各種效果,比如 EaseIn等,選中后右側上方還會出現一些預設的參數,可以根據需求選擇
當然,也可以自己修改曲線,右側預覽圖內,有兩個灰色的控制點,拖拽控制點可以更改曲線的軌跡。如果控制點需要拖出視野外,則可以使用鼠標滾輪或右上角的小比例尺縮放預覽圖,支持的比例從 0.1 到 1
添加動畫事件
【添加事件】
首先選中某個位置,然后點擊按鈕區域最右側的按鈕,這時在時間軸上會出現一個白色的小塊,這就是添加的事件
【刪除事件】
雙擊剛剛出現的白色小塊,打開事件編輯器后點擊 function 后面的回收圖標,會提示是否刪除這個 event,點擊確認則刪除。也可以在動畫編輯器中右鍵點擊 event,選擇 刪除
【設置事件】
雙擊剛剛出現的白色小塊,打開事件編輯器,在編輯器內,可以手動輸入需要觸發的 funtion 名字,觸發時會根據這個函數名,去各個組件內匹配相應的方法
如果需要添加傳入的參數,則在 Params 旁點擊 + 或者 -,只支持 Boolean、String、Number 三種類型的參數
腳本控制Animation
Animation 組件提供了一些常用的動畫控制函數,如果只是需要簡單的控制動畫,可以通過獲取節點的 Animation 組件來做一些操作
【播放】
var anim = this.getComponent(cc.Animation); // 如果沒有指定播放哪個動畫,並且有設置 defaultClip 的話,則會播放 defaultClip 動畫 anim.play(); // 指定播放 test 動畫 anim.play('test'); // 指定從 1s 開始播放 test 動畫 anim.play('test', 1); // 使用 play 接口播放一個動畫時,如果還有其他的動畫正在播放,則會先停止其他動畫 anim.play('test2');
Animation 對一個動畫進行播放的時候會判斷這個動畫之前的播放狀態來進行下一步操作
如果動畫處於 停止 狀態,則 Animation 會直接重新播放這個動畫
如果動畫處於 暫停 狀態,則 Animation 會恢復動畫的播放,並從當前時間繼續播放下去
如果動畫處於 播放 狀態,則 Animation 會先停止這個動畫,再重新播放動畫
【播放多個】
Animation 支持同時播放多個動畫,播放不同的動畫並不會影響其他動畫的播放狀態,這對於做一些復合動畫有幫助
var anim = this.getComponent(cc.Animation); // 播放第一個動畫 anim.playAdditive('position-anim'); // 播放第二個動畫 // 使用 playAdditive 播放動畫時,不會停止其他動畫的播放。如果還有其他動畫正在播放,則同時會有多個動畫進行播放 anim.playAdditive('rotation-anim');
【暫停和停止】
var anim = this.getComponent(cc.Animation); anim.play('test'); // 指定暫停 test 動畫 anim.pause('test'); // 暫停所有動畫 // anim.pause(); // 指定恢復 test 動畫 anim.resume('test'); // 恢復所有動畫 // anim.resume(); // 指定停止 test 動畫 anim.stop('test'); // 停止所有動畫 // anim.stop();
【設置動畫當前時間】
可以在任何時候對動畫設置當前時間,但是動畫不會立刻根據設置的時間進行狀態的更改,需要在下一個動畫的 update 中才會根據這個時間重新計算播放狀態
var anim = this.getComponent(cc.Animation); anim.play('test'); // 設置 test 動畫的當前播放時間為 1s anim.setCurrentTime(1, 'test'); // 設置所有動畫的當前播放時間為 1s // anim.setCurrentTime(1);
AnimationState
Animation 只提供了一些簡單的控制函數,希望得到更多的動畫信息和控制的話,需要使用到 AnimationState
如果說 AnimationClip 作為動畫數據的承載,那么 AnimationState 則是 AnimationClip 在運行時的實例,它將動畫數據解析為方便程序中做計算的數值。 Animation 在播放一個 AnimationClip 的時候,會將 AnimationClip 解析成 AnimationState。 Animation 的播放狀態實際都是由 AnimationState 來計算的,包括動畫是否循環,怎么循環,播放速度等
【獲取 AnimationState】
var anim = this.getComponent(cc.Animation); // play 會返回關聯的 AnimationState var animState = anim.play('test'); // 或是直接獲取 var animState = anim.getAnimationState('test');
【獲取動畫信息】
var anim = this.getComponent(cc.Animation); var animState = anim.play('test'); // 獲取動畫關聯的clip var clip = animState.clip; // 獲取動畫的名字 var name = animState.name; // 獲取動畫的播放速度 var speed = animState.speed; // 獲取動畫的播放總時長 var duration = animState.duration; // 獲取動畫的播放時間 var time = animState.time; // 獲取動畫的重復次數 var repeatCount = animState.repeatCount; // 獲取動畫的循環模式 var wrapMode = animState.wrapMode // 獲取動畫是否正在播放 var playing = animState.isPlaying; // 獲取動畫是否已經暫停 var paused = animState.isPaused; // 獲取動畫的幀率 var frameRate = animState.frameRate;
【設置動畫播放速度】
speed 值越大,速度越快,值越小則速度越慢
var anim = this.getComponent(cc.Animation); var animState = anim.play('test'); // 使動畫播放速度加速 animState.speed = 2; // 使動畫播放速度減速 animState.speed = 0.5;
【設置動畫循環模式和循環次數】
AnimationState 允許動態設置循環模式,目前提供了多種循環模式,這些循環模式可以從 cc.WrapMode中獲取到。如果動畫的循環類型為 Loop 類型的話,需要與 repeatCount 配合使用才能達到效果。 默認在解析動畫剪輯的時候,如果動畫循環類型為 Loop 類型,repeatCount 將被設置為 Infinity,即無限循環;如果動畫循環類型為 Normal 類型,repeatCount 將被設置為 1
var anim = this.getComponent(cc.Animation); var animState = anim.play('test'); // 設置循環模式為 Normal animState.wrapMode = cc.WrapMode.Normal; // 設置循環模式為 Loop animState.wrapMode = cc.WrapMode.Loop; // 設置動畫循環次數為2次 animState.repeatCount = 2; // 設置動畫循環次數為無限次 animState.repeatCount = Infinity;
動畫事件
動畫事件的回調其實就是一個普通的函數,在動畫編輯器里添加的幀事件會映射到動畫根節點的組件上
假設在動畫的結尾添加了一個幀事件,如下圖
那么,在腳本中可以這么寫:
cc.Class({ extends: cc.Component, onAnimCompleted: function (num, string) { console.log('onAnimCompleted: param1[%s], param2[%s]', num, string); } });
將上面的組件加到動畫的 根節點 上,當動畫播放到結尾時,動畫系統會自動調用腳本中的 onAnimCompleted 函數。 動畫系統會搜索動畫根節點中的所有組件,如果組件中有實現動畫事件中指定的函數的話,就會對它進行調用,並傳入事件中填的參數
要特別注意的是,該腳本必須綁定到 node 節點上,否則腳本中的函數將不會被執行
【注冊事件回調】
除了動畫編輯器中的幀事件提供了回調外,動畫系統還提供了動態注冊回調事件的方式
目前支持的回調事件有:
play: 開始播放時
stop: 停止播放時
pause: 暫停播放時
resume: 恢復播放時
lastframe: 假如動畫循環次數大於1,當動畫播放到最后一幀時
finished: 動畫播放完成時
當在 cc.Animation 注冊了一個回調函數后,它會在播放一個動畫時,對相應的 cc.AnimationState 注冊這個回調,在 cc.AnimationState 停止播放時,對 cc.AnimationState 取消注冊這個回調
cc.AnimationState 其實才是動畫回調的發送方,如果希望對單個 cc.AnimationState 注冊回調的話,那么可以獲取到這個 cc.AnimationState 再單獨對它進行注冊
var animation = this.node.getComponent(cc.Animation); // 注冊 animation.on('play', this.onPlay, this); animation.on('stop', this.onStop, this); animation.on('lastframe', this.onLastFrame, this); animation.on('finished', this.onFinished, this); animation.on('pause', this.onPause, this); animation.on('resume', this.onResume, this); // 取消注冊 animation.off('play', this.onPlay, this); animation.off('stop', this.onStop, this); animation.off('lastframe', this.onLastFrame, this); animation.off('finished', this.onFinished, this); animation.off('pause', this.onPause, this); animation.off('resume', this.onResume, this); // 對單個 cc.AnimationState 注冊回調 var anim1 = animation.getAnimationState('anim1'); anim1.on('lastframe', this.onLastFrame, this);
