【Unity】Timelineの再生位置をスライダーで指定する

こんにちはっ🌟八ツ橋まろんですっ！

ARアプリを作っているときに、Timelineをスライダーで操作したくなったのでスクリプトを書きました。本記事ではそのコードと解説をします。

↓↓↓↓このコードによってこんなことができます↓↓↓↓(GIF)
(このGIFではアニメーションだけが入ったTimelineを使用しています)
![Qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478685/2669182a-6c04-640f-b5b3-ca336c93a1fe.gif)

・再生ボタン/一時停止ボタンの実装
・Timelineの進行度合いがスライダーに反映される
・スライダーの位置をクリックして変えるとTimelineの再生位置も連動する

以下、完成コード

```C#:TimelineUiController.cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Playables;
using System; // Actionに必要

public class TimelineUiController : MonoBehaviour
{
    [SerializeField]
    PlayableDirector timeline;

    [SerializeField]
    Slider slider;

    [SerializeField]
    GameObject playButton;

    [SerializeField]
    GameObject pauseButton;

    double length;

    float sliderValue;
    float sliderValueStored;
    float time;

    private void Start()
    {
        length = timeline.duration;
        Play();
    }

    void Update()
    {
        sliderValue = slider.value;
        time = ConvertDtoF(timeline.time);

        if(time >= 0.995 && timeline.extrapolationMode != DirectorWrapMode.Loop)
        {
            playButton.SetActive(true);
            pauseButton.SetActive(false);
        }

        if (sliderValue != sliderValueStored)
        {
            sliderValueStored = sliderValue;
            time = sliderValue;
            SetTimelineTime(time);
        }
        else if(time != sliderValueStored)
        {
            slider.value = time;
            sliderValueStored = time;
        }
    }

    public void Play()
    {
        timeline.Play();
        playButton.SetActive(false);
        pauseButton.SetActive(true);
    }

    public void Stop()
    {
        timeline.Stop();
        PlayOneFrame();
    }

    public void Pause()
    {
        timeline.Pause();

        time = ConvertDtoF(timeline.time);
        sliderValue = time;
        sliderValueStored = time;
        playButton.SetActive(true);
        pauseButton.SetActive(false);
    }
    public void LengthReset()
    {
        length = timeline.duration;
    }
    void PlayOneFrame()
    {
        Play();

        //1フレーム後にPauseする
        StartCoroutine(DelayMethod(1, () => {Pause(); }));
    }

    private IEnumerator DelayMethod(int delayFrameCount, Action action)
    {
        for (var i = 0; i < delayFrameCount; i++)
        {
            yield return null;
        }
        action();
    }

    void SetTimelineTime(float f)
    {
        double d = ConvertFtoD(f);
        timeline.time = d;
        PlayOneFrame();
    }

    double ConvertFtoD(float f)
    {
        double d = (double)(f * length);
        return d;
    }

    float ConvertDtoF(double d)
    {
        float f = (float)d / (float)length;
        return f;
    }
}


```

###使い方
    [SerializeField]
    PlayableDirector timeline;

    [SerializeField]
    Slider slider;

    [SerializeField]
    GameObject playButton;

    [SerializeField]
    GameObject pauseButton;
上記にPlayableDirectorとスライダー、再生ボタン、停止ボタンをあてがってください。

###解説
Timelineの総時間は

    length = timeline.duration;
で取得できます。ただし、これはdouble型で得られるため、スライダーと連動したいならfloat型に変換しないといけないので注意。

    float ConvertDtoF(double d)
    {
        float f = (float)d / (float)length;
        return f;
    }
timeline.timeで現在の再生位置をdouble型で取得し、総時間で割ったあとにfloat型に変換して、0～1にしています。

    time = ConvertDtoF(timeline.time);

スライダーを触ってtimelineの再生位置を変更する場合、timeline.Pause();してtimeline.time = xxx;だけではスライダー位置が動かないため、その後に1フレームだけ再生することでスライダー位置を動かすようにしています。


###おわりに
Timelineの再生位置制御の需要は、なかなかないと思いますが、Animation制御を越えてカメラワークやエフェクトの巻き戻しとかを任意でやりたい場合に使えます✨よかったら使ってみてください。

八ツ橋まろん