第25章 時間を制御する TimeControl - エディター拡張入門

第25章 時間を制御する TimeControl

エディター拡張をしていると、Animation ウィンドウや AnimationClip のプレビューで使われているような、タイムコントロールを扱いたい時が出てきます。

再生ボタンを押すと時間が流れる比較的単純なもの

図25.1: 再生ボタンを押すと時間が流れる比較的単純なもの

複雑な要素が絡んだ本格的なもの

図25.2: 複雑な要素が絡んだ本格的なもの

本章では、これらの時間を管理するための基板となる TimeControl を解説します。

25.1 時間の素 EditorApplication.timeSinceStartup

経過時間を把握するために EditorApplication.timeSinceStartup を使用します。これは Unity エディターが起動されてからの経過時間を取得することが可能です。*1

次のコードが、時間を計測し続ける仕組みになります。経過時間の更新は エディターの更新時に呼び出される EditorApplication.update 経由で行います。

public class TimeControl
{
    private double m_lastFrameEditorTime = 0;
    public float currentTime;

    public TimeControl ()
    {
        EditorApplication.update += TimeUpdate;
    }

    public void TimeUpdate ()
    {
        var timeSinceStartup = EditorApplication.timeSinceStartup;
        var deltaTime = timeSinceStartup - m_lastFrameEditorTime;
        m_lastFrameEditorTime = timeSinceStartup;
        currentTime += (float)deltaTime;
    }
}

これだけでは、時間が流れていくだけなので「再生」や「一時停止」「停止」を実装していきます。

25.2 再生

public bool isPlaying { get; private set; }

public void TimeUpdate ()
{
    if (isPlaying) {
        //略。currentTime 更新
    }
}

public void Play ()
{
    isPlaying = true;
    m_lastFrameEditorTime = EditorApplication.timeSinceStartup;
}

再生は簡単です。トリガーとなる Play メソッドと再生中かを判断する isPLaying プロパティーを作成するだけで好きな時に再生を行うことができます。

1つだけ注意する点として、m_lastFrameEditorTime の更新も同時に行わなければいけません。これを行わなければ、再生するまでの経過時間も更新時に含まれてしまい、時間が飛んだような挙動になってしまいます。

25.3 一時停止

public void Pause ()
{
    isPlaying = false;
}

isPlaying を false にするだけです。

25.4 停止

public void Stop ()
{
    isPlaying = false;
    currentTime = 0;
}

isPlaying を false にして、currentTime を 0 にリセットします。

25.5 時間の下限/上限

Play メソッドを実行し、ずっと時間を計測するのもいいですが「ある一定の経過時間でループする」処理を加えると AnimationClip で扱うような挙動ができるようになります。

public float minTime { get; private set; }

public float maxTime { get; private set; }

public float currentTime {
    get {
        m_currentTime = Mathf.Repeat (m_currentTime, maxTime);
        m_currentTime = Mathf.Clamp (m_currentTime, minTime, maxTime);
        return m_currentTime;
    }
    set {
        m_currentTime = value;
    }
}

public void SetMinMaxTime (float minTime, float maxTime)
{
    this.minTime = minTime;
    this.maxTime = maxTime;
}

まず、minTimemaxTime プロパティーを作成します。次に、currentTime を取得するときに RepeatClamp で時間を加工します。Repeat は時間ループのため*2、Clamp は minTime による時間の開始位置を指定するために使用します。

25.6 再生スピード

public float speed { get; set; }

public void TimeUpdate ()
{
    if (isPlaying) {
        var timeSinceStartup = EditorApplication.timeSinceStartup;
        var deltaTime = timeSinceStartup - m_lastFrameEditorTime;
        m_lastFrameEditorTime = timeSinceStartup;

        currentTime += (float)deltaTime * speed;
    }
}

最後に再生スピードを変更できるようにします。speed プロパティーを作成して時間更新時に乗算します。

25.7 動くか確認

簡単なコードを書いて動作を確認してみましょう。サイドバーのつまみを移動させても、その位置から再生されることを確認してください。

using UnityEngine;
using UnityEditor;

public class Example : EditorWindow
{
    TimeControl timeControl;

    [MenuItem ("Window/Example")]
    static void Open ()
    {
        GetWindow<Example> ();
    }

    void OnEnable ()
    {
        timeControl = new TimeControl ();
        timeControl.SetMinMaxTime (0, 10);
    }

    void OnGUI ()
    {
        var buttonText = timeControl.isPlaying ? "Pause" : "Play";

        if (GUILayout.Button (buttonText)) {

            if (timeControl.isPlaying)
                timeControl.Pause ();
            else
                timeControl.Play ();
        }

        timeControl.currentTime = EditorGUILayout
            .Slider (timeControl.currentTime, 0, 10);

        //GUI 更新
        if (timeControl.isPlaying)
            Repaint ();
    }
}

本章で作成した TimeControl は 第21章「パーティクルを制御する」 や 第23章「SpriteAnimationPreview(スプライトアニメーション)」 で使用されています。使用例として参考にしてください。

[*1] DateTime.Now.Ticks を使っても問題ありません。紹介のために timeSinceStartup を扱ってみました。

[*2] 機能的にループ強制としました。ループをオプションとしたいときには loop 変数などを作成して制御するようにしてください。

第24章 シーンアセットにスクリプトをアタッチ 第26章 AssetDatabase