在早前的文章中,我們用特別笨的辦法來實現了幀動畫(或者說“逐幀動畫”)。
玩家朋友想必有所耳聞,遊戲看似連貫、流暢的畫面實際上是GPU(或者CPU)在不斷地做算術題,然後把得到的圖片不斷放到顯示器上,這個過程是如此之快,以至於我們會認為看到的本來就是連貫的動畫。幀動畫也是類似地不斷替換顯示的圖片來達到動畫的效果。不過可以預見幀動畫需要藝術家繪製足夠多的幀才能達到流暢的效果。
在使用數字技術製作動畫時,除了幀動畫之外還有一種常用的動畫技術就是關鍵幀(key frame或keyframe,也作動詞)動畫。在以骨骼(rig)控制的動畫中尤為常見。實際上關鍵幀動畫不只是在遊戲、動畫中常見,在各種UI開發工具、Web開發中也大量應用。
關鍵幀,顧名思義是一段動畫中關鍵時刻對應的幀。在關鍵幀動畫中,我們通常會確定一段動畫的始末時間點,然後確定這兩個時間點上某個(或某些)屬性變化前後的值。而在這兩個時間點中間的值,就交給計算機給我們算出來。
Blender動畫製作界面掠影,我們稍後會在Godot中看到極為相似的界面
插值
在2D空間中,假設我要你在點(0, 0)和(2, 2)之間畫一條曲線段(直線段也是曲線段的一種),你會怎麼畫?
毫無疑問你有無數種畫法。不過假設,我需要你求得在x為0.5、0.25、0.75、1.25等值時,在你所畫的曲線上的點(的y值)——你可能就想畫點更規矩的曲線了。
線性插值
於是乎,你在這兩點之間畫了一條直線(段)——一條足夠簡單的曲線。你用上了小學學到的知識,求得這條直線的表達式為y=x。現在在0到2的範圍內,你可以根據給定的x和這個表達式求得對應的在這條線上的任意一個點(也就是對應的y值)。
我們根據給定的兩個點,然後在這兩個點之間(按照某種特定方法)找到了更多的點。恭喜你,你剛剛完成了人力線性插值。
這和動畫有什麼關係呢?
為了做一個簡單水平平移動畫,比如兩秒內把一個東西在水平方向上從50移動到150。那麼我們的問題就轉化成了這樣:為了把position.x從50變到150,在兩秒內的每一幀中應該將position.x設置為多少?
問題進一步轉化,假設在二維座標系中,x代表的是幀(或者說幀對應的時間),y代表的是某個東西的水平位置,並且我們知道過程中水平位置的始末狀態,如果要在兩秒內把50變成150,在x為0到2的任意一個值時,y應該為多少?於是我們用剛才學到的簡單的線性插值辦法,作過兩點的直線,然後代入時間求得y值即可。
在實踐中,我們可以用GDScript的lerp(linear interpolation的縮寫)函數來進行線性插值:
看起來似乎有點複雜。不過這裡做的事情就是把這個sprite在兩秒鐘內向右平移100個單位(從50到150)。
首先解釋一下賦值用的等號右邊的反斜槓是什麼意思。如果你發現你的表達式太長太複雜,寫在一行裡面特別長的話,就可以用反斜槓來換行。下一行的內容會被視作同一行的內容。
此外還需要注意,lerp函數可以接受各種類型的參數(其參數類型為Variant),但是你必須要保證各參數類型相同可以插值。你也可以選擇用lerpf等變體來確保參數類型。
lerp函數有三個參數,前兩個參數分別對應插值的起點和終點,最後一個函數在GDScript中稱為weight(權重),某些地方又稱為alpha。但是無論叫什麼,它都是代表“現在要在哪裡插入一個值”。
這裡我想要的效果是在“兩秒鐘內”進行插值。因此每次進行插值時,我通過Time.get_ticks_msec函數獲得遊戲啟動至今經過了多少“毫秒”。msec代表millisecond即毫秒,1秒為1000毫秒。lerp函數的第三個參數權重一般來說應當是一個從0到1的值,為0時得到起始值,為1時得到終點值。所以這裡除以2000(2秒)並通過clamp函數限定取值範圍。也就是說這裡是在求得“現在在0到2秒範圍內的哪裡”,進而進行線性插值求得當前的位置應該走到哪裡。
於是乎我們就得到了這樣一個小動畫:
參數化曲線
從另一個角度來理解一下插值。
經過(0, 50)和(2, 150)兩點的直線方程為y=50x+50——這是所謂直線的斜截式方程,可能中小學數學課一開始講直線方程的時候就是這種形式。
直線有什麼特點呢,那就是它的斜率為常量(不變)。y=50x+50的斜率為50,它的方程用另一種形式來表達就是:
等號右邊的值實際上就是斜率。直線的斜率實際上就是直線上兩點的座標分量分別相減然後相除,上式也可以寫成:
如果(0, 50)視作起點,那麼任意一個點的斜率都應該和(150, 2)所在處的斜率相同。
根據分式、乘除法的運算法則,我們交換左右的這兩個部分的位置:
這種寫法基本上就是所謂的“兩點式”。由於這是一個等式,我們再加一個變量t擴展等式。這個t就是參數。現在直線的方程可以寫成這樣:
或者換個寫法:
這就是它的參數化方程。這個t就是lerp函數的第三個參數。對於整條直線來說,t可以為任意實數。如果限制它的取值範圍為0到1,那麼得到的對應的點就限制在x為0到2的範圍內,也可以說就是這個線段內。
如果把x=2t的2除到左邊去,就相當於我們在調用lerp時最後傳入weight參數時在做的的事情。
你可能會問為什麼Godot文檔中為什麼用這種形式解釋呢?
interpolation = A * (1 - t) + B * t
這裡的A是插值起點,B是終點,那麼我們代入我們的起點和終點也就得到了y=50(1 - t) + 150t,化簡一下也就是y=100t+50。
如果從座標(向量)的形式思考其實更直接。這個式子又可以視作(x, y) = (1 - t)(0, 50) + t(2, 150)。如果把直線的方向視作從(0, 50)到(2, 150)的話,它的方向向量就是兩點相減(2, 100)(沒事做可以把它單位化)。直線上任意一點和起點相減(連線)的方向向量應該和直線本身的方向向量平行,由此又可以得到一個方程然後參數化。從抽象上,我們可以想象這是一個滑條,最左邊就是起點,最右邊就是終點,t就是滑到哪裡了。
怎麼樣,數學很有意思吧。稍後我們還會看到參數方程。
不同的插值方法
正如我們前面問你的那樣,我們在兩點間可以畫出各種不同的曲線。插值也有各種不同的方法來進行。線性插值蘊含了“按恆定速率插值”的意思——我們在兩點間畫了一條直線,它的斜率是恆定的(它的導函數是常值函數)。我們會在後面的內容中講到,一些插值方式可以獲得變化的插值速率,從而可以得到“淡入淡出”的效果。
可以“被插值”的數據類型也不止標量類型,例如我們已經接觸了很久的Vector2也可以進行前面介紹過的線性插值,然後可以將剛才的代碼改成這樣:
不同類型的數據實際上會用不同的算法進行插值。像用來表示旋轉和方位的四元數又會以一種不同的方法插值。
用於製作關鍵幀動畫的節點
Godot中的AnimationPlayer節點可以用於製作關鍵幀動畫。你可以像我一樣做一個簡單的新場景來體驗它的功能。
無論如何,首先要給場景加入一個AnimationPlayer節點。現在注意到下方面板中的Animation,這個面板是專為AnimationPlayer準備的:
目前動畫編輯器中什麼都沒有。和之前做幀動畫的AnimatedSprite的動畫編輯器類似,我們可以在這裡管理不同的動畫片段。要新建一段動畫,點擊Animation按鈕選擇New:
如果你使用過其它的視頻/動畫編輯器,這個界面可能也不會太陌生,這裡借用一下官方文檔中標記好了的截圖:
中間的區域是時間線,藍色的箭頭加豎線,某些軟件中會稱其為Playhead,為了方便就叫它播放頭好了)代表著當前所在的時間。右側的秒錶圖標旁邊的輸入框用於指定動畫的長度(秒為單位)。默認為1秒。時間旁邊的垃圾回收(循環)圖標可以控制動畫自動循環播放。
左上方的播放控制按鈕太常見,不贅述。
下方的Snap(吸附)功能默認啟用,右側的輸入框可以調整吸附功能的基準,默認為0.1秒,播放頭只會停留在0.1秒的整數倍時間上。
放大鏡可以縮放時間線的數軸。
最左側的軌道區域我們便操作邊瞭解。
軌道
一段動畫包含多條軌道。類似於視頻編輯軟件,不同的軌道可以有不同的東西,在動畫播放過程中,不同軌道的操作會同步進行。
一段動畫至少要有一條軌道才可能有實質的內容。點擊Add Track(添加軌道):
可以看到軌道分為若干類型。不同類型的軌道會根據關鍵幀的設置對對應的數據隨時間變化進行插值。可以看到這裡也有涉及3D場景數據的軌道,AnimationPlayer是一個2D和3D場景都可以用的節點。它本身派生自Node。
比如我們想要做一個簡單讓Sprite移動的動畫,我們就需要改變它的position屬性,那麼我們需要選擇Property Track(屬性軌道)。在彈出的窗口中選擇Sprite節點並選擇position屬性。現在動畫編輯器中出現了一條軌道:
軌道欄展示了軌道控制的是哪個節點的哪個屬性。最右側的垃圾桶毫無疑問是刪除軌道。
關鍵幀
既然是關鍵幀動畫那麼肯定需要指定關鍵幀。在時間線的0處單擊右鍵選擇Insert Key(插入關鍵幀),此時軌道對應位置上會出現一個菱形(也可能是矩形):
這就代表這裡有一個關鍵幀。關鍵幀的圖標和軌道對應屬性名稱左邊的圖標,以及新建軌道選擇類型時左側的圖片都是對應的。
一個巴掌拍不響,關鍵幀動畫會在每兩個關鍵幀之間進行插值,因此我們在1秒處再插入一個關鍵幀:
現在的軌道是這個樣子。如果播放動畫,什麼也不會發生,因為這兩個關鍵幀上Sprite的位置都是一樣的。實際上兩個關鍵幀之間的直線就表示這段時間內這個屬性沒有發生變化。
由於目前Sprite的position屬性受AnimationPlayer控制,所以雖然你可以直接在場景中移動節點或者修改它的position屬性,但是它還是會變回軌道算出來的值。
軌道和關鍵幀都是和時間相關的。我們在編輯相關的值時要考慮當前所在時間。我們現在的問題是1秒時sprite的位置需要調整,那麼我們可以先把播放頭定位到1秒處。
接下來有兩個方法可以編輯此時sprite的位置。首先我們依然可以在2D視口中移動或者編輯檢視面板中的position屬性。但是這裡需要額外的操作來使得這個變化反映到關鍵幀中。
編輯完畢後看到檢視面板的這裡:
由於AnimationPlayer的存在,場景中各節點的檢視面板中可以受其控制的屬性編輯器右側會出現一個鑰匙圖標。點擊這個圖標就會在當前播放頭所在位置插入一個關鍵幀。由於1秒處已經有一個關鍵幀,所以這裡實際上是更新這個關鍵幀對應的position屬性值。另外提醒一句,如果你是在檢視面板中調整位置,那麼調整完畢後記得按一下回車,否則它會變回去。
另一種方法是直接在軌道上選中關鍵幀,在關鍵幀的檢視面板中編輯對應的值:
編輯完畢後,可以發現兩個關鍵幀之間的直線也消失了。現在播放動畫就可以看到這個簡單的動畫的實際效果。
自動插入關鍵幀
如果你覺得這樣編輯關鍵幀的值還是麻煩,那麼可以使用錄製功能自動插入關鍵幀。
有AnimationPlayer存在時,2D視口上方的工具欄中會多出幾個圖標:
當你在2D視口中對某個節點進行移動、旋轉、縮放後,可以通過這幾個按鈕快速插入關鍵幀。
最左邊的三個圖標分別對應位置、旋轉、縮放。只有點亮的圖標對應的屬性才會在插入關鍵幀時插入。這三個圖標可以點亮複數個(但是也只有這三個屬性顯示在這裡,畢竟你在2D視口中只能直接手動編輯這三個屬性),也就是說可以一次性插入到多個軌道中,比較方便。
點擊鑰匙按鈕即可把關鍵幀插入對應的軌道。不存在的軌道會自動提示新建。
啟用錄製(自動插入)功能後,一旦對應軌道的屬性發生改變,關鍵幀會自動插入:
貝塞爾軌道
貝塞爾軌道就是用貝塞爾曲線進行插值的軌道。那什麼是貝塞爾曲線,貝塞爾曲線是一種參數化曲線,利用它可以構造處各種平滑且千變萬化的曲線。實際上設計行業的朋友估計很難說沒聽說過貝塞爾曲線。
一般常見的二次(二階)貝塞爾曲線由三個(不共線)的點確定。假設這三個點為P0、P1、P2,貝塞爾曲線的參數方程就是:
粗體在很多時候用來表示向量,這裡的P都是二維空間的點(二維向量)。
光看這個方程有點讓人摸不著頭腦,實際上對給定的參數t,二次貝塞爾曲線上的點就是先用t在P0和P1、P1和P2上分別插值得到Q1和Q2,然後再用t在Q1和Q2之間插值,實際上上面的方程是化簡形式,把它寫開來就是:
我承認這兩個公式是我搬的維基百科的,我就懶得打了。
類似地,三次貝塞爾曲線由四個點確定,然後給定t又先兩兩線性插值得到三個點,然後三個點再進行二次插值。
為什麼不提“一階貝塞爾曲線”呢?因為在一階情況下就是簡單的線性插值得到的一條直線:
創建貝塞爾軌道的方法和其它類型軌道類似。這裡我們以sprite的rotation屬性為例進行學習。新建貝塞爾軌道,或者點擊Sprite2D節點的rotation屬性旁的鑰匙。如果你點擊的是鑰匙圖標,那麼在彈出的對話框中記得勾選Use Bezier Curves以新建一個貝塞爾軌道(右邊的選項不變)。
接著分別在0秒、0.5秒、1秒創建三個關鍵幀,對應的rotation值分別通過檢視面板設置為0、90、0度。現在看起來應該是這樣:
目前來說除了圖標是藍色的之外和一般的軌道沒有什麼區別。接下來點擊下方這個按鈕:
切換至貝塞爾曲線編輯器模式。默認情況下你的動畫編輯器在貝塞爾曲線模式下看起來應該是這樣:
此模式下只會顯示貝塞爾軌道,這沒有問題。但是右邊怎麼看都只有一條直線,說好的貝塞爾曲線呢。注意到時間線縱軸標線上顯示了一個100,表示這裡默認縮放到了可以顯示到100左右的範圍。然而,右下方的縮放滑條卻只能縮放橫軸,這是我不太理解的。
要縮放縱軸,需要按下Ctrl+Alt的同時用鼠標滾輪縮放。縮放到大概1.5左右就能看到曲線的真面目了:
縮放過程中你可能發現曲線變到下面去了,這個時候按住鼠標中鍵移動可以平移時間線。
當然你可能有個疑問,檢視面板裡面的Rotation不是以度為單位嗎,90度怎麼要縮放到這麼小才看得到。你可能還記得rotation屬性在GDScript中實際上是以弧度存儲的,所以0.5秒處的1.571弧度大約就是90度。
可以看出,貝塞爾曲線比起線性插值的直線要平滑不少。你還可以用上面的控制點來手動編輯貝塞爾曲線來達到想要的效果(此時最好關閉吸附功能)。右鍵菜單中也有常見的貝塞爾曲線編輯選項。這裡你可以自行實驗,不贅述。
現在你就可以嘗試做一些自己想象中的小動畫了!
復位動畫
每次我們打開有AnimationPlayer的場景都會看到受動畫控制的節點好好地呆在原位,保持著一個起始狀態——即使我們之前做了若干編輯,或者把播放頭放在動畫的某個中間位置。
這實際上是一個特殊的動畫在起作用。這個動畫是自動為我們創建的RESET動畫(這個名字是大小寫敏感的,比如叫reset不會被視作這個特殊動畫)。場景重新打開時,會根據這個動畫設置的值,來重置相關受動畫控制的屬性值。
如果你關閉場景重新打開,選中AnimationPlayer,可以看到當前激活的動畫就是RESET動畫:
這個動畫基本上就只有一瞬間,且只有一個關鍵幀,這個關鍵幀上的屬性值就相當於初始值。你也可以像編輯一般的動畫那樣編輯這個關鍵幀的值來調整初始狀態。
為一個尚不存在對應軌道的屬性創建軌道或關鍵幀時會彈出這個窗口:
Create RESET Track(創建RESET軌道)選項就是指的要不要在RESET動畫中也給它創建一個軌道,也就是說要不要在必要時復位這個屬性。
控制動畫播放
AnimationPlayer的動畫可以在遊戲中自動播放,點亮這個按鈕後,啟動場景你就可以看到動畫自動播放了:
但是你不一定需要這樣,我們自然也可以在代碼中控制它的播放。AnimationPlayer和AnimatedSprite類似,提供了一個play方法,傳入動畫的名稱即可播放:
給主菜單添加動畫
我們試著給主菜單添加一個動畫。比如我們想讓標題有一個從畫面外落下來的感覺,那麼我們就可以對它的位置做一個簡單的動畫,有了前面的鋪墊,這裡確實足夠簡單,也不需要新知識,所以我就不細說了。
此外我們可以給菜單按鈕做一個逐漸顯現的效果——也就是調節它的透明度。那麼透明度是哪個屬性呢?實際上CanvasItem都有一個Modulate屬性,它實際上就是一個顏色屬性可以直接“添加到”CanvasItem自身的顏色上。
Godot中的Color類型就是用來表示顏色的。Color的RGBA四個屬性對應的就是RGBA格式的顏色。其中的A就是alpha,相當於是透明度。
將modulate的顏色的A值調整為0就可以看到整個VBoxContainer就看不見了。Modulate屬性的調整會影響到該節點的子節點——這是我們想要的效果。如果你不想影響子節點,那麼就去調整Self Modulate。
自然,我們在動畫結束時把A值拉滿。
用AnimationPlayer對顏色進行插值時,時間線上會顯示對應的顏色變化情況,這是一個不錯的功能。
直接用代碼做簡單動畫
也許你的動畫足夠簡單,沒必要在編輯器裡點來點去,又或者有些關鍵幀的值你無法提前確定。這個時候我們可以考慮直接在代碼裡實現動畫。
當然我的意思不是用各種函數自己寫一堆插值代碼(當然也可以),此時Tweener會幫你的忙。
Tween來自inbetween一詞,指的就是在動畫製作過程中繪製中間幀的過程。
Godot中的Tween要比AnimationPlayer更加輕量化,更適合用代碼來製作一些簡單的動畫(或者單純地進行插值)。
要獲得一個Tween對象,Godot主要提供了兩種方法。一個是SceneTree.create_tween,一個是Node.create_tween。前者在調用此方法的節點被釋放時依然會繼續運作,而後者則不會。但是無論如何Tween都是場景樹控制的,一個tween可以對不同的節點進行控制。Node.create_tween等價於把調用tween的bind_node方法綁定到這個節點上。結合TweenProcessMode的設置,可以讓部分節點暫停時保持另一些動畫獨立播放。
當然這裡暫時不需要那麼細緻地控制,我們在根節點上的腳本就簡單調用create_tween好了(即Node的版本)。光是創建tween沒有什麼用,我們需要給它添加若干Tweener,tweener是表示具體的動畫操作的對象。
Tween的tween_property方法會產生一個PropertyTweener,顧名思義這是用來控制節點的屬性的。例如,這段代碼會讓sprite移動到(100, 200)處:
第一個參數是要對哪個節點進行控制——準確地說它不一定是一個節點,它可以是任意Object,這就是為什麼說這些所謂用來做動畫的東西也不一定就真的在做一些視覺效果,也可以單純地用來插值。第二個參數是屬性的路徑,雖然它是NodePath類型(使用$語法引用節點時也是用的NodePath),但是傳入字符串會自動轉換。NodePath相關的語法可以查看文檔中的NodePath部分。
這裡傳入的是position屬性。如果你只需要控制位置的某個分量,那麼需要用冒號來訪問屬性的屬性:
第三個參數是最終值。這裡我們可以注意到它和AnimationPlayer的區別,使用Tween時我們不用關心起始值,並且可以隨時指定任意最終值,因此對於運行時要播放的動畫來說是很方便的。但是要注意屬性和最終值的類型要匹配,否則運行時會報錯。
最後一個參數是時間(動畫長度),秒為單位。
實際上現在運行場景這個動畫就會自動播放。Tween的設計決定了創建它之後動畫就會自動播放,並且tween不應該被重複使用。
此外,默認情況下在tween上調用的各種tween方法是按順序執行的,而不是同時進行。例如:
是在sprite移動到位後才會開始旋轉。如果要讓tween上的tweener同時進行,可以調用Tween.set_parallel讓所有tweener並行執行。或者在調用tweener前先調用parrallel。
配置Tweener
tween_property會返回一個PropertyTweener,通過這個對象實例我們可以進行更進一步的控制。
from方法可以配置Tweener控制的屬性的起始值——如果你不希望從當前值開始插值的話。
set_delay是在開始執行tweener的操作之前延遲多少秒。
set_easy和set_trans可能是最有用的兩個方法,它們兩個的組合可以控制動畫的插值函數和淡入淡出效果。
Tweener的方法都會返回修改後的Tweener,這樣你就可以連續調用多個方法,比如:
我們就得到了一個設置了過渡效果的動畫:
這兩個調整過渡效果的方法的參數是一系列枚舉值,放一個cheatsheet在這裡:
相對值
默認情況下PropertyTweener會把tween_property的第三個參數作為插值的最終結果。而PropertyTweener的as_relative會講這個參數的值視作相對值,也就是說最終值實際上是根據其實值加上這個值求得的。
例如這段代碼就利用這一方法做了個有意思的小動畫:
思考一下再寫代碼驗證猜想!
其它Tweener
CallbackTweener就是在動畫中間調用某個函數,通過tween_callback方法創建,傳入一個Callable。
IntervalTweener會在動畫中插入指定的時間間隔,由tween_interval創建。
MethodTweener由tween_method創建,這個看似是最複雜的Tweener:
tween_method的第一個參數是一個Callable,第二、三個參數分別是插值的起點和終點,最後一個參數依然是時間。它的作用就是在每次插值時,以中間值為參數,調用傳入的方法。
你可能會問,如果想要調用的參數有一個以上的參數怎麼辦?第一,用lambda包裝一下:
第二,用Callable的bind方法綁定參數。注意,GDScript的bind會在Callable調用後再傳入綁定的參數,也就是說順序在這裡很重要。tween_method以中間值調用方法後,才會依次傳入用bind綁定的參數:
練習使用Tween
下面又來做個小練習。我們來為之前拾取道具提示做一個小動畫吧!
首先要考慮的是什麼時候播放。常見的效果是UI元素顯示出來的時候播一個小動畫,消失的時候也可以播一段。這裡我就只演示一半,做一個顯示出來的時候播的小動畫。
接下來,我們要考慮在誰身上tween,tween哪個屬性。我們的PickupTip控件為了方便,根節點是一個單獨的鋪滿整個畫面的節點,因此這裡只Tween一下容納信息的VBoxContainer。先添加對它的引用。
為了有那種“顯現出來的效果”,這裡以調整其scale屬性為例。當然你也可以按照自己的喜歡添加其它效果。
我們在主場景中的腳本是通過設置它的visible屬性來達到顯示/隱藏效果的,當然第一次顯示的時候需要實例化場景。我們希望在顯示出來的時候播放這樣一個放大出來的小動畫,因此我們就連接上定義在CanvasItem上的visibility_changed信號,當發現visible變為true時,我們就tween一下:
這裡我們把scale在0.3秒內從(0, 0)變到(1, 1)。為了便於開發過程中查看我們不直接設置它的scale初始值為0,而是在這裡調用from指定。
現在啟動遊戲,嘗試走到櫻桃上……
沒錯,遊戲遇到錯誤中斷了:
錯誤信息說的是無法在null上調用from。實際上可以在右邊看到除了self實際上我們那些引用的各節點都是null。
實際上,這是在我們第一次需要展示PickupTip剛實例化出來添加到場景中發生的錯誤。雖然此時場景構造了出來並且添加到了主場景中,但是此時它還沒有ready!(你可以在做個響應信號的方法中print一下is_node_ready方法的返回值)因此很多操作在這時無法安全運行。
這裡我們可以簡單地處理一下,直接用await等待ready信號發出再tween:
如果發現沒有ready,我們就等待ready信號。還記得await的用法嗎?
現在我們就得到了這樣一個小動畫:
你可能覺得有點奇怪,它看起來是從左上角變出來的——當然如果你接受這種效果也沒問題。
要調整這種行為,我們需要修改Control的Transform下的Pivot Offset(支點偏移)。這個屬性就是控制rotation和scale屬性是以哪個點為中心。對於Control來說它的默認值是左上方,所以這個動畫播出來就是這種效果。如果想讓它看起來是從中間飛出來,我們就把它的x座標改成寬度的一半:
注意,那個加號就是當前支點的位置。
現在我們就得到了這樣的效果:
你也可以根據自己的需要調整為其它值。
這一篇文章不知不覺寫了這麼多,但實際上也很難完全覆蓋這兩個方面的所有內容。如果你覺得哪裡不清楚或者一篇裡寫這麼多內容不太合適,又或者有其它建議,也歡迎評論指出。