Unityの タイムライン(Timeline) が良いの自分なりにまとめてみる

この記事は[Unity Advent Calendar 2020](https://qiita.com/advent-calendar/2020/unity) 15日目の記事です。昨日は [@tan-y](https://qiita.com/tan-y) さんの「[Rust で Unity Native Plugin を実装する](https://zenn.dev/tan_y/articles/d90b0633fae0b798c120)」の記事でした。

※ タイムラインの表記ゆれは修正してないです

# はじめに
自分は、趣味でUnityを使って友人とゲーム制作をしています。

基本的に、友人主導のプロジェクトでいつもは基本的な大枠を友人がつくって自分が細かいところに修正を入れたり、改善する感じの役割でした。しかし、次を作るものは大枠を自分が作ることになったので、いままで課題となっていた、「**シナリオゲームの演出部分のプログラムが読みにくいのでなんとかしたいなぁ**」を改善すべく、Timeline機能をつかってみることに。

自分が根っからのエンジニアで、友人がデザイナーよりなので、演出を **Unityの機能だけ** でなんとかしたい気持ちありました。タイムラインはそれを叶えられそうなものでしたので、自分でまとめておきたいと思った次第です。

実際にこんな感じのものをつくりました。 ↓↓
![test1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/9ef0c342-b6ea-3b6f-f17c-752ade8d6cd6.gif)
この記事では、**UnityプロジェクトへのTimelineの導入から自作の`PlayableBehaviour`を作るところまで** をまとめて、以下の項目については追々更新して行こうと思います。

- Unity上での挙動とか(仕組み?)
- Playableについて
- Playable APIとかPlayable Graphとか
- PrefabとかGameObjectとの参照のこと
- PlayableBehaviourのブレンド
- Signal&Maker
- Timelineの流用

# タイムラインとは
まず、タイムラインとは一体何者なのかを明らかにしましょう。

[Unity Document タイムライン](https://docs.unity3d.com/ja/2018.4/Manual/TimelineSection.html)
> Unity のタイムラインを利用して、映画的コンテンツ、ゲームプレイシーケンス、オーディオシーケンス、複雑なパーティクルエフェクトを作成できます。
> Unity のタイムラインで作成するカットシーン、映画的コンテンツ、ゲームプレイシーケンスはそれぞれ、タイムラインアセットとタイムラインインスタンスから構成されています。 Timeline エディターウインドウ では、タイムラインアセットとタイムラインインスタンスの作成や修正を同時に行えます。

[Timeline で手軽に演出を構築しよう](https://unity3d.com/jp/unity/features/editor/art-and-design/timeline)
> 自分の思い通りに、リッチでシネマティックなコンテンツ、カットシーンやゲームプレイシーケンスを作成しましょう
> アーティストやデザイナーに最適
> Timeline を使うと、コンテキスト内で作業することができ、コンテンツが思った通りの形になるまでイテレーションすることができます。 これは強力でユーザーフレンドリーなツールです。以下のことができます。
> - リッチなコンテンツを作成： ゲームオブジェクト、アニメーション、サウンド、パーティクルなどのシーン要素を用いてリッチなコンテンツを作成する
> - 作成、演出や調整などを行う： アニメーション、カメラ、オーディオ、および、その他のあらゆるゲームオブジェクト
> - コンテンツをより速く作成： 使い慣れたマルチトラックインターフェイスを用い、オブジェクトを「ドラッグアンドドロップ」して、アニメーションを制御して記録、リッチなシーンを素早く作成する

要は **Unityのシーン空間をスタジオとして考えて、タイムラインウインドウでカメラやアニメーションやGameObjectを制御できる** ものでしょう。
(動画マンならAdobe After Effectsで映像を作るような気持ち)

## タイムラインのここがいい
タイムラインというだけあって、すべてが時系列でわかりやすいです。

**Before**: 今までの演出は、コルーチンを使ってyieldでタイミングを図ってAnimation再生したりでした。これではすべての動きをプログラムからは想像することは難しいですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/d23de387-b1c2-06e2-0724-652965763c97.png)
**After**: シナリオテキストやアニメーション、GameObjectなどの挙動のタイミングが一目でわかります。並行で行われる動きもこれらならわかりやすい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/7c4973a0-5c23-12b7-cad6-736fa77f2f28.png)
**Unity Editorからタイミングも長さも変えられる** のは最高！！
![test3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/ea86598c-d044-b1a1-15f6-c193fb88cc01.gif)

Unity EditorでPlayをしなくても確認できるのも良い!これなら任意の時間から演出を確認したり、好きな再生速度や1フレームずつ確認ができるのでゲーム制作がはかどります!
![test4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/4b755212-2f62-eb79-4703-50d2116ceeba.gif)

## タイムラインのここは良くない
これで、演出をすべてプログラムで組み上げるみたいなことから開放されると思いきや、Unityのタイムラインでは **プログラムを作成しない** と以下の基本トラックしか使えない。( [Default Playables](https://assetstore.unity.com/packages/essentials/default-playables-95266) を追加すると表現の幅が増えます )

|トラックの種類||
|---|---|
|Activation Track|対象オブジェクトのアクティブ化/非アクティブ化|
|Animation Track|対象オブジェクトのモーション制御/移動/回転など|
|Audio Track|オーディオ制御|
|Control Track|オブジェクトの生成|
| ~~Playable Track~~ | ~~スクリプト処理~~ 従来のMonoBehaviourをタイムラインに組み込める?|

つまり、凝った表現にはどうしてもコーディングは避けられないですね。(Adobe Premiere Proのエフェクトコントロールみたいにできるといいのかな、 **これでも十分な機能だとは思う**)
 
また、Unity Editorでは問題なく表示されるTimelineですが、実態は、YAMLで記述された `.playable` ファイルです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/93151eb8-1ef6-afa5-f97c-de8d1701a46d.png)
複数人で開発するとなると、コンフリクトしそうですね。ここはしっかりプロジェクトで開発ルールや仕組みを整えなければなりません。コンフリクトを避ける一例として、 [Timelineから他のTimelineを呼び出して再生する](http://tsubakit1.hateblo.jp/entry/2017/10/16/214324) という技で担当ごとに作成したタイムラインをまとめて1つのタイムラインにするものもあります。

あとは、ちょっと前に ***Unity EditorでPlayをしなくても確認できるのも良い!*** とコメントを書いたが、場合によっては、Unity EditorでPlayをしなくても確認できるようにするには、すこし工夫が必要だったりします。

*Unity の Timeline をカスタマイズするための詳細#Play中、エディタ上、両方で同じ処理を実行する*
https://qiita.com/hadashiA/items/566f0d0222cb9a4e209b#Play中、エディタ上、両方で同じ処理を実行する

*UnityのTimelineTrackを実装する#初期値の記憶と復帰*
https://qiita.com/ousttrue/items/ac2f0b3847e76a36b1f0#初期値の記憶と復帰

# Timeline を作成
※ **2019.2.1f1** で検証しています。

さて、これからプロジェクトに新しくタイムラインを作成します。
タイムラインは次の2つの方法で作ることができます。

1. GameObjectを選択して、Timelineタブを開いてCreate
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/0cc8709b-c168-6c42-d2e4-e50f33aab65e.png)
1. 右クリック/AssetsからCreate > Timeline を選択(この場合はPlayable Directorが自動で生成されません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/d84771bd-d7a3-efaa-c625-f9070fe04691.png)

作成したタイムラインをアタッチしたGameObjectを選択するとTimelineタブに作成したタイムラインが表示されます。(簡単ですね!)これで、様々なトラックやクリップを並べてリッチなコンテンツを目指していけそうです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/15335143-d023-6426-bc79-2dbe73982f5f.png)

タイムラインでゲームのどの演出/ロジックを組むかはプロジェクトしだいなので、この一つのタイムラインで、一つのシーンのすべてを作り込むのもヨシ！一つの小さなカットシーンや演出を作るのもヨシ! **けれど、何でもかんでもここに投げ込むのは良くないので、ルールは必要そうですね。**

Unity EditorのTimelineタブから編集できる部分はいろんな方がまとめているので、この記事では割愛して、参考にした記事をまとめておきます。

*Timelineの基本機能とCinema Directorとの違い#4-代表的な５つの機能と使い方*
https://www.crossroad-tech.com/entry/Unity2017_2_0_timeline#4-代表的な５つの機能と使い方

*タイムラインの基礎を理解する【Unity】*
https://styly.cc/ja/tips/timeline_unity_kaki/

*【Unity】Unityのタイムラインの基本的な使い方を総まとめ！最短でTimelineを使いこなす*
https://light11.hatenadiary.com/entry/2018/09/18/235615

*【Unity】Timeline上でビデオクリップを再生したりフェードで切り替えたりする方法*
http://tsubakit1.hateblo.jp/entry/2018/08/30/200000

# タイムラインで使えるカスタムしたクリップを実装する
クリップ(図中赤枠)とは、タイムライン上のトラックの上に並んだ、 **その時間での挙動を表すようなもの** です。基本的には、アニメーションや動画、音楽を再生したり、プロパティを変更したりすることができます。実体は **PlayableAsset** で、タイムラインの再生に必要な情報を保持してくれます。タイムラインの編集ではこのクリップを並べて行きます！(動画編集で言う、素材を並べる)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/554ffe5f-3c8b-ec7a-1953-e31bbd0d7258.png)
これからいろんな演出を考えると、スクリプトで自由にクリップを作成できるに越したことないはず。

※ お手軽にControl Trackを使ってMonoBehaviourを作る感じでするのも良さそうだけど...
*【Unity】ITimeControlで、Timelineから"コンポーネント"を操作する*
http://tsubakit1.hateblo.jp/entry/2017/09/21/234138



## テンプレート
きっと、これから
[スクリプティングでタイムラインをより創造的に活用しよう](https://blogs.unity3d.com/jp/2018/04/05/creative-scripting-for-timeline/) のdemoプロジェクトから拝借して、最小限の何もしないクリップを作りました。

```C#:TemplatePlayableAsset.cs
using UnityEngine;
using UnityEngine.Playables;

[System.Serializable]
public class TemplatePlayableAsset : PlayableAsset
{
    [SerializeField]
    private ExposedReference<GameObject> templateGameObject;

    public TemplatePlayableBehaviour template = new TemplatePlayableBehaviour();

    // Factory method that generates a playable based on this asset
    public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    {
        var playable = ScriptPlayable<TemplatePlayableBehaviour>.Create(graph, template);

        // Get PlayableBehaviour
        var behaviour = playable.GetBehaviour();

        // Resolve Reference
        behaviour.templateGameObject = templateGameObject.Resolve(graph.GetResolver());

        return playable;
    }
}
```

```C#:TemplatePlayableBehaviour.cs
using UnityEngine;
using UnityEngine.Playables;

[System.Serializable]
// A behaviour that is attached to a playable
public class TemplatePlayableBehaviour : PlayableBehaviour
{
    private PlayableDirector director;
    public GameObject templateGameObject { get; set; }

    [SerializeField]
    public bool boolValue = false;

    public override void OnPlayableCreate(Playable playable)
    {
        director = (playable.GetGraph().GetResolver() as PlayableDirector);
    }

    // Called when the owning graph starts playing
    public override void OnGraphStart(Playable playable)
    {

    }

    // Called when the owning graph stops playing
    public override void OnGraphStop(Playable playable)
    {

    }

    // Called when the state of the playable is set to Play
    public override void OnBehaviourPlay(Playable playable, FrameData info)
    {

    }

    // Called when the state of the playable is set to Paused
    public override void OnBehaviourPause(Playable playable, FrameData info)
    {

    }

    // Called each frame while the state is set to Play
    public override void PrepareFrame(Playable playable, FrameData info)
    {

    }
}
```

テンプレートなのでファイル名にTemplateを冠していますが、このPlayable系のクラスとファイルは名前の付け方があるので、確認しておきましょう。
*UnityのTimelineTrackを実装する#名前の付け方*
https://qiita.com/ousttrue/items/ac2f0b3847e76a36b1f0#名前の付け方 

TrackAssetを作成していないので、このテンプレートクリップはPlayable Trackへ置きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/22b0ef02-bbec-c5a8-501f-55cd72952dcf.png)
Inspectorで確認するとこんな感じで、クリップにGameObjectを渡したり、値を設定できることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/0552a061-e919-50a1-46b0-4bcf542d4a28.png)

あとはPlayableBehaviourを継承したクラスに、やりたいことを記述していきます。PlayableBehaviourには次のメソッドが用意されているので必要に応じた場所に書いていきましょう。

|メソッド|タイミング|
|---|---|
|OnGraphStart|タイムライン開始時|
|OnGraphStop|タイムライン停止時|
|OnBehaviourPlay|PlayableTrack再生時|
|OnBehaviourPause|PlayableTrack停止時|
|PrepareFrame|PlayableTrack再生時毎フレーム|
※ PlayableBehaviourとMonoBehaviourを比べた図みたいなのを書くとわかりやすいので今度作ります。

さて、このテンプレートをつかってバリエーションを増やしてみます
### Textを1文字ずつ表示するやつを作成

TextMeshProを使うと、 `maxVisibleCharacters` を設定するだけで1文字ずつ表示を増やしていけます。これをタイムラインと組み合わせると、PrepareFrameでクリップの割合を計算して、入れてあげれば良い。(以下のコードは初期化を省略しています

```C#:NarrationBehaviour.cs
public class NarrationBehaviour : PlayableBehaviour
{
    public TMP_Text mTextUI { get; set; }

    public override void PrepareFrame(Playable playable, FrameData info)
    {
        if (mTextUI != null)
        {
            var progress = (float)(playable.GetTime() / playable.GetDuration());
            var current = Mathf.Lerp(0,mParsedText.Length, progress);
            var count = Mathf.CeilToInt(current);

            mTextUI.maxVisibleCharacters = count;
        }
    }
}
```

スクラブすると1文字ずづつ表示されます。戻るとちゃんと1文字づつ減っていくのがまたいいですね。
![test5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/18d9cfaa-dc97-1566-9ca5-9f8b21f41bf0.gif)


### ユーザーの入力を待つ
Textを1文字ずつ表示するやつができると、次は文章の一番最後で入力を待って(タイムラインを止めて)、ユーザーからの入力があったら次の文章へすすむ(タイムラインを動かす)やつがほしくなります。実装は至って簡単で、 `OnBehaviourPause` で動いているタイムラインのスピードを0にしてあげます。これで、タイムラインが一時的に停止します。ユーザーの入力に応じて、スピードを1にもどせれば、またタイムラインは動き始めます。

```C#:NarrationBehaviour.cs
public class NarrationBehaviour : PlayableBehaviour
{
    private PlayableDirector director;

    private bool pauseScheduled = true;
    public override void OnPlayableCreate(Playable playable)
    {
        director = (playable.GetGraph().GetResolver() as PlayableDirector);
    }

    public override void OnBehaviourPause(Playable playable, FrameData info)
    {
        if (pauseScheduled)
        {
            director.playableGraph.GetRootPlayable(0).SetSpeed(0d);
            pauseScheduled = false;
        }
    }
}
```

入力がスペースなのでわかりにくいですが、クリップの終わりでタイムラインが一時的に停止しています。これで、ユーザーの入力をタイムラインで待てるようになりました。
![test6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224134/f0e2cf55-a4d3-9e94-06b7-0c2bab821cf5.gif)


<!--
考え方
AnimationをAnimatorがまとめていて、そのAnimatorをまとめたり、Playableなものをまとめて制御できるものだと思います。
ある時間でActiveになってUpdateが呼ばれて、ある時間でとまるC#コードをおいたゲームオブジェクトをタイムラインにのせる感じかな？
-->

# 最後に
まだ、触りはじめで作例みたいのが豊富じゃなくて、タイムラインの良さを伝えきれないのが残念ですが、これから伝えられるようにしていけたらいいのかなと思います。あとは、タイムラインを触りながら、今日の時点でまとめられなかったことを追記していけたらと思います。(課題がかぶってギリギリで書いてしまった

最後に、友人と作ったゲームを宣伝しておきます。
[ハタイケダのゲーム作品 Google PlayStore](https://play.google.com/store/apps/developer?id=%E3%83%8F%E3%82%BF%E3%82%A4%E3%82%B1%E3%83%80&hl=ja)
[ハタイケダのゲーム作品 Apple AppStore](https://apps.apple.com/jp/developer/takamasa-ikeda/id1478586193)

# +参考にした記事
- UnityのTimelineをいくらか理解する: https://framesynthesis.jp/blog/2018/06/18/
- テラシュールブログ タグTimeline: http://tsubakit1.hateblo.jp/archive/category/Timeline
- Unity の Timeline をカスタマイズするための詳細: https://qiita.com/hadashiA/items/566f0d0222cb9a4e209b

<!--
https://light11.hatenadiary.com/entry/2019/05/22/003523
https://light11.hatenadiary.com/entry/2019/05/16/214328
https://blog.applibot.co.jp/2018/12/17/unity-timeline/
https://xr-hub.com/archives/11137
https://bardaxel.jp/archives/643
-->