一、骨骼動畫與幀動畫的比較與選擇
參考
幀動畫,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使用
三、Laya中使用骨骼動畫
參考
Laya 播放Spine骨骼動畫
Laya 播放DragonBones動畫
Laya 骨骼動畫模板、播放模式、換裝、切換動作
Egret 機甲戰士
引擎中使用骨骼動畫無論是Spine還是DragonBone其實用法都是一樣的,因為在轉換過程中轉換工具將兩種動畫都轉成了引擎可以使用的相同的格式
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");
}
}
}
也可以預加載:
參考多個骨骼動畫可以預加載嗎,有好幾十個
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')事件的使用
打開Dragonbones(或spine),選擇龍骨動畫的關鍵幀,在屬性面板會看到事件,單擊事件后面的文本框,添加自定義幀事件,命名隨意,譬如label、'label'、jump、walk....,支持多個幀事件
參考分享: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);
}