Timeline

Unity2017のTimelineをやってみた

こんにちは。Unityエンジニアの新井です。
今回はUnity2017で導入が予定されている新機能Timeline(beta版)を動かしてみたいと思います。 Timelineについての記事はすでにいくつか見かけますが、導入方法やコンポーネントの名称などがbetaのバージョンによって大きく違っている箇所もあったので、最新のUnity2017.1.0b9(6/10現在)を使って改めて検証したいと思います。

Timelineとは?

一言でいうと、時系列にAnimationClipやAudioClip、Cameraを配置することができ、シーケンシャルにアニメーションを実行するエディタウィンドウです。 例えば、何かのアニメーションを終えて別のキャラクターアニメーションを発動し、そのアニメーションの途中のとあるタイミングでカメラの動きを変更し、カメラの動きのこのあたりでエフェクト出して…(続く)といったタイミングを考慮したカットシーンを作りたい時、Timeline上に置かれたクリップをドラッグしてずらなどして、再生タイミングや再生時間を調整することが簡単にできます。 また、配置したオブジェクトを動かして、かつ、その場でRecordingしてAnimationClip化することも可能です。

Recordingボタン
Recordingボタン

AnimatorControllerとの違い

UnityにはAnimatorControllerという仕組みが用意されています。個々のオブジェクト(GameObject, Camera, UIなど)のアニメーションステートを管理する場合には有効ですが、複数のAnimatorControllerを全体の時間軸に沿うように扱う事はできず、一つのAnimationClipに個々のオブジェクトを取り込んでキーフレーム制御する必要がありました。 その方法でもやりたいことが実現出来なくはないですが、凝ったシーンを作ろうとした場合、構造が複雑になり修正や追加開発に時間がかかったりAnimationEventが入っている場合など仕様が漏れがちで、イベントトリガーを進行管理に使っていた場合には大きなバグを引き起こす危険性も考えられます。

Animator State
Animation State

Timelineエディタの導入で、直感的なGUI操作でプログラマを介することなく制御したい複数のオブジェクトの表示タイミングや時間などを調整することが容易でシンプルになり、クリエイティブのイテレーション回数を増やす事が可能になったと思っています。

Timelineにできること

  • Timeline上でGameObject、カメラ、オーディオを制御できる
  • 時系列に並べて、再生・表示タイミングと期間をGUIベースで制御できる
  • Timelineで指定した期間断続的に発動するスクリプトを制御できる

導入方法

Assets > Create > Timeline か、または、Projectウィンドウ上で右クリックCreateボタンからも追加できます。
「New Timeline」というオブジェクトがProjectウィンドウに追加されます。

Timeline Asset
Timeline Asset

Projectウィンドウに追加された Timelineオブジェクト(New Timeline)をヒエラルキーにドラッグ&ドロップします。
(サンプルでは名前を”Timeline”にしています)
ヒエラルキーにドラッグ&ドロップしたTimelineオブジェクトを選択するとPlayableDirectorがAddComponentされていて、「Playable」ボックスに作成したNew Timelineがアタッチされている事が確認できます。

PlayableDirector
PlayableDirector

次に、編集用のエディタウィンドウを表示します。
Window > TimelineEditorよりTimelineWindowを選択します。Timelineに配置したClipはドラッグすることで表示時間を変更できます。

Clipはドラッグできる
Clipはドラッグできる
メモ
Timelineウィンドウは、他のオブジェクトを選択するたびに表示が途切れますので、ロックをかけておくことをおすすめします。
また、「Preview」が押されていない場合は、Trackにオブジェクトを紐付けることができません。Previewが押せない場合は、
一度noneを選択して、再度指定のTimelineを選ぶと押せるようになります。
Previewが押せない件
Previewが押せない件

Trackの追加

Timelineを利用するには各種オブジェクト(GameObjectやCameraなど)を「Track」と呼ばれるAssetにバインドする必要があります。
Trackには用途別にいくつかの種類があり、AddボタンからTimelineに追加することで利用可能です。

Trackの追加
Trackの追加

Track Group
各Trackをグループ化します。

Activation Track
設定したGameObjectの表示、非表示をおこないます。
Timeline上で右クリック > AddActivationClip をしてTimelineに追加したClipの長さ(時間)だけバインドしたオブジェクトをActiveにします。

Activation Track
Activation Track

Animation Track
AnimationClipを使用することが出来ます。既存のAnimationClipを追加したり、Timeline上で直接アニメーションカーブを付けることで、新規のAnimationClipとして保存することが出来ます。(Recording機能)
また、Clipのつなぎをクロスフェードさせることも可能です。

Animation Track
Animation Track

Audio Track
AudioClipを制御するために利用します。

Audio Track

Control Track
prefabをインスタンス化することが出来ます。インスタンス化するprefabと親のGameObjectを指定出来ます。
パーティクルの表示はこの機能を使うと良さそうです。

Control Track
Control Track

Playable Track
スクリプトを扱う事が出来ますが使い方はやや特殊で、あとでもう少し踏み込んで検証します。

Playable Track
Playable Track

Cinemachine.Timeline
カメラを扱うTrackになります。(後述)

サンプルをつくってみる

GameObject(サンプルはユニティちゃん)が動き、それをカメラが追うという簡単なサンプルを作ってみます。

走るユニティちゃん
走るユニティちゃん

Track作成の流れ

  • 動かしたいオブジェクトの設定(AnimationTrack)
  • タイムライン進捗で移動するようなスクリプトを準備(PlayableTrack)
  • 動かしたいカメラをAnimationTrackに設定(AnimationTrack)

Trackの追加とバインド

新しいシーンを作り、これまでの手順でTimelineをヒエラルキーに追加します。
Timelineウィンドウを開いてロックを掛け、AddボタンからAnimationTrackを追加し、動かしたいGameObject(ユニティちゃん)をバインドします。
AnimationTrackのTimeline上で右クリック>Add From Animationを選択し、再生したいAnimationClipを選択。(標準のRunning@loopを選択)

Add AnimationClip
Add AnimationClip

Playable Trackの導入

次にGameObjectの移動についてですが、いろいろな方法が考えられます。せっかくなので、Timelineのシークバーの移動にあわせて進む(あるいは戻る)ようにしたいと思います。
シークバーの移動で処理を加えるにはTimelineに関するAssetをスクリプトで制御する必要があり、Playable Trackで利用することができます。
また、PlayableTrackを使用するために、あらかじめ、以下2つのPlayableスクリプトを用意しておく必要があります。

PlayableAsset

Timelineのトラック上に配置し、設定した項目をScriptPlayableのCreateを使用しPlayableBehaviourに渡すスクリプト

PlayableBehaviour

TimelineのPlayableTrackにアタッチされたPlayabaleAssetの振る舞いが書かれたスクリプト。

Assets > Create > Playables より作成します。
ここでは名前をそれぞれTimelinePlayableAssetTimelinePlayableBehaviourにしました。

Playableスクリプトの作成
Playableスクリプトの作成

TimelinePlayableAsset.cs(コード全文)

using UnityEngine;
using UnityEngine.Playables;

[System.Serializable]
public class TimelinePlayableAsset : PlayableAsset
{
    public ExposedReference<GameObject> charaObj;

    // Factory method that generates a playable based on this asset
    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {    
        var behaviour = new TimelinePlayableBehaviour();
        behaviour.charaObject = charaObj.Resolve(graph.GetResolver());
        return ScriptPlayable<TimelinePlayableBehaviour>.Create(graph, behaviour);
    }
}

コードの説明
動かしたいGameObjectは”charaObject”をいう名前であらかじめアタッチしていますが、
TimelineAssetで使用する場合、アタッチ方法が新しい方法になります。

public ExposedReference<GameObject> charaObject;

ExposedReferenceは5.6から追加されたものです。PlayableAssetはアセットであるため、シーンの特定オブジェクトをEdit中(再生していない時)に参照することができません。ExposedReferenceを使用することで、実行時に実際のオブジェクトの参照を解決します。

参考URL:
https://docs.unity3d.com/560/Documentation/ScriptReference/ExposedReference_1.html

// もともとある、CreatePlayableメソッド内で、PlayableBehaviourを以下のようにインスタンス化
var behaviour = new TimelinePlayableBehaviour();

// Resolveメソッドを利用して、charaObjectの参照を実行時に解決します。
behaviour.charaObject = charaObject.Resolve(graph.GetResolver());

TimelinePlayableBehaviour.cs(コード全文)

using UnityEngine;
using UnityEngine.Playables;

// A behaviour that is attached to a playable
public class TimelinePlayableBehaviour : PlayableBehaviour
{
    public GameObject charaObject;

    public override void ProcessFrame (Playable playable, FrameData info, object playerData)
    {
        // PlayableTrackのClip上でシークバーが移動するたびに呼ばれ続ける(PrepareFrameの後)
        if(charaObject == null){ return; }
        var currentTime = (float)playable.GetTime() / (float)playable.GetDuration();
        var currentPos = Vector3.Lerp(CharaDefine.START_POS, CharaDefine.GOAL_POS, currentTime);
        charaObject.transform.localPosition = currentPos;
    }
}

Playable Behaviourにはあらかじめ規定のタイミングで呼ばれるメソッドが幾つか用意されています。
実行タイミングは以下になります。

Playable Behaviour
Playable Behaviour

ProcessFrameは毎フレーム実行されるメソッドです。

var currentTime = (float)playable.GetTime() / (float)playable.GetDuration();
var currentPos = Vector3.Lerp(CharaDefine.START_POS, CharaDefine.GOAL_POS, currentTime);
charaObject.transform.localPosition = currentPos;

現在時間 / 全体Durationでシークバーの位置を0-1で取得し、GameObjectのポジションを算出しています。

2つのPlayableオブジェクトを作成したら、Addボタンから、Playable Trackを選択し、追加されたTrackのTimeline上で右クリック>Timeline Playable Asset(作成しておいたPlayableAsset)を選択します。

Playable Assetの選択
Playable Assetの選択

最後にカメラの位置の調整をします。
カメラの移動・回転は、Timelineで直接Recordingしてみます。

AddボタンからAnimation Trackを選択し、Cameraを適用します。
Recordingボタンを押すと、ボタンが点滅し、CameraのPlayableAsset領域が赤くなりRecording…表示になります。
AnimationClipのカーブをいじる要領で動きをつけることができます。

メモ
RecordingするとProjectウィンドウのTimelineの配下にRecordedという名前でClipが作成されます。
このClipは直接Deleteすることができません。Deleteしたい場合は、Timeline.playableファイルを
テキストエディタで開き(YAML形式)、該当する名前を探して一連を削除することで、ClipもDeleteされます。
また、Timeline内のRecordedClipを複製(Cmd+D)することで、Timelineから外に「コピー」することが可能です
Recorded Clip
Recorded Clip

Timelineはこのようになりました。

Timeline 結果
Timeline 結果

このような感じでカメラのレコーディングと、Runの特定タイミングをスロー再生するようにしました。
スロー再生は、TimelineでスローにしたいAnimationClipを選択し、インスペクタパネルで、SpeedMultiPlierの数値を小さくすることで変わります。(デフォルトは1)
ただ、サンプルでは再生はスローですが、移動量はそのままなので完全なものを作るにはもう少し工夫が必要です。

また、カメラが追うという処理を入れると、タイミングと位置合わせを調整することが思ったように行きません。
カメラ自体を特定オブジェクトにLookAtするようなスクリプトをかけば解消されますが、Unity2017でリニューアルするCinemachineアセット (ベータ版公開中) を利用すれば、カメラが被写体のどこを映すかを設定出来たり、設定した対象とカメラの位置を同期したり、カメラのショットをもっと便利に作成することができます。

Cinemachine Base Rig
Cinemachine Base Rig

Timelineとの親和性も非常に高いので、次回はCinemachineアセットver2.0を導入し、TimelineでCinemachine.Timeline (Track)を 使ってみたいと思います。