Laya 动画系列三 骨骼动画

一、骨骼动画与帧动画的比较与选择

参考
帧动画,Tween 动画,骨骼动画等动画的关系及分类是怎样的?
骨骼动画在H5使用攻略
骨骼动画原理与前端实现浅谈
1.帧动画
动画的每一帧都独立地保存在媒体内,连续播放这些帧即形成了连续动态。一般常见于GIF动画。
2.Tween动画
受到了Flash的影响。Tween其实是In-between的简写,指的是计算机自动插值补全关键帧Keyframe之间的动画。补全的动画既可以是动态Motion也可以是变形Morph。所以Tween其实只是一个补全的过程,更加合理的称呼应该是关键帧动画。
3.骨骼动画
“骨骼动画”并不是一种动画制作形式,而是一种制作手段。目前的动画物体,特别是有机体,其构成和变形非常复杂,比方说一个由成千上万个点构成的3D角色。尽管动画本质上是这些点在空间内做位移构成的,但直接驱动这些点非人力能为。因此我们把运动简化成骨骼来代表,并把点一一映射到骨骼上。构成骨骼的过程叫Rigging,把点映射到骨骼上的过程叫蒙皮Skinning。
4.骨骼动画比传统的逐帧动画要求更高的处理器性能(帧动画吃内存),但同时它也具有更多的优势:

  • 动画更加生动逼真
  • 图片资源占最小的存储空间,骨骼动画的图片容量可以减少90%(配置文件H5的压缩方案后面详解)
  • 动画切换自动补间。过渡动画自动生成,让动作更加灵动
  • 骨骼可控 :可以通过代码控制骨骼,轻松实现角色装备更换,甚至可对某骨骼做特殊控制或事件监听骨骼事件帧,动画执行到某个动作或某个帧,触发自定义事件行为
  • 动作数据继承,多角色可共用一套动画数据
  • 可结合物理引擎和碰撞检测

5.效率与选择
参考想知道用laya做动画和用spine做动画然后导进laya 占用的资源是一样大的吗
如果项目中同屏动画小于10-20个的话可以使用骨骼动画(骨骼动画节省图片资源,运算量大
如果同屏的动画数量大于20个以上,建议用序列帧animation,引擎针对animation做了极致的优化,在渲染提交的过程中一个图集是一个drawcall

参考关于白鹭引擎中dragonbones制作的骨骼动画的执行效率问题
Q:我游戏中不添加任何逻辑,只是播放20个小绿龙的行走动画,在手机上效率就直接掉到20帧了,暂停动画播放后,又会恢复到60帧。说明就是骨骼动画的执行效率问题了,这样的话,是不是就不能使用骨骼动画来实现了?我的游戏场景里铁定会超过30个,有可能会到60个骨骼动画
A:这里可以给你一个表格, 是官方的数据
在小米2,上使用DB,建议同屏最多渲染图片(骨骼)数

DB 普通模式 DB 极速模式
h5 80 150
打包app (未开启db c++) 250 500
打包app (开启db c++) 750 1500

如果你想做纯h5的游戏,即使使用极速模式,建议同屏也不要超多150个骨骼。极速模式可以参考极速模式
小龙的角色有17个骨骼,也就是同屏不要超过9个。
基于你同屏30个角色的需求,至少要使用打包app的方案。

参考1个骨骼动画,为何drawcall不是1,资源都在一个大图上
如果用到网格和换肤的话,会导致drawCall的增加。这个是正常的,建议开发者减少2者的使用(可以将网格处的图片转成png进行使用)
参考骨骼动画蒙皮和网格动画性能问题
骨骼动画本身性能就是比较耗的,尤其是用到蒙皮和网格,animation是目前最高效的动画方式,建议开发者最好不要使用蒙皮和网格动画,如果资源量不大的话,以图集动画为最优!
参考为啥不同的骨骼动画drawCall不一样主要受啥影响
主要应该是蒙皮和网格吧,还有骨骼数的不同吧,理论上骨骼数不要超过255,但是建议最好不要超过200

6.参考2D动画,是用dragonbones还是spine更强大?
spine比drangonbones多的功能:IK,自由变形,多种输出格式。如果只是做骨骼动画,那龙骨是够用的。如果对自由变形要求比较高,那就只能选spine了。 希望龙骨尽快迎头赶上,可以完全取代spine。毕竟spine的价格高的离谱,而且希望国产软件能不输给国外软件。

二、dragonbones使用

参考dragonbones官网
官网指南video

三、Laya中使用骨骼动画

参考
Laya 播放Spine骨骼动画
Laya 播放DragonBones动画
Laya 骨骼动画模板、播放模式、换装、切换动作
Egret 机甲战士

引擎中使用骨骼动画无论是Spine还是DragonBone其实用法都是一样的,因为在转换过程中转换工具将两种动画都转成了引擎可以使用的相同的格式


image.png

1.直接加载使用

package
{
    import laya.ani.bone.Skeleton;
    public class DragonBonesDemo
    {
        public function DragonBonesDemo()
        {
            //初始化舞台
            Laya.init(1334, 750);
            //创建一个Skeleton对象
            var skeleton:Skeleton = new Skeleton();
             //添加到舞台
            Laya.stage.addChild(skeleton);
            skeleton.pos(600,350);
            //通过加载直接创建动画
            skeleton.load("res/DragonBones/rooster/Rooster_Ani.sk");
        }
    }
}

也可以预加载:
参考多个骨骼动画可以预加载吗,有好几十个

image.png

2.使用骨骼动画模板
要更好的使用骨骼动画就必须提到模板的概念,在LayaAir引擎中模板是一种特别的概念,表示一种数据结构,这种数据结构可以被复用。骨骼动画就使用到了模板,对于同一个动画来说,可以只创建一个动画模板,然后实例多个播放的实例,这样内存中就只有一份的动画数据,但是却可以在舞台上显示多个动画

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);
            //创建动画模板
            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
        {
            //创建第一个动画
            var skeleton0:Skeleton;
            //从动画模板创建动画播放对象
            skeleton0 = templet.buildArmature(0);
            skeleton0.pos(200, 700);
            //切换动画皮肤
            skeleton0.showSkinByIndex(1);
            //播放
            skeleton0.play(0,true);
            Laya.stage.addChild(skeleton0);
            //创建第二个动画
            var skeleton1:Skeleton;
            skeleton1 = templet.buildArmature(0);
            skeleton1.pos(500, 700);
            skeleton1.showSkinByIndex(1);
            skeleton1.play(0,true);
            Laya.stage.addChild(skeleton1);
        }
    }
}

3.播放模式
我们在从模板创建动画的时候传了一个参数0,这个参数就表示动画的播放模式。(skeleton0 = templet.buildArmature(0);)动画有三个播放模式,下面分别说明:

  • 0:使用模板缓冲的数据,模板缓冲的数据,不允许修改 (内存开销小,计算开销小,不支持换装)
  • 1:使用动画自己的缓冲区,每个动画都会有自己的缓冲区,相当耗费内存。(内存开销大,计算开销小,支持换装)
  • 2:使用动态方式,去实时去画(内存开销小,计算开销大,支持换装,不建议使用)

​ 这三种模式中 0:不支持换装,1,2支持换装。

4.换装

//切换动画皮肤
skeleton0.showSkinByIndex(1);

我们在这里传了一个参数1,表示切换到1号皮肤。事实上这个动画有三个皮肤,0号是默认皮肤,1号是男角色皮肤,2号是女角色皮肤,下面我们给一个显示不同皮肤的例子。

//创建第三个动画
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() {
    //创建模式为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]);
}

在论坛网友分享的例子中,参考分享:Dragonbones/Spine的换肤操作
目前LayaAir下支持龙骨的局部换肤(根据插槽索引换肤、根据插槽name换肤、纹理换肤、网格换肤)、全局换肤。需注意:

  • Dragonbones不支持全局换肤,Spine支持全局换肤
  • 使用到IK和网格的动画需要开启WebGL,否则可能会出现皮肤丢失的情况
  • 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;
    //获取动画动作数量
    aniCount = skeleton.getAnimNum();
    tActionID = tActionID % aniCount;
    //显示当前要播放的动画名
    text.text = skeleton.getAniNameByIndex(tActionID);
    //切换播放的动画
    skeleton.play(tActionID, true);
}

6.事件
参考骨骼动画--Spine事件

private parseComplete():void {
    //创建模式为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如何监听播放完成事件!
当skeleton.play(0,true)第二个参数为true时,每播放完一遍龙骨动画,会自动触发Event.COMPLET事件
skeleton.player.on(Event.COMPLETE,this,onComplete);
当skeleton.play(0,false)第二个参数为false时,当前动画播放完成后,会自动触发Event.STOPED事件,而不是Event.COMPLETE事件
skeleton.on(Event.STOPPED, this, completeHandler);

7.子骨骼动画
参考关于骨骼动画
Q:现在游戏中需要给人物加上翅膀,并且翅膀是可以更换的,请问下面的做法是否可以实现:

  • 将翅膀单独做成一个骨骼动画
  • 再根据需要将翅膀的骨骼动画绑定到人物骨骼动画上去(即翅膀骨骼动画作为人物骨骼动画的子骨骼动画存在)
  • 换槽位的贴图的方法我知道怎么做,但是翅膀根据不同的类型可能会有不同的动画,所以想以子骨骼动画的形式进行更换

请问上述方式是否可以实现
A:layaAir目前不支持翅膀骨骼动画作为人物骨骼动画的子骨骼动画存在(骨骼动画嵌套或者多骨骼播放不支持),建议开发者换种方式实现吧(可以对整体动画的动作---翅膀+躯体进行调整,单个部位换肤)!

8.参考可以在动画骨骼中插入Laya元件吗
Q:在运行骨骼动画时可以通过函数得到相应的骨骼或者插槽,那我可以通过骨骼或者插槽加入比如Sprite这些Laya元件进入动画中吗?比如我做了一系列的玩家移动、攻击动画,我想在人物的双手位置加入一些粒子特效,并且是随着人物状态实时改变的,也就是动态添加进去的,可以实现吗?虽然可以通过骨骼的世界坐标+旋转缩放计算来达到在手的位置显示对象的效果,但是无法插入到人物的层级中去,例如效果应该显示在内部手和身体之间那一层,但是动画外的对象无法插入到动画内对象的层级中去。
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 {
    //创建模式为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();//释放动画模板类下的纹理数据
    mFactory.releaseResource(true);//释放龙骨资源
}

public function removeEvent():void
{
    mFactory.off(Event.COMPLETE, this, parseComplete);
    mArmature.off(Event.STOPPED, this, completeHandler);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,820评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,648评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,324评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,714评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,724评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,328评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,897评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,804评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,345评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,431评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,561评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,238评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,928评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,417评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,528评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,983评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,573评论 2 359

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,297评论 25 707
  • 111. [动画系统]如何将其他类型的动画转换成关键帧动画? 动画->点缓存->关键帧 112. [动画]Unit...
    胤醚貔貅阅读 13,053评论 3 90
  • 今天吼了也叫了,过后我在镜子面前端详了一阵自己发怒的样子,眉头紧皱,额头上也长了一浪浪的横纹,眼睛冒着凶光,真的不...
    远远_d1f1阅读 128评论 0 3
  • 刘美芹种子日记 2017年 08 月 29日 [拳头]身修家和 美丽中国 种子践行日记 地方:南通家...
    刘小兔子阅读 194评论 0 0
  • 通过一天的跟进挑战了不起项目,对于这个活动有几个点收获比较大, 一.是对于选择小区很重要, 二.是团队的领队要起到...
    爱思哲阅读 541评论 0 0