概要
周期的に何らかの処理を実行したい場合、Update関数内でTime.deltaTimeから経過時間を取得する方法が一般的です。
しかしUpdate関数の実行は描画タイミングに依存しており、例えば1秒毎に処理を実行したいときに、パフォーマンス次第で周期が1.2秒とか1.5秒になってしまうので必ずしも厳密なタイマーとは言えません。
先日BPMと同期した動きをするシステムを作ろうとしたときにこのことが結構な壁になったので、厳密なタイマーの作り方を記載します。
やりかたとしては2通りあります。
- WaitForSecondsを使ったトリック
- FixedUpdateを使用する
WaitForSecondsを使ったトリック
周期的に処理を実行する方法としてStartCoroutine()とWaitForSeconds()を組み合わせる方法がありますが、
WaitForSecondsで1[ms]を指定し、コルーチンを1ms周期で呼ぶようにします。これにより、高速な似非無限ループができます。
あとはTime.timeを使って経過時間を監視します。
Time.deltaTimeを使ってはいけません。タイマ計測をフレームレート(Update関数)に依存させないようにするためです。
IEnumerator FuncCoroutine() {
while(true){
if (Time.time > this.elapsed) {
// Do something
this.elapsed = Time.time + timeOut;
}
yield return new WaitForSeconds((float)1/1000);
}
}
パフォーマンス次第ではありますが、この方法は"FixedUpdateを使用する"方法より厳密なタイマーがつくれます。
FixedUpdateを使用する
Updateでやってた時間計測をFixedUpdateでやります。(そのままコードをコピペでOK)
ただしFixedUpdateに処理を詰め込みすぎると衝突判定などに影響がでるので注意。
余談
方法としては2通りご紹介しましたが、どちらもストレートな解決策ではありません。。
本当ならUnityのmainループのようなところにタイマーを張れば済むことなんですが、その方法は見つかりませんでした。
またはC#の機能を使って別のスレッドを作り、そっちのスレッドで経過時間を監視する方法もあるようなのですが、とりあえず上記の方法で大体よくなったので試してません。
Fract OSCというUnityエンジンのゲームが完璧にBPMシンクしてる気がするんだけどどうやってるんだろう・・・