我並非製作動畫專業,亦非程式專業,以下僅為查閱各個網站以及稍微實驗過後的歸納觀點。
-----
AE(AfterEffect)裡的程式部分有個很有趣的詞彙——time,精確來說並不是這個詞彙很有趣,而是它的用途很有趣。
當你在程式碼裡寫time的時候,它表示一個數字——「從動畫開始處到此處所經過的秒數」,如果你在動畫中用一個文字框來顯示time的話,就可以在動畫撥放的過程中從文字框裡看到此刻動畫已經撥放了幾秒。
據我個人所接觸過的程式軟體,比較沒有類似這樣功能(以及它的應用)的詞彙,其他程式裡也會有關於time的東西,但那通常是指現實世界的幾點幾分,或是創造一個讀秒器timer,但不太可能會有這篇文章所談的用法。
當然AE能有這種功能也是基於動畫時間軸的觀點。
要見識time的有趣之處,需要再介紹一個詞彙——value,當你在一個屬性打Expression的位置打value時,它表示該屬性在當前時間的值,這個值可能是一維的,如轉動角度、透明度;也可能是二維的,如x-y座標值(或三維的空間座標)。
舉例來說,如果你原本有設定一個透明度為50的圓,你在該圓的透明度打Expression的地方輸入value+10,畫面呈現的效果就會是一個透明度60的圓。
這裡有個會令人困惑的點,既然無時無刻value都在讀這個透明度的值,那它不會讀到60嗎?畢竟畫面上我這個圓的透明度是60而非50啊?但它如果讀到60,它呈現出的透明度value+10不就該是70嗎?所以透明度會越來越大?
答案是不會,這點我查了很多資料才比較有搞清楚,感覺這方面的學習資源並不多,連Adobe官網也沒有說得非常好懂。
在AE中,一個屬性的值可以想成有兩個階段,一個是其原本的值,這個值包含keyframe(關鍵影格)作用的結果;另個值是前者經過Expression作用後所得到的值。
當你在屬性中打value的時候,讀到的是前者的值,而非後者(經過Expression作用後)的值。
所以如果你原本有設一個透明度的keyframe從0跑到50,那麼在Expression中輸入value+10,你最終動畫在那段時間中呈現的就會是透明度從10跑到60的動畫。
-----錯誤內容----
接著就可以來介紹一個函式(或說「方法」)——valueAtTime(time,preExpression)
這個函數有兩個輸入值,分別是:
欲讀取的時間time
是否要讀取Expression作用之前的值(預設值為否false)
會回傳此屬性在你所指定的時間時的值。
當然後面第二個輸入值就是控制你得到的值是Expression作用前或作用後的。
-----錯誤內容----
確實有兩個輸入值的valueAtTime函式,但是並不能在Expression中使用,只有在Script中才能使用。
Expression中的valueAtTime只能輸入第一個時間的輸入值。
接著就可以來介紹一個函式(或說「方法」)——valueAtTime(time)
其輸入值time是你欲讀取的時間,此函式會回傳此屬性在你所指定的時間時的值。
(至於是讀取到Expression作用前還是後的值呢?這由AE決定,後面會提到)
這個函式可以搭在其他圖層的屬性的後面,例如:thisComp.layer("m").position.valueAtTime(time)
你就可以讀到名為m的圖層在當前時刻position(位置)的值。
所以如果你創了一顆球,透過keyframe或其他Expression使它移動,那你可以創另一顆球,在它position的地方去讀原本那顆球在當前時刻位置的值,那麼你所創的另顆球就會跟著原本那顆球同步移動。
但這通常不是作動畫的人要的效果,比較有可能要的是「跟隨」的效果,也就是那顆球往前跑,我這顆球在後面追著(或利用透明度製作殘影效果)。
那要怎麼樣讓我新創的球的位置追著原本那顆球跑呢?這就是time的有趣之處。
改為輸入:thisComp.layer("m").position.valueAtTime(time - 1)
現在你讀到的位置不是另顆球在當前時刻的位置了,而是另顆球在1秒鐘之前所在的位置。
所以在動畫中,你這顆球所在的位置,永遠是另顆球1秒鐘前所在的位置,這就實踐了「跟隨」的效果。
這件事情很有趣,在其他程式軟體中,要讀取一秒鐘前的資訊,你通常要自己寫一個空間去記錄;
但在動畫中,只要電腦先把另一個圖層的動畫過程給計算完,其他的圖層就可以「讀取該圖層某個屬性在某個時間的值」,並透過那個值來決定自己本身的變化。
-----錯誤內容----
補充說明一下,上面提到value所指的是此屬性在此刻尚未經過Expression作用的值,所以我猜測(我沒實驗過)在一個屬性中輸入value跟輸入valueAtTime(time,true)是等價的(不明白我在說什麼的往上翻valueAtTime兩個輸入值的含意)。
-----錯誤內容----
由於無法輸入第二個輸入值,故在Expression中value是等價於valueAtTime(time)。
有趣的是這個時候valueAtTime(time)指的是經過Expression作用"前"的值;
但如果你寫的是其他圖層某個屬性的valueAtTime(time),得到的會是該屬性經過Expression作用"後"的值。
再來看另一個「震動」函式(方法)——wiggle(freq, amp, octaves=1, amp_mult=.5, t=time)
有5個輸入值(通常只會用到前兩個):
freq頻率,表示每秒鐘振動的次數
amp振幅,表示震動的幅度
三、四項我也不太瞭解,主要跟震動的複雜程度有關,裡面有寫好預設的值(1跟0.5),如果不知道要輸入什麼就輸入預設值。
第五項就又是time了,預設為當前時間。
一個「震動」的效果怎麼又跟當前時間有關了?
當你在position屬性中輸入wiggle(2,10)(後面省略不寫的項會自動視為預設值)時,動畫會呈現這個圖形的位置每秒震動2次,且振幅為10。
這跟時間有什麼關聯?
要明白這件事情,就不能只把wiggle看成是一個震動的效果。
首先wiggle會根據該屬性回傳一個值,如果是position就會回傳二維座標值。
而以下是我個人的推測(正如我所說,我查不太到官方資料,只能從各種網路教學應用裡去解讀)。
wiggle函式可以分成兩個部分:
第一個部份,它會去讀value(你如果設定移動的keyframe,且使用wiggle,你會看到該物體隨著keyframe移動的過程中一邊震動著),也就是當前該屬性的值。
第二個部份是,它會根據隨機種子(randomSeed)生成一段以時間為變量的函數,你可以想像成類似心電圖的跳動圖形,橫軸是時間,縱軸對應到正負振幅之間的值。(我描述的是一維的情況,但你可以依此類比二維座標的情況,只是變成有兩個心電圖,一個對應到x軸的變化,另個對應到y軸的變化)
而wiggle就是根據當前時間,回傳第一個部份加上第二個部份的值。
所以wiggle並不是時時刻刻隨機一個震動的結果給你,而是在你使用這個函式時,它就預先生成了一段符合你要求的頻率與震幅的函數(以時間為變數),然後在動畫中根據你的時間參數,讀取出該時間的震幅。
瞭解這個點以後,我們可以來看如果去修改wiggle的第五個輸入值「時間」會有什麼效果。
例如在position中輸入:wiggle(1, 10, 1, 0.5, 0)
(注意第五個值輸入0)
那麼動畫的結果是,你的圖形並不會震動,因為你任何時刻都只讀取wiggle第二部份在0秒時的值,等於你函數的變數永遠都代0進去,結果當然就是一個定值。
而輸入:wiggle(1, 10, 1, 0.5, time*2)
(注意第五個值輸入time*2,是當前時間值乘以2倍的意思)
那麼動畫的結果並不會每秒震動1次(雖然我們在第一項頻率的地方寫1),而會每秒震動2次,意思是,雖然wiggle創造出的第二部份的函數是每秒震動一次,但是我們以兩倍的速度讀取它,實際時間0秒的時候我們讀到函數0秒位置的值,但實際時間1秒的時候我們讀到的是2秒位置的值。
結果雖然實際時間只經過了1秒,但畫面上已經經過了該函數的0~2秒鐘的2次震動,所以看起來頻率變成2倍了。
而輸入:wiggle(1, 10, 1, 0.5, time%3)
(注意第五個值輸入time%3,「%」是取餘數的意思,所以time%3是當前時間除以3的餘數,在AE的程式語言中,小數也可以除以某數得到小數餘數,如3.5%3=0.5、4.32%3=1.32)
那麼動畫的震動並不會是完全隨機的,而會是每三秒重複一次,因為每經過3秒,time%3就又會從0開始跑到2.9999...,而我們知道同一個wiggle函式在輸入相同時間時,輸出的結果是一樣的,所以每經過3秒,動畫就會瞬間跳到第一個瞬間震動的位置,然後從那裡開始依相同軌跡移動。
但要注意的是,光是這樣的程式碼,並不能實踐「迴圈震動(looping wiggle)」的效果,這個效果的意思是震動會繞著一個相同的軌跡不停地跑(但這個軌跡是隨機生成的)。
不能實踐的原因是,雖然wiggle(1, 10, 1, 0.5, time%3)會一直重播3秒間的震動軌跡,但是這個軌跡的開頭跟結尾未必會接在一起,所以每經過3秒就會有一個斷點(從一處突然跳到另一處)。
要實踐迴圈震動的效果需要一些其他的技巧,可參考:
https://www.motionscript.com/design-guide/looping-wiggle.html
(基本上我絕大部分的理解都是從這個網站上的資料獲取的)
說到這個網站,裡面某篇文章有提到一個wiggle的用法不可行,那就是利用slider去控制wiggle的頻率,如果你在動畫中設置了一個值逐漸變大的slider,然後你試圖寫類似wiggle(slider,10)的寫法(當然實際上程式碼不是這樣寫,我只是說你想讓震動的頻率越來越大),那麼動畫的結果很可能不如你預期。
因為像我上面說的,wiggle並不是隨時根據機率隨機一個震動的效果給你,它只是去讀一個已經隨機出的函數在該時間的值,如果你不停地改變頻率的話,那wiggle大概(至少以我的猜測)會生成非常多個依不同頻率震動的函數,而不同時刻讀取不同函數在該時刻的值,結果就是變得一團混亂。
但如果你的slider是保持一個定值,然後變大到另個定值、變小到另個定值(hold keyframe),那問題就不大。
要實踐改變震動頻率的寫法是,將slider的值乘在後面的time上,也就是隨著slider越變越大,讀取time的速度就跑得越來越快(但仍是讀取同個函數),實際上的效果就會是震動頻率越來越快。
而如果你在網路上搜尋一種同步震動的效果,也就是多個圖形依相同的軌跡震動(但各自在不同的起始點),它的作法是在各個圖形position中的wiggle函式前加上seedRandom(1)(或其他數字皆可,重點是每個圖形要輸入相同的值),那麼各個圖形wiggle的震動軌跡就會是相同的。
這是因為電腦中並沒有真正的隨機,任何隨機生成數字的程序,都只是依一個種子數字,然後經過一些複雜運算,得出一個看似隨機的數字或數列。
wiggle也是如此,它的隨機也需要一個種子數字,如果你在不同圖層的wiggle(當然前提是頻率跟震幅等等的都要設定相同)前設定相同的seedRandom數字,那麼這些wiggle所生成的「第二部分(震動的那部份)」的函數就會是相同的。
而隨之而來的小問題是,如果不事先設定seedRandom會怎樣?答案是AE會自動將每個圖層的wiggle的種子數字設為不同的(例如可能以圖層的index作為種子數字),這確保了不懂程式的人即使用了多個wiggle,也不會產生相同的震動效果。
-----錯誤內容-----
題外話,據我上述的論點,同步震動也可以用其他方式來實踐。(雖然不一定是更適合的寫法)
假如有個圖層m的position屬性寫入了wiggle(2,10),
那麼因為valueAtTime可以根據第二個輸入值讀取「經Expression作用前或後的值」,只要用
valueAtTime(time,false)減去valueAtTime(time,true),這個值就會是當前時刻該屬性的震動變化量(即變化後的位置減去變化前的位置)。
我只要在另個圖層的position裡寫:
value + (thisComp.layer("m").position.valueAtTime(time,false) - thisComp.layer("m").position.valueAtTime(time,true))
就可以把名為m的圖層的position中wiggle震動效果複製到我現在這個圖層的position裡。
事實上這個寫法應該可以完全複製另個圖層經過Expression前後的變化效果。
(不過我並沒有實驗過這個寫法,如果實際上不行的話我會很訝異(嘆))
-----錯誤內容-----
數學的概念上是對的,但在Expression中不可行(理由同樣是因為Expression中不能輸入valueAtTime的第二個輸入值)。