Laya 動畫系列三 骨骼動畫

一、骨骼動畫與幀動畫的比較與選擇

參考
幀動畫,Tween 動畫,骨骼動畫等動畫的關系及分類是怎樣的?
骨骼動畫在H5使用攻略
骨骼動畫原理與前端實現(xiàn)淺談
1.幀動畫
動畫的每一幀都獨立地保存在媒體內,連續(xù)播放這些幀即形成了連續(xù)動態(tài)。一般常見于GIF動畫。
2.Tween動畫
受到了Flash的影響。Tween其實是In-between的簡寫,指的是計算機自動插值補全關鍵幀Keyframe之間的動畫。補全的動畫既可以是動態(tài)Motion也可以是變形Morph。所以Tween其實只是一個補全的過程,更加合理的稱呼應該是關鍵幀動畫。
3.骨骼動畫
“骨骼動畫”并不是一種動畫制作形式,而是一種制作手段。目前的動畫物體,特別是有機體,其構成和變形非常復雜,比方說一個由成千上萬個點構成的3D角色。盡管動畫本質上是這些點在空間內做位移構成的,但直接驅動這些點非人力能為。因此我們把運動簡化成骨骼來代表,并把點一一映射到骨骼上。構成骨骼的過程叫Rigging,把點映射到骨骼上的過程叫蒙皮Skinning。
4.骨骼動畫比傳統(tǒng)的逐幀動畫要求更高的處理器性能(幀動畫吃內存),但同時它也具有更多的優(yōu)勢:

  • 動畫更加生動逼真
  • 圖片資源占最小的存儲空間,骨骼動畫的圖片容量可以減少90%(配置文件H5的壓縮方案后面詳解)
  • 動畫切換自動補間。過渡動畫自動生成,讓動作更加靈動
  • 骨骼可控 :可以通過代碼控制骨骼,輕松實現(xiàn)角色裝備更換,甚至可對某骨骼做特殊控制或事件監(jiān)聽骨骼事件幀,動畫執(zhí)行到某個動作或某個幀,觸發(fā)自定義事件行為
  • 動作數(shù)據(jù)繼承,多角色可共用一套動畫數(shù)據(jù)
  • 可結合物理引擎和碰撞檢測

5.效率與選擇
參考想知道用laya做動畫和用spine做動畫然后導進laya 占用的資源是一樣大的嗎
如果項目中同屏動畫小于10-20個的話可以使用骨骼動畫(骨骼動畫節(jié)省圖片資源,運算量大
如果同屏的動畫數(shù)量大于20個以上,建議用序列幀animation,引擎針對animation做了極致的優(yōu)化,在渲染提交的過程中一個圖集是一個drawcall

參考關于白鷺引擎中dragonbones制作的骨骼動畫的執(zhí)行效率問題
Q:我游戲中不添加任何邏輯,只是播放20個小綠龍的行走動畫,在手機上效率就直接掉到20幀了,暫停動畫播放后,又會恢復到60幀。說明就是骨骼動畫的執(zhí)行效率問題了,這樣的話,是不是就不能使用骨骼動畫來實現(xiàn)了?我的游戲場景里鐵定會超過30個,有可能會到60個骨骼動畫
A:這里可以給你一個表格, 是官方的數(shù)據(jù)
在小米2,上使用DB,建議同屏最多渲染圖片(骨骼)數(shù)

DB 普通模式 DB 極速模式
h5 80 150
打包app (未開啟db c++) 250 500
打包app (開啟db c++) 750 1500

如果你想做純h5的游戲,即使使用極速模式,建議同屏也不要超多150個骨骼。極速模式可以參考極速模式
小龍的角色有17個骨骼,也就是同屏不要超過9個。
基于你同屏30個角色的需求,至少要使用打包app的方案。

參考1個骨骼動畫,為何drawcall不是1,資源都在一個大圖上
如果用到網(wǎng)格和換膚的話,會導致drawCall的增加。這個是正常的,建議開發(fā)者減少2者的使用(可以將網(wǎng)格處的圖片轉成png進行使用)
參考骨骼動畫蒙皮和網(wǎng)格動畫性能問題
骨骼動畫本身性能就是比較耗的,尤其是用到蒙皮和網(wǎng)格,animation是目前最高效的動畫方式,建議開發(fā)者最好不要使用蒙皮和網(wǎng)格動畫,如果資源量不大的話,以圖集動畫為最優(yōu)!
參考為啥不同的骨骼動畫drawCall不一樣主要受啥影響
主要應該是蒙皮和網(wǎng)格吧,還有骨骼數(shù)的不同吧,理論上骨骼數(shù)不要超過255,但是建議最好不要超過200

6.參考2D動畫,是用dragonbones還是spine更強大?
spine比drangonbones多的功能:IK,自由變形,多種輸出格式。如果只是做骨骼動畫,那龍骨是夠用的。如果對自由變形要求比較高,那就只能選spine了。 希望龍骨盡快迎頭趕上,可以完全取代spine。畢竟spine的價格高的離譜,而且希望國產(chǎn)軟件能不輸給國外軟件。

二、dragonbones使用

參考dragonbones官網(wǎng)
官網(wǎng)指南video

三、Laya中使用骨骼動畫

參考
Laya 播放Spine骨骼動畫
Laya 播放DragonBones動畫
Laya 骨骼動畫模板、播放模式、換裝、切換動作
Egret 機甲戰(zhàn)士

引擎中使用骨骼動畫無論是Spine還是DragonBone其實用法都是一樣的,因為在轉換過程中轉換工具將兩種動畫都轉成了引擎可以使用的相同的格式


image.png

1.直接加載使用

package
{
    import laya.ani.bone.Skeleton;
    public class DragonBonesDemo
    {
        public function DragonBonesDemo()
        {
            //初始化舞臺
            Laya.init(1334, 750);
            //創(chuàng)建一個Skeleton對象
            var skeleton:Skeleton = new Skeleton();
             //添加到舞臺
            Laya.stage.addChild(skeleton);
            skeleton.pos(600,350);
            //通過加載直接創(chuàng)建動畫
            skeleton.load("res/DragonBones/rooster/Rooster_Ani.sk");
        }
    }
}

也可以預加載:
參考多個骨骼動畫可以預加載嗎,有好幾十個

image.png

2.使用骨骼動畫模板
要更好的使用骨骼動畫就必須提到模板的概念,在LayaAir引擎中模板是一種特別的概念,表示一種數(shù)據(jù)結構,這種數(shù)據(jù)結構可以被復用。骨骼動畫就使用到了模板,對于同一個動畫來說,可以只創(chuàng)建一個動畫模板,然后實例多個播放的實例,這樣內存中就只有一份的動畫數(shù)據(jù),但是卻可以在舞臺上顯示多個動畫

package  
{
    import laya.ani.bone.Skeleton;
    import laya.ani.bone.Templet;
    import laya.events.Event;
    import laya.webgl.WebGL;
    /**
     * ...
     * @author ww
     */
    public class SkeletonTempletSample 
    {
        public var templet:Templet;
        public function SkeletonTempletSample() 
        {
            WebGL.enable();
            Laya.init(1000, 900);
            //創(chuàng)建動畫模板
            templet = new Templet();
            templet.on(Event.COMPLETE, this, parseComplete);
            templet.on(Event.ERROR, this, onError);
            //加載動畫文件
            templet.loadAni("res/spine/goblins/goblins.sk");
        }
        private function onError():void
        {
            trace("parse error");
        }
        private function parseComplete():void
        {
            //創(chuàng)建第一個動畫
            var skeleton0:Skeleton;
            //從動畫模板創(chuàng)建動畫播放對象
            skeleton0 = templet.buildArmature(0);
            skeleton0.pos(200, 700);
            //切換動畫皮膚
            skeleton0.showSkinByIndex(1);
            //播放
            skeleton0.play(0,true);
            Laya.stage.addChild(skeleton0);
            //創(chuàng)建第二個動畫
            var skeleton1:Skeleton;
            skeleton1 = templet.buildArmature(0);
            skeleton1.pos(500, 700);
            skeleton1.showSkinByIndex(1);
            skeleton1.play(0,true);
            Laya.stage.addChild(skeleton1);
        }
    }
}

3.播放模式
我們在從模板創(chuàng)建動畫的時候傳了一個參數(shù)0,這個參數(shù)就表示動畫的播放模式。(skeleton0 = templet.buildArmature(0);)動畫有三個播放模式,下面分別說明:

  • 0:使用模板緩沖的數(shù)據(jù),模板緩沖的數(shù)據(jù),不允許修改 (內存開銷小,計算開銷小,不支持換裝)
  • 1:使用動畫自己的緩沖區(qū),每個動畫都會有自己的緩沖區(qū),相當耗費內存。(內存開銷大,計算開銷小,支持換裝)
  • 2:使用動態(tài)方式,去實時去畫(內存開銷小,計算開銷大,支持換裝,不建議使用)

? 這三種模式中 0:不支持換裝,1,2支持換裝。

4.換裝

//切換動畫皮膚
skeleton0.showSkinByIndex(1);

我們在這里傳了一個參數(shù)1,表示切換到1號皮膚。事實上這個動畫有三個皮膚,0號是默認皮膚,1號是男角色皮膚,2號是女角色皮膚,下面我們給一個顯示不同皮膚的例子。

//創(chuàng)建第三個動畫
var skeleton2:Skeleton;
skeleton2 = templet.buildArmature(0);
skeleton2.pos(700, 700);
//切換動畫皮膚 使用標號為2的皮膚
skeleton2.showSkinByIndex(2);
skeleton2.play(0,true);
Laya.stage.addChild(skeleton2);

在另外一個例子中,使用了showSkinByName

var mSkinList = ["goblin","goblingirl"];

function parseComplete() {
    //創(chuàng)建模式為1,可以啟用換裝
    mArmature = mFactory.buildArmature(1);
    mArmature.x = mStartX;
    mArmature.y = mStartY;
    Laya.stage.addChild(mArmature);
    mArmature.on(Event.STOPPED, this, completeHandler);
    play();
    changeSkin();
    Laya.timer.loop(1000, this, changeSkin);
}

function changeSkin()
{
    mCurrSkinIndex++;
    if (mCurrSkinIndex >= mSkinList.length)
    {
        mCurrSkinIndex = 0;
    }
    mArmature.showSkinByName(mSkinList[mCurrSkinIndex]);
}

在論壇網(wǎng)友分享的例子中,參考分享:Dragonbones/Spine的換膚操作
目前LayaAir下支持龍骨的局部換膚(根據(jù)插槽索引換膚、根據(jù)插槽name換膚、紋理換膚、網(wǎng)格換膚)、全局換膚。需注意:

  • Dragonbones不支持全局換膚,Spine支持全局換膚
  • 使用到IK和網(wǎng)格的動畫需要開啟WebGL,否則可能會出現(xiàn)皮膚丟失的情況
  • Dragonbones與spine的接口是一樣的,除第一種情況外,下面的示例通用于Dragonbones和spine

5.切換動作

private var skeleton:Skeleton;
private var text:Text;
private function test():void
{  
    skeleton = new Skeleton();
    skeleton.url = "res/spine/alien/alien.sk";
    skeleton.pos(300, 700);
    Laya.stage.addChild(skeleton);
    text = new Text();
    Laya.stage.addChild(text);
    text.color = "#00ff00";
    text.fontSize = 30;
    Laya.stage.addChild(text);
    Laya.stage.on(Event.MOUSE_DOWN, this, changeAction);
}
private var tActionID:int=0;
private function changeAction():void
{
    tActionID++;
    var aniCount:int;
    //獲取動畫動作數(shù)量
    aniCount = skeleton.getAnimNum();
    tActionID = tActionID % aniCount;
    //顯示當前要播放的動畫名
    text.text = skeleton.getAniNameByIndex(tActionID);
    //切換播放的動畫
    skeleton.play(tActionID, true);
}

6.事件
參考骨骼動畫--Spine事件

private parseComplete():void {
    //創(chuàng)建模式為1,可以啟用換裝
    this.mArmature = this.mFactory.buildArmature(1);
    this.mArmature.x = this.mStartX;
    this.mArmature.y = this.mStartY;
    this.mArmature.scale(0.5, 0.5);
    Laya.stage.addChild(this.mArmature);
    this.mArmature.on(Event.LABEL, this, this.onEvent);
    this.mArmature.on(Event.STOPPED, this, this.completeHandler);
    this.play();
}

private completeHandler():void
{
    this.play();
}

private play():void
{
    this.mCurrIndex++;
    if (this.mCurrIndex >= this.mArmature.getAnimNum())
    {
        this.mCurrIndex = 0;
    }
    this.mArmature.play(this.mCurrIndex,false);
    
}

private onEvent(e):void
{
    var tEventData:EventData = e as EventData;
    
    Laya.stage.addChild(this.mLabelSprite);
    this.mLabelSprite.x = this.mStartX;
    this.mLabelSprite.y = this.mStartY;
    this.mLabelSprite.graphics.clear();
    this.mLabelSprite.graphics.fillText(tEventData.name,
    0, 0, "20px Arial", "#ff0000", "center");
    Tween.to(this.mLabelSprite, { y:this.mStartY - 200 },
    1000, null,Handler.create(this,this.playEnd))
}

private playEnd():void
{
    this.mLabelSprite.removeSelf();
}

關于如何為動畫關鍵幀添加事件,參考分享:Skeleton下Event.LABLE('label')事件的使用

image.png

打開Dragonbones(或spine),選擇龍骨動畫的關鍵幀,在屬性面板會看到事件,單擊事件后面的文本框,添加自定義幀事件,命名隨意,譬如label、'label'、jump、walk....,支持多個幀事件

image.png

console.log("label event:",e)

參考分享:Skeleton如何監(jiān)聽播放完成事件!
當skeleton.play(0,true)第二個參數(shù)為true時,每播放完一遍龍骨動畫,會自動觸發(fā)Event.COMPLET事件
skeleton.player.on(Event.COMPLETE,this,onComplete);
當skeleton.play(0,false)第二個參數(shù)為false時,當前動畫播放完成后,會自動觸發(fā)Event.STOPED事件,而不是Event.COMPLETE事件
skeleton.on(Event.STOPPED, this, completeHandler);

7.子骨骼動畫
參考關于骨骼動畫
Q:現(xiàn)在游戲中需要給人物加上翅膀,并且翅膀是可以更換的,請問下面的做法是否可以實現(xiàn):

  • 將翅膀單獨做成一個骨骼動畫
  • 再根據(jù)需要將翅膀的骨骼動畫綁定到人物骨骼動畫上去(即翅膀骨骼動畫作為人物骨骼動畫的子骨骼動畫存在)
  • 換槽位的貼圖的方法我知道怎么做,但是翅膀根據(jù)不同的類型可能會有不同的動畫,所以想以子骨骼動畫的形式進行更換

請問上述方式是否可以實現(xiàn)
A:layaAir目前不支持翅膀骨骼動畫作為人物骨骼動畫的子骨骼動畫存在(骨骼動畫嵌套或者多骨骼播放不支持),建議開發(fā)者換種方式實現(xiàn)吧(可以對整體動畫的動作---翅膀+軀體進行調整,單個部位換膚)!

8.參考可以在動畫骨骼中插入Laya元件嗎
Q:在運行骨骼動畫時可以通過函數(shù)得到相應的骨骼或者插槽,那我可以通過骨骼或者插槽加入比如Sprite這些Laya元件進入動畫中嗎?比如我做了一系列的玩家移動、攻擊動畫,我想在人物的雙手位置加入一些粒子特效,并且是隨著人物狀態(tài)實時改變的,也就是動態(tài)添加進去的,可以實現(xiàn)嗎?雖然可以通過骨骼的世界坐標+旋轉縮放計算來達到在手的位置顯示對象的效果,但是無法插入到人物的層級中去,例如效果應該顯示在內部手和身體之間那一層,但是動畫外的對象無法插入到動畫內對象的層級中去。
A:你好,目前除了換膚外,不支持將粒子等其他元件插入到龍骨的插槽里

9.參考骨骼動畫加遮罩

10.參考分享:銷毀龍骨動畫!

public function startFun():void
{
    mAniPath = "Dragon/Dragon.sk";
    mFactory = new Templet();
    mFactory.on(Event.COMPLETE, this, parseComplete);
    mFactory.loadAni(mAniPath);
}

private function parseComplete(fac:Templet):void {
    //創(chuàng)建模式為1,可以啟用換裝
    mArmature = mFactory.buildArmature(0);
    mArmature.x = 400;
    mArmature.y = 500;
    mArmature.scale(0.5, 0.5);
    Laya.stage.addChild(mArmature);
    mArmature.on(Event.STOPPED, this, completeHandler);
    play();
}
        
private function completeHandler():void
{
    play();
}

private function play():void
{
    mCurrIndex++;
    if (mCurrIndex >= mArmature.getAnimNum())
    {
        mCurrIndex = 0;
    }
    mArmature.play(mCurrIndex,false);
}
        
public function destroy():void
{
    mArmature.stop();//停止龍骨動畫播放
    removeEvent();//移除事件
    mArmature.removeSelf();//從顯示列表移除龍骨動畫本身
    mArmature.removeChildren();//從顯示列表移除龍骨動畫子對象
    mArmature.destroy(true);//從顯存銷毀龍骨動畫及其子對象
    mFactory.destroy();//釋放動畫模板類下的紋理數(shù)據(jù)
    mFactory.releaseResource(true);//釋放龍骨資源
}

public function removeEvent():void
{
    mFactory.off(Event.COMPLETE, this, parseComplete);
    mArmature.off(Event.STOPPED, this, completeHandler);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,716評論 25 709
  • 111. [動畫系統(tǒng)]如何將其他類型的動畫轉換成關鍵幀動畫? 動畫->點緩存->關鍵幀 112. [動畫]Unit...
    胤醚貔貅閱讀 13,504評論 3 88
  • 今天吼了也叫了,過后我在鏡子面前端詳了一陣自己發(fā)怒的樣子,眉頭緊皺,額頭上也長了一浪浪的橫紋,眼睛冒著兇光,真的不...
    遠遠_d1f1閱讀 141評論 0 3
  • 劉美芹種子日記 2017年 08 月 29日 [拳頭]身修家和 美麗中國 種子踐行日記 地方:南通家...
    劉小兔子閱讀 236評論 0 0
  • 通過一天的跟進挑戰(zhàn)了不起項目,對于這個活動有幾個點收獲比較大, 一.是對于選擇小區(qū)很重要, 二.是團隊的領隊要起到...
    愛思哲閱讀 599評論 0 0

友情鏈接更多精彩內容