我的新書AI 職場超神助手:ChatGPT 與生成式 AI 一鍵搞定工作難題的教材投影片已製作完成
歡迎各位有需要的教師和博碩文化索取教材

[20- Pixi教學] 連連看公仔實作- 逐格動畫

動畫物件

在一般的2D遊戲中,動畫可以用2D骨骼動畫製作(如:Spine)、粒子系統(如:Particles)、或者使用Tween(如:gsap)以及逐格動畫來製作。

下面為這四種動畫的介紹:

  • 骨骼動畫:針對角色動畫的骨架做設定,並且經由改變骨骼元件方向及變形來讓角色做出如行走、跳躍等動態動作。使用骨骼動畫可以大大降低角色所占的圖像空間,尤其是在2D的遊戲裡,這是不可或缺的技術。
  • 粒子系統:有許多編輯器可以編輯出粒子效果的設定檔,並藉由粒子系統的程式來做出特效。
  • Tween:針對一個物件的某個屬性去設定一段時間將該屬性從a數值變成b數值。可以用在物件位移(更動x、y屬性)、放大縮小(更動scale)、旋轉(更動rotate)等效果上。也可以更改自定義屬性,如跑分效果(將分數慢慢加上去)。
    下面是官網上的一個效果範例:
  • 逐格動畫:逐格動畫是最簡單原始的動畫類型,由許多張的動態圖組合成為一幅動畫。逐格動畫的動畫製作能夠最精緻、最多樣化,但是也需要花最多力氣在繪製圖像,並且會有很大的檔案大小。其原理與一般電影、gif檔相同。

逐格動畫

PIXI.extras.AnimatedSprite是在Pixi.JS裡處理動畫的類別,官網對此類別的說明如下:

An AnimatedSprite is a simple way to display an animation depicted by a list of textures.

也就是可以用來播放由多張連續的圖檔組成的逐格動畫,在前面的[13- 遊戲製作] 素材處理的部份,已經有分享過該如何將連續圖檔打包成所需的格式,在這一章裡,則會在遊戲裡來實際使用這些連續圖檔。

角色動畫需求

在遊戲中,我們希望角色動畫可以在不同的時間播放不同的動畫,讓角色與遊戲能夠更有互動感,播放時機與動畫的設計如下:

    • 待機動畫:播放Character_Idle.json
      消除成功時:播放Character_Laugh.json
      按下提示時:播放Character_Jump.json
  • 角色動畫程式撰寫

    1. 產生動畫元件: 傳入要產生的動畫名稱,然後設定動畫完成後要呼叫的動作。

        createAnim(id:string, onComplete:any){
            let anim = Loader.resources[id].textures;
            let textures = [];
            for(var index in anim) {
                textures.push(anim[index]);
            }
            var character = new PIXI.extras.AnimatedSprite(textures);
            character.play();
            character.animationSpeed = 0.25;
            character.loop = false;
            character.onComplete = onComplete;
            this.addChild(character);
            return character;
        }

    2. 產生動畫並設定handler:因為動畫若播到一半被中斷會很突兀,這邊一律是等到動畫播完後,才會依狀態判斷下一個要播的動畫

        private shouldPlayTarget:string = 'idle';
        constructor(){
            super();
            //每次動畫完成之後,都要判斷下一個要播放的動畫為何
            this.idle = this.createAnim('Character_Idle', this.playAnim.bind(this));
            this.jump = this.createAnim('Character_Jump', this.playAnim.bind(this));
            this.laugh = this.createAnim('Character_Laugh', this.playAnim.bind(this));
    
            eventEmitter.on(GameFlowEvent.LinkedLineSuccess, ()=>{
                this.shouldPlayTarget = 'laugh';//設定下一個要播的動畫
            });
            eventEmitter.on(GameFlowEvent.TipsRequest, ()=>{
                this.shouldPlayTarget = 'jump';
            });
        }
        //依據shouldPlayTarget的值來判斷現在要播的動畫
        //如果沒有特殊要播的動畫的話,則一律播放待機動畫
        playAnim(){
            this.idle.visible = false;
            this.laugh.visible = false;
            this.jump.visible = false;
            this[this.shouldPlayTarget].visible = true;
            this[this.shouldPlayTarget].gotoAndPlay(0);
            this.shouldPlayTarget = 'idle';
        }

    Container特性

    在上面,我們是直接產生三個動畫物件,在需要使用時才會把visible設為true並且播放,因此要小心是否會造成效能問題,在研究了一下Container.js的原始碼,可以發現如果當元件的visible為false時,是不會去render這個物件的,也不會去處理其相關子元件的畫面變更。


    Source:Container.js

    在經過實測後,的確將畫面上不會被看到的元件的visible設為false後,能夠提升畫面的fps,因此這也是開發上一個可以多加利用的小技巧。

    另外,filter的使用,會花費較多的效能,需要謹慎使用。而mask下的物件,即便因為mask而無法看到,仍舊會一直被render,因此即便是已經在mask下因此畫面上無法看見的元件,依然建議要把可試範圍外的物件的visible設為false,以提升遊戲效能。

    今日成果


    線上demo:http://claire-chang.com/ironman2018/1104/
    今日成果下載:ironman1104


    17年資歷女工程師,專精於動畫、影像辨識以及即時串流程式開發。經常組織活動,邀請優秀的女性分享她們的技術專長,並在眾多場合分享自己的技術知識,也活躍於非營利組織,辦理活動來支持特殊兒及其家庭。期待用技術改變世界。

    如果你認同我或想支持我的努力,歡迎請我喝一杯咖啡!讓我更有動力分享知識!