Unityスケジューラー! (Task Scheduler in Unity : 一日に一回実行するスケジューラ)

# デモ？

こんなことができます。

https://youtu.be/t4W4Ba8DX1Y

録音した音声をスケジューリングするのに使っています。

## 参考

https://takap-tech.com/entry/2022/07/26/233234

Unityじゃ、動かないんじゃ...

## スクリプト

```CustomTimer.cs
using System;
using System.Timers;
using UnityEngine.Events;
using UnityEngine;
using System.Collections;

/// <summary>
/// 1日1回だけ指定した時刻に処理を実行するタイマークラス
/// </summary>
public class CustomTimer : MonoBehaviour
{
    // Fields - - - - - - - - - - - - - - - - - - - -

    // 最後に処理を実行した時刻
    DateTime _nextExecTime;
    // 排他制御用
    private bool locking;

    
    // Events - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// 一日一回指定した時刻に実行する処理を設定するハンドラー
    /// </summary>
    /// <remarks>
    /// この通知は環境によって Rx とか MessagePipe に変更しても良い。
    /// </remarks>

    // Props - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// 処理が実行される時間を取得します。
    /// <para>時刻部分しか参照しません。日付は無視されます。</para>
    /// </summary>
    public DateTime ExectionTime;

    private int Counter;

    private int _count;

    public Action OnElapsed;

    // Constructors & Methods - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// タイマーの実行間隔を指定してオブジェクトを作成します。
    /// </summary>
    public void Initialize(DateTime execTime,  int counter = -1)
    {
        ExectionTime = execTime;
        Counter = counter;
    }

    /// <summary>
    /// タイマーを開始します。
    /// </summary>
    public void StartTimer()
    {
        locking = false;
        // 次の日時を作成する
        var now = DateTime.Now;
        var next = 
            new DateTime(now.Year, now.Month, now.Day, 
                ExectionTime.Hour, ExectionTime.Minute, ExectionTime.Second);
        if (now > next)
        {
            // 起動予定時刻を過ぎていたら次の日を設定
            next = next.AddDays(1);
        }
        _nextExecTime = next;
        UnityEngine.Debug.Log("Execution date " + _nextExecTime);
        StartCoroutine("OnceDayTimer");
    }

    /// <summary>
    /// タイマーを停止します。
    /// </summary>
    public void Stop()
    {
        locking = true;
    }

    // IDisposable の実装
    public void  OnDestroy()
    {
        CancelInvoke();
    }

    // 定周期で実行されるメソッド
    IEnumerator OnceDayTimer()
    {
        while(true){
            var now = DateTime.Now;
            while(_nextExecTime > now)
            {
                yield return new WaitForSecondsRealtime(1);
                now = DateTime.Now;
            }
            if(locking){
                yield return new WaitForSecondsRealtime(1);                
            }
            //ここが実行タイミング
            OnElapsed?.Invoke();
            _count += 1;

            _nextExecTime = _nextExecTime.AddDays(1); // 次回実行時刻を設定

            //回数を設定した場合、その回数に達したら停止する.
            if(_count == Counter){
                DestroyImmediate(this);
            }
            yield return new WaitForSecondsRealtime(1);
        }
    }
}
```

使い方
```?.cs
var timer = this.gameObject.AddComponent<CustomTimer>();
timer.Initialize(DateTimeUtil.GetTime(int.Parse(hour), int.Parse(min)),  cnt);
timer.OnElapsed += () => {ここに関数;};
timer.StartTimer();
```

AddComponentして初期化、そのあとでラムダ関数を登録して、タイマーのスタートをすることで実行できます。

WaitForSecondsRealtime(1)を使っていますが、数秒単位でずれます...。

また、カウンターに数値をいれれば、そのカウンター分実行後にコンポーネントが破棄されます。