1. 程式人生 > >《Unity遊戲動畫設計》-動畫基礎知識

《Unity遊戲動畫設計》-動畫基礎知識

要想讓遊戲看上去栩栩如生,動畫表現往往扮演著十分重要的角色,我們開啟一座陳舊的遺蹟大門,打翻一架沉重的鋼琴,或是按下“R”釋放一團閃耀著的大火球。。這些都需要動畫來賦予他生命,剛體動畫,骨骼動畫,粒子動畫等等,否則,一切元素都會歸於靜止而顯得枯燥和缺乏生命力。


什麼是動畫

在基本的層面上,動畫就是時間和變化這兩個獨立特徵之間關係的表示,這麼說未免顯的太隱晦,不如說,動畫定義了一段時間內的變化。如紅綠燈由紅燈變為黃燈,這就是一段動畫,表示一段時間(紅燈開始到黃燈亮起)內的變化(顏色變黃)。

因為我本身不是動畫設計師,所以沒能從藝術角度分析什麼是動畫,但毫無疑問優秀的動畫可以有效得表達情緒,氛圍,場景以及理念等特徵。大大加強遊戲的代入感。(例如經典的奧日與黑暗森林,用了大量動畫效果打造出一個幻想的世界)

奧日與黑暗森林

1.幀

幀是我們在遊戲中經常能聽到的概念,在動畫表示中,時間需要被劃分為變化過程中獨立且離散的單位,這一單位就是幀。,這一概念的出現給屬性變化提供了機會————我們可以在某一幀內開啟大門或是移動角色。但是每秒的幀數(即FPS)是因計算機裝置而異的,通常較差的幀率意味著較低的效能。

1.1關鍵幀

雖然幀為我們的動畫提供了改變機會,但並不是每一幀都需要改變,例如無人開啟的大門將一直保持靜止狀態,直到有人用鑰匙喚醒它,即大門有兩個狀態,開啟狀態和閉合狀態,這兩個狀態變化時刻就可以表示動畫中的關鍵節點,即關鍵幀。通常我們只要定義關鍵幀的動畫,Unity會自動幫我們生成中間幀,平滑的補全中間動畫,這一數學過程稱之為差值運算,也是我們在Unity中常用的一種運算方法。


動畫型別

從技術角度上動畫可以根據實現方式的不同劃分為不同的動畫型別,我們先簡要介紹他們,隨後的章節我們會補全更多的知識。

1.剛體動畫

剛體動畫用於建立預製動畫序列,通常不需要我們過多的人工干涉,直接利用Unity的動畫元件進行配置即可。剛體動畫多用來移動和改變物件的整體屬性,如賓士的車輛,沿合頁閉合的大門,翻倒的大樹等,均是作為獨立的整體進行操作,在動畫結束後,其內部結構和組成未發生任何變化,這一類動畫可直接在Unity的動畫編輯視窗加以定義,其中曲線中的結點為關鍵幀(變化幀)。

剛體動畫視窗

2.骨骼動畫

如果僅靠剛體動畫,我們是無法表現人類的追逐跑動,獨眼巨人霸氣的一記重擊,對此就要用到骨骼動畫,這類動畫通常不改變整體的位置,旋轉和縮放,而會在幀間對內部組成和結構進行移動或變形。動畫設計師往往建立特定骨骼物件的網狀物,並以底層網狀骨骼予以近似,進而對周邊和內部幾何體實現方便獨立的操作

。十分有效的模擬人物行走時手臂的擺動或是頭部的轉動。通常情況下,骨骼動畫會在3D建模軟體中完成,但是Unity也提供相應的元件進行設定,我們後期給予討論。

一個簡單易懂的2D骨骼動畫教程 by Brackeys(youtube)

2D骨骼動畫例項

3.精靈動畫

這是對於2D動畫,圖形使用者介面以及各類3D特效(例如水紋,刀光)通常要用到的動畫,這類動畫不會像剛體那樣運動,其內部結構也不會發生改變。而是利用影象或幀序列以特定的幀速率播放,進而呈現一致的動畫外觀,例如2D動畫中角色的行走,跑動,跳躍,通常是已經繪製完畢並放置與精靈表單中,在需要時直接輪換播放。

精靈表單

4.物理動畫

在很多時候,我們希望動畫呈現真實的效果並動態的與場景反饋,但很多時候是我們無法預知的,我們希望落石因為重力而下墜,希望氣球因為浮力而上升,但我們無法控制所有的落石,氣球。這是最常用的方法是採用Unity提供的物理系統,讓其自動模擬真實世界的行為方式。

5.變形動畫

有些情況下,我們對動畫有更為複雜的要求,我們希望主角晚上看到月亮變成魁梧的狼人,希望青蛙變成帥氣的王子,類似這些情況,我們需要將網格狀態混合,或通過平滑方式從一幀合併至另一幀的不同狀態中去,這一過程稱之為變形動畫,或形狀混合,實際上,該方案依賴於動畫關鍵幀之間網格的對應定點,以及中間幀不同形態見得混合結果,該方案計算量較大,對效能會產生一些影響,但可以產生精美切逼真的外觀表現。

6.視訊動畫

Unity可播放視訊檔案,將視訊轉化為.OGV格式,即可將其作為資原始檔。根據此類檔案,Unity可將視訊視為網格物件上的動畫紋理進行回放。

7.粒子動畫

動畫模擬過程中經常會出現某些不具備特定形狀的非實體物件,例如火焰,花火,霧效,雲朵,這時候就是粒子動畫的主場了。粒子系統作為Unity的重要元件有著很強大的作用,通常可配置出令人驚豔的動畫效果。

粒子動畫

8.可程式設計動畫

一定時間內動畫屬性的變化來自於程式設計,即開發者針對特定功能編寫的程式碼,這樣的動畫,我們稱之為可程式設計動畫。也是我們最常見的動畫型別。當然多數時候,動畫效果都是通過設計師製作完成,程式碼僅僅在執行期間對動畫行為進行觸發和引導。


編寫程式碼實現動畫

下面通過例項來解釋Unity動畫中一些重要的概念,一致性動畫,運動向量,協同程式和動畫曲線。這裡我假設已經掌握了Unity的基礎知識,懂得Unity中視窗和元件的名稱所指,熟悉建立物體,建立指令碼並編寫簡單程式碼。

一致性動畫

我們建立這樣一個2D場景(所有技術均可以擴充套件到3D專案),一艘宇宙飛船正在關卡中勻速前進。這看起來很簡單,但是很酷不是嗎。
建立我們的飛船(其實是一個小方塊貼圖,可隨意更換),位置歸零,調色,建立指令碼Ship.cs掛載到飛船上,然後就如下所示:

ship

現在我們要飛船移動前進,修改update()函式,改變物體的position,讓其每一幀前進0.05個單位,因為幀率很快,所以這個速度絕對不算慢:

    void Update () {
        transform.position += new Vector3(0.05f, 0, 0);
    }

move

但是這裡有一個問題,我們知道update函式每一幀會呼叫一次,但是幀數是根據硬體和軟體環境有所不同的,所以對於FPS為70的機器,1s他會移動70 * 0.05=3.5個單位,而對於FPS為50的機器來說,他會移動50 * 0.05=2.5個單位,使用者的體驗就得不到同樣的保證,而且在對於多人遊戲來說更是一種不平衡,必須所有玩家都處於同步狀態!

解決方法就是一致性動畫————速度,時間和deltaTime

根據上述條件我們知道,因為幀率的不同我們不能用它來表示物體的移動,我們按照現實世界的規律可以採用速度——距離——時間公式,讓距離=速度*時間,對於1m/s的物體,5s就移動5米!這種思考就不會受到幀速率的影響。另外不同的計算機在時間上都是保持一致的,1s在所有裝置上不多不少,這時就要用到deltaTime。

deltaTime表示為Unity本地變數,作為time類中的資料在各幀中被更新,表示了據上一幀所經歷的時間,這是一個很重要的訊息,當乘以速度後我們就可以對速度就行縮放,進而將幀率不同的裝置上取得一致的速度。例如我的FPS大約為100,那麼我這一次執行update距上次大約為過去0.01s,所以deltaTime就為0.01,我的速度會乘0.01,另一臺機器FPS假設為50,那麼它的deltaTime為0.02,速度會乘0.02,我幀率雖然是他的兩倍,但因為縮放,最終效果是一樣的移動距離。這就是deltaTime的神奇之處。(雖然看起來沒啥變化= =)

fps

    //添加了速度變數,方便操作,因為幀率大約縮放了0.01,所以我的速度值乘以100
    public float speed = 5.0f;
    //...
    //用deltaTime對速度進行縮放
    void Update () {
        transform.position += new Vector3(speed * Time.deltaTime, 0, 0);
    }

deltaTime Move

(p.s 如果移動的很慢很慢,一定是你的Inspector視窗的速度沒變,Unity會自動儲存之前的值)

某一方向的運動行為

如果你看過星球大戰,那麼你一定知道真正飛船拖著炫彩的火焰尾巴在宇宙中盤旋,翻滾躲避鐳射,相比之下我們的飛船移動太過生硬了,肯定會被一發導彈就炸燬!現在我們要將他在任意方向上移動。

這裡需要用到向量這個概念,相信你不會陌生,因為向量既有方向,又有大小,這不正好符合我們的速度嗎。例如(1,0,0)代表x軸正方向移動,(0,-1,0)代表y軸負方向移動,我們甚至可以使用(1,1,0)表示在x軸和y軸的45°夾角方向進行移動。程式碼如下:

    //重新定義一個方向變數
    public float speed = 0.05f;
    //...
    //三者相乘    
    void Update () {
        transform.position += direction.normalized * speed * Time.deltaTime;
    }

我們在inspector視窗改變其方向,執行結果如下:

inspector

dir move

動畫曲線

但是這還不是我們想要的,我們不想要直線運動,而是要讓飛船在宇宙空間畫一個完美的曲線!或者速度從快到慢進行一個過度。這時候就到了動畫曲線出場了。

    //新增動畫曲線
    public AnimationCurve AnimCurve;
    //乘以動畫曲線
    void Update () {
        transform.position += direction.normalized * AnimCurve.Evaluate(Time.time) * speed * Time.deltaTime;
    }

這是我們Inspector窗口出現了可配置的曲線屬性,點選開啟曲線視窗就可對曲線進行編輯。曲線中橫軸表示時間,縱軸便是曲線的值,可以點選左下角的預製曲線進行快速配置,也可以在曲線上點選新增控制點,我們以建立常用的曲線為例:開始時速度遞增,結束時速度遞減,運動1s,其中開始點,和結束點均位於0點便是速度為0,靜止狀態,中間1表示達到最大速度。
我們在曲線中心插入控制點拖動到1,兩端控制點拖動到0,並移動控制點兩端的線段使其平滑,通常右鍵設定Auto,Unity會自動幫我們平滑處理,更多的操作參考官方文件。最終效果如下:

curve

curve move

協同程式

下面我們應用協同程式實現一個常用的動畫效果,朝目標平滑旋轉。這在遊戲裡也是常用的技巧之一,例如RPG遊戲中我們讓前進的角色向右走,角色會先把身體轉向右邊再執行行走動畫,如果在我們輸入指令后角色突然面向右邊了,就太詭異了。
以兩個方形為例,我們讓橘紅色方塊(假設是一輛坦克戰艦)始終瞄準飛船。新建Tank.cs指令碼如下:

    //瞄準的目標
    public Transform target;
    //旋轉速度
    public float RotateSpeed = 100f;
    // Use this for initialization
    //遊戲開始時執行協程
    void Start () {
        StartCoroutine(TrackRotation(target));
    }
    //旋轉協程
    IEnumerator TrackRotation(Transform target)
    {
        while (true)
        {
            if (target != null)
            {
                //找到旋轉方向
                Vector3 dir = target.position - transform.position;
                //轉化為Quaternion
                Quaternion newRotation = Quaternion.LookRotation(Vector3.forward,dir);
                //應用旋轉
                transform.rotation = Quaternion.RotateTowards(transform.rotation, newRotation, RotateSpeed * Time.deltaTime);
            }

            yield return null;
        }
    }

這段程式碼涉及的東西有些多,我們一一來說,IEnumerator即協同程式類似與多執行緒,多工機制,可以與其他處理一同進行,對於動畫而言,這是十分有用的特性,他至少包含一條yield語句,代表留空多長時間,上段程式yield return null表示我們跳過一幀然後再回到while迴圈。協程程式需要StartCoroutine()來開啟,用法與函式沒有太大不同。
至於Quaternion四元數我會專門用一章時間來介紹,因為旋轉是Unity中十分重要的操作之一,而且理解起來也不容易,等我寫好後會放在部落格上。現在只需要知道,我們找到旋轉方向後把方向轉化為四元數(一種的旋轉表示方法),然後讓Y軸始終指向dir方向即可。
效果如下:

rotate

其實這些只是Unity動畫系統中很少的一部分,涉及的知識也是很基礎的知識,要想深入瞭解動畫系統,需要投入很大的精力,但是別急,這次只是Unity動畫基礎知識講解和一些實驗,接下來我們一起學習其他的知識。


完整的工程專案在我的Github主頁

歡迎大家與我交流學習