Timeline擴展功能實踐指南
轉載於http://forum.china.unity3d.com/forum.php?mod=viewthread&tid=32842,介紹了timeline的軌道擴展
Timeline功能隨Unity 2017.1發布後,收到了大量相關反饋,我們發現很多開發者不希望只把Timeline用作簡單的序列工具。我們分享過文章《為Timeline創造性地設計腳本》,介紹如何利用Timeline的非常規用途。
本文將介紹如何使用Timeline來推動對話、支持分支功能以及連接遊戲的AI系統。
關於Timeline
什麽是Timeline?Timeline是一個線性編輯工具,用於序列化不同元素,包括動畫剪輯、音樂、音效、攝像機畫面、粒子特效以及其它Timeline。它大體上和Premiere?、After Effects?或Final Cut?類似,不同之處在於它為實時播放而設計。
Timeline一開始就以可擴展性為主要目標而設計,開發團隊在設計該功能時考慮到,除了內置剪輯和軌道外,用戶還會創建自制剪輯和軌道。所以用戶提出了很多有關在Timeline使用腳本的問題。
Playable API
Playables API是一組強大的API,它能讓你讀取和混合多個數據源。例如:動畫、音頻等,並通過輸出內容進行播放。該系統提供精準的程序控制,它的性能開銷較低,並且針對性能進行了優化。
Timeline是基於Playables API實現的。當開始播放Timeline時,會構建一個由Playables節點組成的視圖。它們會以PlayableGraph樹形結構進行組織。
如果想要在場景中可視化PlayableGraph,例如:Animator和Timeline,你可以下載PlayableGraph Visualizer工具。本文將使用該工具可視化自定義剪輯的視圖。更多關於Playable API和Animator相關視圖的信息,請閱讀:《Timeline功能亮點Playable API》。
演示項目
下文將展示四個簡單示例,介紹如何使用Timeline的擴展功能。首先會介紹在Timeline添加腳本的最簡單方法。然後再逐步增加更多概念,充分使用Timeline的更多功能。
本文所有示例內容你請訪問下面地址下載:
本帖隱藏的內容
鏈接: https://pan.baidu.com/s/13Uu4NcsZMQXCf-GeI8kVOQ 密碼: skzg
請註意,項目中將使用前綴來區分每個示例中的各個類,例如“Simple_”、“Track_”和“Mixer_”等。而在後文代碼中,為提高可讀性則省略了這些前綴。
示例1:自定義剪輯
第一個示例非常簡單,目標是修改Light組件上的顏色和亮度屬性,該組件帶有自定義剪輯。
創建自定義剪輯需要二個腳本:
1、一個用於存放數據:從PlayableAsset繼承
2、另一個用於處理邏輯:從PlayableBehaviour繼承
Playable API的核心原則是分離邏輯和數據。所以首先需要創建PlayableBehaviour,使用該API編寫行為,代碼如下:
public class LightControlBehaviour : PlayableBehaviour
{
public Light light = null ;
public Color color = Color.white;
public float intensity = 1f;
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
if (light != null )
{
light.color = color;
light.intensity = intensity;
}
}
}
|
該代碼實現了什麽功能呢?首先它確定需要修改Light組件的哪些屬性。而且PlayableBehaviour 擁有名為ProcessFrame 的方法可供重寫。
每次更新時都會調用ProcessFrame。在該方法中,你可以設置Light組件的屬性。然後為自定義剪輯創建PlayableAsset,代碼如下:
public class LightControlAsset : PlayableAsset
{
public ExposedReference<Light> light;
public Color color = Color.white;
public float intensity = 1.0f;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
{
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);
var lightControlBehaviour = playable.GetBehaviour();
lightControlBehaviour.light = light.Resolve(graph.GetResolver());
lightControlBehaviour.color = color;
lightControlBehaviour.intensity = intensity;
return playable;
}
}
|
PlayableAsset有二個目的。首先它包含剪輯數據,因為它會在Timeline資源中序列化。其次它會構建PlayableBehaviour ,最後呈現為Playable視圖。
第一行代碼如下:
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);
|
該代碼會新建Playable,為它附加自定義行為LightControlBehaviour。然後可以在PlayableBehaviour設置光的屬性。
那麽代碼中ExposedReference的作用是什麽?由於PlayableAsset 是個資源,它不能直接引用場景中的對象。此時ExposedReference 會充當一個約定,表示在調用CreatePlayable 時,會解析一個對象。
現在可以在時間軸中添加Playable Track,並右鍵點擊該新軌道添加自定義剪輯。將Light組件指定給該剪輯並查看結果。
https://v.qq.com/x/page/p07816563al.html
在這種情況下,內置Playable Track是個通用軌道,它能接收簡單的Playable剪輯,例如:剛創建的剪輯。對於較復雜的情況,需要在專用軌道上托管剪輯。
示例2:自定義軌道
第一個示例的特點是每次添加自定義剪輯時,都要指定Light組件到每個剪輯上,如果剪輯數量較多,該過程非常麻煩。可以使用軌道的Bound(綁定)對象解決該問題。
軌道結構圖
軌道上可以綁定對象或組件,這意味著該軌道上的每個剪輯可以直接在綁定對象上操作。這是個很常見的行為,同時也是Animation、Activation和Cinemachine軌道的工作方式。
如果想要修改多個剪輯上的Light屬性,可以創建自定義軌道,將Light組件作為綁定對象。創建自定義軌道需要擴展TrackAsset的腳本,代碼如下:
[TrackClipType( typeof (LightControlAsset))]
[TrackBindingType( typeof (Light))]
public class LightControlTrack : TrackAsset {}
|
這段代碼有二個屬性:
1、TrackClipType 指定該軌道將接受的PlayableAsset 類型。本例指定自定義LightControlAsset。
2、TrackBindingType 指定軌道要求綁定的類型,例如遊戲對象、組件或資源。本例為Light組件。
現在還需要對PlayableAsset和PlayableBehaviour 稍作修改,從而使它們適用於軌道上。下面註釋掉了不必要代碼行,以供參考。
public class LightControlBehaviour : PlayableBehaviour
{
//public Light light = null;
public Color color = Color.white;
public float intensity = 1f;
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
Light light = playerData as Light;
if (light != null )
{
light.color = color;
light.intensity = intensity;
}
}
}
|
PlayableBehaviour 目前不需要Light變量。本示例中,ProcessFrame 方法直接提供軌道的綁定對象,只需將該對象轉換為合適類型即可。
public class LightControlAsset : PlayableAsset
{
//public ExposedReference<Light> light;
public Color color = Color.white;
public float intensity = 1f;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
{
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);
var lightControlBehaviour = playable.GetBehaviour();
//lightControlBehaviour.light = light.Resolve(graph.GetResolver());
lightControlBehaviour.color = color;
lightControlBehaviour.intensity = intensity;
return playable;
}
}
PlayableAsset 不需要為Light組件保留ExposedReference 。該引用將由軌道托管,並直接交給PlayableBehaviour。
我們可以在Timeline中添加LightControl軌道,並給它綁定Light組件。現在給該軌道添加的每個剪輯都將根據該Light組件進行操作。
[url]https: //v.qq.com/x/page/t0781rsxun3.html[/url]
如果使用Graph Visualizer展示該視圖,效果如下:
[align=center] [attach]15184[/attach][/align]
上圖中剪輯顯示為右邊名字相同的五個方塊。可以把每個方塊看作軌道。然後所有軌道都進入紫色方塊表示的Timeline中。
請註意,Playable粉色方塊實際上是Unity創建的Playable混合器。所以它和剪輯的顏色相同。混合器的概念將在示例3中說明。
[b]示例3:用混合器混合剪輯[/b]
Timeline支持重疊剪輯,從而在剪輯之間創建混合效果或交叉漸變效果。自定義剪輯也支持混合效果。為了啟用混合功能,需要創建混合器,它能訪問所有剪輯數據,然後混合數據。
混合器由PlayableBehaviour派生,就像前面使用的LightControlBehaviour。實際上混合器還使用ProcessFrame 函數。關鍵區別在於,該Playable會通過重寫CreateTrackMixer函數在軌道腳本中聲明為混合器。
LightControlTrack腳本代碼如下:
[mw_shl_code=csharp, false ]
[TrackClipType( typeof (LightControlAsset))]
[TrackBindingType( typeof (Light))]
public class LightControlTrack : TrackAsset
{
public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount) {
return ScriptPlayable<LightControlMixerBehaviour>.Create(graph, inputCount);
}
}
|
創建該軌道的Playable Graph後,它還會創建一個新行為即混合器,並將該行為連接到軌道上的所有剪輯。
你還要將邏輯從PlayableBehaviour 上移動到該混合器中。因此PlayableBehaviour中的代碼較少。
public class LightControlBehaviour : PlayableBehaviour
{
public Color color = Color.white;
public float intensity = 1f;
}
|
該代碼只包含運行時中來自PlayableAsset 的數據。而混合器將擁有ProcessFrame 函數中的所有邏輯:
public class LightControlMixerBehaviour : PlayableBehaviour
{
// 註意:該函數會在運行時和編輯時調用。在設置屬性數值時要註意這一點。
public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
Light trackBinding = playerData as Light;
float finalIntensity = 0f;
Color finalColor = Color.black;
if (!trackBinding)
return ;
int inputCount = playable.GetInputCount (); //get the number of all clips on this track
for ( int i = 0; i < inputCount; i++)
{
float inputWeight = playable.GetInputWeight(i);
ScriptPlayable<LightControlBehaviour> inputPlayable = (ScriptPlayable<LightControlBehaviour>)playable.GetInput(i);
LightControlBehaviour input = inputPlayable.GetBehaviour();
finalIntensity += input.intensity * inputWeight;
finalColor += input.color * inputWeight;
}
trackBinding.intensity = finalIntensity;
trackBinding.color = finalColor;
}
}
|
混合器可以訪問軌道上的所有剪輯。本示例中,需要查看剪輯當前參與混合的所有亮度數值和顏色,所以要使用for循環在它們之中叠代。每次循環都要訪問輸入內容(GetInput(i)),並使用每個剪輯的權重(GetInputWeight(i))來構建最終數值,從而獲得該剪輯對混合內容的貢獻量。
假設要混合二個剪輯:一個剪輯貢獻紅色,另一個剪輯貢獻白色。當混合內容為四分之一時,顏色由0.25 * Color.red + 0.75 * Color.white計算而來,這樣會得到稍淡一點的紅色。
在循環結束時,要將整體結果應用到綁定的Light組件。得到如下內容:
https://v.qq.com/x/page/l0781srhmr7.html
可以看出,紅色方塊表示你編寫的混合器Playable,你可以完全控制它。這和示例2相反,示例2的混合器是由Unity默認提供的。
因為該視圖處於混合中間,第二和第三個綠色方塊都有白線連接著混合器,表示它們各自權重約有0.5。
請註意,不管何時在混合器實現混合,邏輯內容都取決於開發者。混合二個顏色很簡單,但混合AI系統中二個代表不同AI狀態的剪輯會產生什麽結果呢?是否會在UI出現二行對話?如何在定格動畫中混合二個靜態姿勢呢?
或許混合不是連續的,但它是以“階梯式”進行的,所以姿勢會以離散增量相互變形,例如:0, 0.25, 0.5, 0.75, 1。有了這個功能強大的系統,便可以得到各種有趣的情景。
示例4:為自定義剪輯設置動畫
作為本指南最後一部分,我們首先來回顧一下前面的示例,然後使用“模板”來實現另一種移動數據的方式。該方法的優點是,它能讓你設置模板屬性的關鍵幀,從而可以在Timeline中為自定義剪輯創建動畫。
在前面示例中有一個Light組件的引用,指向PlayableAsset和PlayableBehaviour上的顏色和亮度。該數據在PlayableAsset 的檢視窗口上設置,然後在創建視圖時,會在運行時復制到PlayableBehaviour 中。
該方法能有效處理數據,但會復制之後需要一直保持同步的數據。但復制這些數據很容易產生錯誤。或者你也可以通過在PlayableAsset創建引用,從而使用PlayableBehaviour 的“模板”概念。
因此首先如下重寫LightControlAsset :
public class LightControlAsset : PlayableAsset
{
public LightControlBehaviour template;
public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) {
var playable = ScriptPlayable<LightControlBehaviour>.Create(graph, template);
return playable;
}
}
|
LightControlAsset 現在只有對LightControlBehaviour的引用,而不是包含它的數據。代碼量比之前更少。
LightControlBehaviour 保持原樣即可:
[System.Serializable]
public class LightControlBehaviour : PlayableBehaviour
{
public Color color = Color.white;
public float intensity = 1f;
}
|
對該模板的引用會在選中Timeline剪輯時,自動產生如下檢視窗口:
完成該腳本後,就可以開始設置動畫了。
請註意,如果創建新剪輯,你將在軌道標題欄看到紅色的圓形按鈕。它表示該剪輯現在不需要添加Animator就可以設置關鍵幀。你只需點擊該紅色按鈕,選取剪輯,定位創建該關鍵幀的播放頭位置,然後修改該屬性數值即可。
你也可以點擊白色的方形按鈕,展開Curves視圖來查看關鍵幀創建的曲線。
這樣做還有的優點是你可以雙擊Timeline剪輯,讓Unity打開Animation面板,然後將該剪輯鏈接到Timeline。你可以註意到,當下圖的按鈕出現時,這些剪輯將被鏈接。
這樣在對Timeline和Animation窗口進行處理時,播放頭將保持同步,從而完全控制關鍵幀。你可以在Animation窗口修改動畫,在更便捷的環境中處理關鍵幀。
07.png
下載附件
在該視圖中,你可以充分使用動畫曲線和攝影表(Dopesheet),來優化自定義剪輯的動畫。
請註意,在通過這種方法設置動畫時,會創建動畫剪輯。可以在Timeline資源下找到這些剪輯。
結語
我們希望本文能作為實用向導指南,介紹通過Timeline可以實現的諸多功能,從而讓你使用腳本將Timeline提升到新的水平。我們也希望大家使用Timeline創作出更多優秀的作品!
更多Unity最新最全面的功能介紹盡在Unity官方中文論壇(UnityChina.cn)!
Timeline擴展功能實踐指南