一座硕、AnimationPlayerBase extends Sprite
動畫播放基類弛作,提供了基礎(chǔ)的動畫播放控制方法和幀標(biāo)簽事件相關(guān)功能』遥可以繼承此類映琳,但不要直接實(shí)例化此類,因?yàn)橛行┓椒ㄐ枰勺宇悓?shí)現(xiàn)蜘拉。
/**
* <p>開始播放動畫萨西。play(...)方法被設(shè)計(jì)為在創(chuàng)建實(shí)例后的任何時(shí)候都可以被調(diào)用,
* 當(dāng)相應(yīng)的資源加載完畢旭旭、調(diào)用動畫幀填充方法(set frames)或者將實(shí)例顯示在舞臺上時(shí)谎脯,
* 會判斷是否正在播放中,如果是持寄,則進(jìn)行播放穿肄。</p>
* <p>配合wrapMode屬性,可設(shè)置動畫播放順序類型际看。</p>
* @param start (可選)指定動畫播放開始的索引(int)或幀標(biāo)簽(String)。
* 幀標(biāo)簽可以通過addLabel(...)和removeLabel(...)進(jìn)行添加和刪除矢否。
* @param loop (可選)是否循環(huán)播放仲闽。
* @param name (可選)動畫名稱。
* @param showWarn(可選)是否動畫不存在時(shí)打印警告
*/
public function play(start:* = 0, loop:Boolean = true,
name:String = "",showWarn:Boolean=true):void {
this._isPlaying = true;
this.index = (start is String) ? _getFrameByLabel(start) : start;
this.loop = loop;
this._actionName = name;
_isReverse = wrapMode == WRAP_REVERSE;
if (this.interval > 0) {
timerLoop(this.interval, this, _frameLoop, null, true, true);
}
}
可以看到interval控制了幀率僵朗,再看看_frameLoop方法赖欣,首先判斷_isReverse和wrapMode,也就是播放模式验庙,最后執(zhí)行這一句this.index = this._index;
public function set index(value:int):void {
_index = value;
_displayToIndex(value);
if (_labels && _labels[value]) {
var tArr:Array = _labels[value];
for (var i:int = 0, len:int = tArr.length; i < len; i++) {
event(Event.LABEL, tArr[i]);
}
}
}
/**
* @private
* 顯示到某幀
* @param value 幀索引
*/
protected function _displayToIndex(value:int):void {
}
處理了Event.LABEL幀事件顶吮,并且把具體如何顯示幀,留給了子類去實(shí)現(xiàn)_displayToIndex方法粪薛。至于幀事件悴了,可以看一下這個(gè)方法:
/**
* 增加一個(gè)幀標(biāo)簽到指定索引的幀上。當(dāng)動畫播放到此索引的幀時(shí)
* 會派發(fā)Event.LABEL事件违寿,派發(fā)事件是在完成當(dāng)前幀畫面更新之后湃交。
* @param label 幀標(biāo)簽名稱
* @param index 幀索引
*/
public function addLabel(label:String, index:int):void {
if (!_labels) _labels = {};
if (!_labels[index]) _labels[index] = [];
_labels[index].push(label);
}
_labels是一個(gè)Object,它的key是一個(gè)數(shù)字藤巢,也就是幀索引(注意幀索引搞莺,是從0開始的)。它的value是一個(gè)數(shù)組掂咒,會把幀標(biāo)簽名稱都存到這個(gè)數(shù)組中才沧。從上面的set index方法中也能看到迈喉,進(jìn)入某一幀時(shí),會判斷_labels相應(yīng)的幀標(biāo)簽數(shù)組温圆,派發(fā)里面所有名稱的事件挨摸。
二、Animation extends AnimationPlayerBase
1.首先去看如何顯示幀_displayToIndex
/**@private */
override protected function _displayToIndex(value:int):void {
if (this._frames) this.graphics = this._frames[value];
}
官方對_frames的解釋是捌木,_frames是當(dāng)前動畫的幀圖像數(shù)組油坝。本類中,每個(gè)幀圖像是一個(gè)Graphics對象刨裆,而動畫播放就是定時(shí)切換Graphics對象的過程澈圈。可以看到_displayToIndex方法確實(shí)是在切換graphics對象帆啃。
2.動畫緩存池framesMap
public static var framesMap:Object = {};
動畫模版緩存池是以一定的內(nèi)存開銷來節(jié)省CPU開銷瞬女,當(dāng)相同的動畫模版被多次使用時(shí),相比于每次都創(chuàng)建新的動畫模版努潘,使用動畫模版緩存池诽偷,只需創(chuàng)建一次,緩存之后多次復(fù)用疯坤,從而節(jié)省了動畫模版創(chuàng)建的開銷报慕。
動畫模版緩存池,以key-value鍵值對存儲压怠,key可以自定義眠冈,也可以從指定的配置文件中讀取,value為對應(yīng)的動畫模版菌瘫,是一個(gè)Graphics對象數(shù)組.使用loadImages(...)蜗顽、loadAtlas(...)、loadAnimation(...)雨让、set source方法可以創(chuàng)建動畫模版雇盖。
3.有三個(gè)類似的方法:loadImages,loadAtlas栖忠,loadAnimation,以及它們?nèi)齻€(gè)的綜合體set source
public function loadImages(urls:Array, cacheName:String = ""):Animation {
this._url = "";
if (!_setFramesFromCache(cacheName)) {
this.frames = framesMap[cacheName] ?
framesMap[cacheName] : createFrames(urls, cacheName);
}
return this;
}
public function loadAtlas(url:String,
loaded:Handler = null, cacheName:String = ""):Animation {
this._url = "";
var _this_:Animation = this;
function onLoaded(loadUrl:String):void {
if (url === loadUrl) {
_this_.frames = framesMap[cacheName] ?
framesMap[cacheName] : createFrames(url, cacheName);
if (loaded) loaded.run();
}
}
if (!_this_._setFramesFromCache(cacheName)) {
if (Loader.getAtlas(url)) onLoaded(url);
else Laya.loader.load(url, Handler.create(
null, onLoaded, [url]), null, Loader.ATLAS);
}
return this;
}
public function loadAnimation(url:String,
loaded:Handler = null, atlas:String = null):Animation {
this._url = url;
var _this_:Animation = this;
if (!_actionName) _actionName = "";
if (!_this_._setFramesFromCache("")) {
if (!atlas || Loader.getAtlas(atlas)) {
_loadAnimationData(url, loaded, atlas);
} else {
Laya.loader.load(atlas, Handler.create(this, _loadAnimationData,
[url, loaded, atlas]), null, Loader.ATLAS)
}
} else {
_this_._setFramesFromCache(_actionName, true);
index = 0;
if (loaded) loaded.run();
}
return this;
}
根據(jù)參數(shù)來看:
loadImages加載的是圖片路徑集合崔挖,比如[url1,url2,url3,...]。
loadAtlas加載的是圖集路徑庵寞,比如animation.loadAtlas("resource/ani/fighter.json");
loadAnimation加載的是由IDE創(chuàng)建的ani文件虚汛,比如ani.loadAnimation("tinyGame/coin0.ani");
這里注意_url屬性,這個(gè)屬性只有在loadAnimation這個(gè)方式時(shí)皇帮,記錄傳入的ani路徑卷哩,其它兩個(gè)方式,都是空字符串属拾。它的用處是在_setFramesFromCache方法中将谊,強(qiáng)行把傳入的name參數(shù)變成_url + "#" + name;
/**@private */
protected function _setFramesFromCache(name:String,
showWarn:Boolean = false):Boolean {
if (_url) name = _url + "#" + name;
if (name && framesMap[name]) {
var tAniO:*;
tAniO = framesMap[name];
if (tAniO is Array) {
this._frames = framesMap[name];
this._count = _frames.length;
} else {
if (tAniO.nodeRoot) {
//如果動畫數(shù)據(jù)未解析過,則先進(jìn)行解析
framesMap[name] = _parseGraphicAnimationByData(tAniO);
tAniO = framesMap[name];
}
this._frames = tAniO.frames;
this._count = _frames.length;
//如果讀取的是動畫配置信息冷溶,幀率按照動畫設(shè)置的幀率播放
if (!_frameRateChanged) _interval = tAniO.interval;
_labels = _copyLabels(tAniO.labels);
}
return true;
} else {
if (showWarn) trace("ani not found:", name);
}
return false;
}
也就是說,IDE創(chuàng)建的ani尊浓,會被緩存成key是"url#動畫名稱" 對應(yīng)相應(yīng)動畫名稱的動畫模板逞频。而另外兩種方式,是否緩存栋齿,以及緩存key叫什么苗胀,是可以自己控制的,對應(yīng)的參數(shù)是cacheName瓦堵。cacheName默認(rèn)為空基协,也就是不進(jìn)行緩存。如果不為空菇用,則表示使用此值做key進(jìn)行動畫模板緩存澜驮。
至于set source,算是對上面三個(gè)方法的一個(gè)整體封裝吧惋鸥,傳入?yún)?shù)如:圖集:"xx/a1.atlas"杂穷;圖片集合:"a1.png,a2.png,a3.png";LayaAir IDE動畫"xx/a1.ani"
public function set source(value:String):void {
if (value.indexOf(".ani") > -1) loadAnimation(value);
else if (value.indexOf(".json") > -1 ||
value.indexOf("als") > -1 ||
value.indexOf("atlas") > -1) loadAtlas(value);
else loadImages(value.split(","));
}
4.createFrames
這個(gè)方法在loadImages和loadAtlas都在使用卦绣,區(qū)別就是一個(gè)傳入的是urls(圖片路徑集合)耐量,一個(gè)是url(圖集路徑)。
public static function createFrames(url:*, name:String):Array {
var arr:Array,i:int,n:int,g:Graphics;
if (url is String) {
var atlas:Array = Loader.getAtlas(url);
if (atlas && atlas.length) {
arr = [];
for (i = 0, n = atlas.length; i < n; i++) {
g = new RunDriver.createGraphics();
g.drawTexture(Loader.getRes(atlas[i]), 0, 0);
arr.push(g);
}
}
} else if (url is Array) {
arr = [];
for (i = 0, n = url.length; i < n; i++) {
g = new RunDriver.createGraphics();
g.loadImage(url[i], 0, 0);
arr.push(g);
}
}
if (name) framesMap[name] = arr;
return arr;
}
從這里也能看出如何去創(chuàng)建一個(gè)Graphics對象滤港。注意最后一句if (name) framesMap[name] = arr;
拴鸵,印證了上面所說,cacheName為空時(shí)蜗搔,不會緩存。
由于這是個(gè)靜態(tài)方法八堡,也可以在不創(chuàng)建Animation實(shí)例前樟凄,先創(chuàng)建動畫緩存,比如:
//創(chuàng)建動畫模板dizziness
Laya.Animation.createFrames(this.aniUrls("die", 4), "die");
Laya.Animation.createFrames(this.aniUrls("fire", 7), "fire");
Laya.Animation.createFrames(this.aniUrls("atk", 3), "atk");
Laya.Animation.createFrames(this.aniUrls("move", 4), "move");
/**
* 創(chuàng)建一組動畫的url數(shù)組(美術(shù)資源地址數(shù)組)
* aniName 動作的名稱兄渺,用于生成url
* length 動畫最后一幀的索引值缝龄,
*/
private aniUrls(aniName: string, length: number): any {
var urls: any = [];
for (var i: number = 1; i < length; i++) {
//動畫資源路徑要和動畫圖集打包前的資源命名對應(yīng)起來
urls.push("imgs/role/wp116/" + aniName + i + ".png");
}
return urls;
}
5.clearCache
根據(jù)上面所說,IDE創(chuàng)建的ani挂谍,會以"url#aniName"作為key來緩存叔壤,清理時(shí)也要注意這一點(diǎn)。
public static function clearCache(key:String):void {
var cache:Object = framesMap;
var val:String;
var key2:String = key + "#";
for (val in cache) {
if (val === key || val.indexOf(key2) == 0) {
delete framesMap[val];
}
}
}
6.重寫的play方法
override public function play(start:* = 0,
loop:Boolean = true, name:String = "",showWarn:Boolean=true):void {
if (name) _setFramesFromCache(name, showWarn);
this._isPlaying = true;
this.index = (start is String) ? _getFrameByLabel(start) : start;
this.loop = loop;
this._actionName = name;
_isReverse = wrapMode == WRAP_REVERSE;
if (this._frames && this.interval > 0) {
timerLoop(this.interval, this, _frameLoop, null, true, true);
}
}
start參數(shù)支持幀標(biāo)簽來控制播放口叙。
loop參數(shù)默認(rèn)是true炼绘,即循環(huán)播放
關(guān)于name參數(shù),注釋是這樣寫的:動畫模板在動畫模版緩存池中的key妄田,也可認(rèn)為是動畫名稱俺亮。如果name為空驮捍,則播放當(dāng)前動畫序列幀;如果不為空脚曾,則在動畫模版緩存池中尋找key值為name的動畫模版东且,如果存在則用此動畫模版初始化當(dāng)前序列幀并播放,如果不存在本讥,則仍然播放當(dāng)前動畫序列幀珊泳;如果沒有當(dāng)前動畫的幀數(shù)據(jù),則不播放拷沸,但該實(shí)例仍然處于播放狀態(tài)色查。
//創(chuàng)建一個(gè)Animation實(shí)例
var tl:Animation = new Animation();
//加載動畫文件
tl.loadAnimation("TimeLine.ani");
//添加到舞臺
Laya.stage.addChild(tl);
//播放Animation動畫
tl.play();
//創(chuàng)建一個(gè)新的Animation實(shí)例
var tl2:Animation = new Animation();
//加載動畫文件
tl2.loadAnimation("TimeLine.ani");
//添加到舞臺
Laya.stage.addChild(tl2);
//播放Animation動畫的pivot動畫
tl2.play(0, true, "pivot");
//動畫的顯示位置
tl2.pos(300,0);
上面的代碼,就是IDE制作的ani中堵漱,有兩個(gè)動畫综慎,可以通過name參數(shù)去選擇播放,缺省播放第一個(gè)動畫勤庐。
7.在loadAnimation方式中示惊,牽涉到_loadAnimationData方法,進(jìn)而使用了_parseGraphicAnimation方法愉镰。這就牽涉到GraphicAnimation類了米罚。
三、幀動畫的數(shù)據(jù)解析
下面這三個(gè)類丈探,針對IDE制作的動畫及動效模板進(jìn)行解析录择。比較復(fù)雜,暫時(shí)不做閱讀碗降。
1.FrameAnimation extends AnimationPlayerBase
關(guān)鍵幀動畫播放類
2.EffectAnimation extends FrameAnimation
動效模板隘竭。用于為指定目標(biāo)對象添加動畫效果。每個(gè)動效有唯一的目標(biāo)對象讼渊,而同一個(gè)對象可以添加多個(gè)動效动看。 當(dāng)一個(gè)動效開始播放時(shí),其他動效會自動停止播放
3.GraphicAnimation extend FrameAnimation
就是IDE創(chuàng)建的時(shí)間軸動畫
四爪幻、IDE動畫編輯器使用
參考時(shí)間軸動畫編輯器詳解菱皆,介紹很詳細(xì),以下記錄自己的使用經(jīng)驗(yàn)
1.選中左側(cè)屬性挨稿,可以設(shè)置相應(yīng)的緩動函數(shù)類型和標(biāo)簽
當(dāng)設(shè)置標(biāo)簽后仇轻,設(shè)置標(biāo)簽的幀會出現(xiàn)紅色圓點(diǎn)∧谈剩可以在項(xiàng)目中篷店,通過標(biāo)簽名用代碼對該幀進(jìn)行操作。
2.多個(gè)節(jié)點(diǎn)臭家,也就是分層動畫船庇,比如上圖中的GraphicNode2,Graphic3
注意官方示例中所說吭产,拖拽一個(gè)新的組件到場景。即會自動新增一個(gè)節(jié)點(diǎn)層鸭轮,剛開始我很困惑怎么增加新節(jié)點(diǎn)層臣淤。
另外,注意窃爷,時(shí)間軸動畫的負(fù)坐標(biāo)區(qū)域內(nèi)邑蒋,無法觸發(fā)點(diǎn)擊事件,如果需要用到點(diǎn)擊事件交互按厘,則動畫的X與Y必須位于正坐標(biāo)區(qū)域医吊,也就是十字紅線交叉的右下區(qū)域。
3.設(shè)置關(guān)鍵幀坐標(biāo)時(shí)TIPS
如果要改坐標(biāo)逮京,先改后面的卿堂。如果從前往后改,后面的坐標(biāo)動了懒棉,前一個(gè)會自動變成中間值 草描。
4.我想做一個(gè)光斑繞扇形移動的動畫,如下圖策严,圓弧段可以逐幀移動穗慕,而兩個(gè)直線區(qū)域則可以使用補(bǔ)間動畫,補(bǔ)間動畫起始點(diǎn)和結(jié)束點(diǎn)妻导,相隔的幀數(shù)逛绵,可以大概估算一下,保證每幀的移動速度和圓弧段每幀移動速度差不多就行倔韭。
這里有個(gè)方便的技巧就是术浪,先建一個(gè)測試動畫,把光斑都放到同一幀寿酌,否則無法直觀看到全部的運(yùn)動軌跡胰苏。當(dāng)然,把背景圖片也放進(jìn)去份名,位置更精確。然后再建正式動畫妓美,每一幀的位置就可以參考測試動畫里的坐標(biāo)了僵腺。
5.Animation中是可以使用遮罩的
如圖,這個(gè)動畫顯示的不是光斑壶栋,而是光條辰如。
方法就是,在Animation里拖一個(gè)Sprite贵试,設(shè)置renderType為mask琉兜。然后在里面用Lines畫出軌跡凯正,同時(shí)設(shè)置線寬。當(dāng)然越寬豌蟋,光條露出來的就越粗了廊散。
五、動效模板
參考創(chuàng)建動效模板(EffectAnimation)
EffectAnimation extends FrameAnimation
動效模板梧疲。用于為指定目標(biāo)對象添加動畫效果允睹。每個(gè)動效有唯一的目標(biāo)對象,而同一個(gè)對象可以添加多個(gè)動效幌氮。 當(dāng)一個(gè)動效開始播放時(shí)缭受,其他動效會自動停止播放
動效模板是基于時(shí)間軸的動畫效果,通過預(yù)設(shè)動畫效果该互,然后把效果附加給某個(gè)組件米者。使得組件無需編碼,卻輕松實(shí)現(xiàn)與編碼相同的動畫效果宇智。動效模板不能獨(dú)立顯示蔓搞,僅可作為動效模板讓UI頁面中的組件獲得動畫效果。
1.按照上述示例普筹,發(fā)現(xiàn)綁定一個(gè)組件后败明,我的組件坐標(biāo)會改變?yōu)閯有0宓淖鴺?biāo)
參考關(guān)于動效模板使用后改變了原來組件的坐標(biāo)問題
2.希望某個(gè)動效完成后,做一些事情
參考監(jiān)聽動效動畫的COMPLETE(播放完成)事件
事件目前只有這幾個(gè)妻顶,暫時(shí)不支持自定義事件。
3.參考動畫模板中蜒车,要求到拖入一張圖讳嘱,然后制作一個(gè)動畫,這張圖是隨便都可以的嗎
資源目錄中png和jpg格式的都可以