3 / 4 絶対時間と相対時間
曲の開始時刻を基準にした時計を扱います。
また、音声の再生と画面の更新のずれや、フレームごとの累積誤差についても確認します。
ゲーム内の時間
Unityで時間を扱う際、起動からの時間としてはTime.time、フレーム間の経過時間にはTime.deltaTimeが用いられます。
ただ、これらの値は様々な理由で変化し、一律で進行するとも限りません。
例えば、ゲームの処理負荷やフレームレートの変動によっても影響を受けます。
ユーザー操作による離席や一時停止なども影響します。
よって、音楽の進行とは必ずしも一致しません。
音声の再生からの経過時間を扱う際にはAudioSettings.dspTimeを使うのが簡便です。
AudioSettings.dspTimeは、音声システムが処理したサンプル数を基に秒を返します。
UnityのAudioSettings.dspTime
配布済みのSongClock.csで、次の処理を確認してください。
public double SongTime => AudioSettings.dspTime - StartDspTime;
public void Begin()
{
source.Stop();
StartDspTime = AudioSettings.dspTime + 2.0;
source.PlayScheduled(StartDspTime);
}StartDspTimeは、曲の開始時刻を表します。
現在より2秒先の時間を保存、PlayScheduledでその時刻の再生を予約しています。
UnityのAudioSource.PlayScheduled
SongTimeは、同じ時計の現在時刻から開始時刻を引くプロパティです。
INFO
dspTimeは、DSP(デジタル信号処理)システムが処理したサンプル数を基に計算される時間です。
DSPのバッファーがフラッシュされる都度の更新となるため、時間としては離散的になり、連続的に変化するわけではありません。
ここでは簡便のために使用していますが、フレームごとの更新間隔と必ずしも同期せず、扱いが難しい場合があります。
より高度なゲームでは、DSP時間と他の時間軸を併用したり、相互に補間して扱うことがあります。
時計の精度、時間計測の離散、画面や音声機器の遅延の問題のすべてが解決したわけではありませんが、ひとまずこれで音源基準となる安定した時計を得ることができました。
フレーム間の経過時間を累積して時間を得ても構いませんが、その際はアプリケーションがなんらかの理由で停止した場合やフレームレートが変動した場合の対応を考える必要があります。
負の時間
曲の開始を0秒とすると、開始より2秒前は−2秒です。
ここでのマイナスは、時間が逆向きに進むのではなく、まだ基準点へ達していないことを表します。
| 音声の時計 | 曲の開始時刻 | SongTime | 状態 |
|---|---|---|---|
| 100秒 | 102秒 | −2秒 | 開始まで2秒 |
| 102秒 | 102秒 | 0秒 | 曲が始まる |
| 105秒 | 102秒 | 3秒 | 開始から3秒経過 |
曲の冒頭のノートは、楽曲の再生前に表示する必要があります。
時間をゼロでクランプしてしまうと不便なため、内部的には負の値を保持しつつ、表示上は0秒から始めるようにしています。
浮動小数点数
浮動小数点数とは
本来、小数点数の扱いはコンピューターにとって負担の大きな計算です。
このため、有限のビット数で小数を表現するための浮動小数点数方式が開発されました。
浮動小数点数は、指数と仮数の組み合わせで数値を表現する方式です。
柔軟な数値表現と高速な計算を両立させた優れた形式ですが、内部は2進数で表現されているため、10進数を正確に扱えないことがあります。
浮動小数点数と累積誤差
浮動小数点数型は、例えば10進数の0.1をそのまま正確に扱うことはできません。
例えば、floatの0.1fを10回足したfloatの値は、1.0と完全には一致しません。
C#の浮動小数点数
多くの場合、誤差は無視できる程度に小さく、直接問題になることはあまりありません。
ですが累積されると誤差が無視できないレベルに達してしまい、アプリケーションを長期に運用した時にのみ発生する問題などのわかりづらいトラブルの原因となることがあります。
float sum = 0.0f;
for (int i = 0; i < 10; i++)
sum += 0.1f;
Debug.Log(sum.ToString("G9")); // 1.00000012
Debug.Log(sum == 1.0f); // False浮動小数点数が内部的に保持できる有効な桁数は型によって異なり、32ビットのfloatは約7桁、64ビットのdoubleは約15桁の有効桁数を持ちます。
この桁をあふれた計算は、丸め誤差を生じる原因となります。
また、浮動小数点数は有限の桁数でしか値を表現できないため、小さな値を累積し続けると桁あふれが発生する可能性があります。doubleはfloatより多くの桁を保持でき、ほとんどの計算で十分な精度を得られますが、誤差がなくなるわけではありません。
時間をシビアに扱う場合には誤差に注意し、また扱う時間自体の寿命を考慮する必要があります。
今回のサンプルの規模で問題になることはほとんどありませんが、アプリケーションに長時間の運用や高い精度が求められる場合は、相応の対策が必要です。
INFO
例えばすべての時間を概念上のTickで扱うことで整数として管理し、浮動小数点数の累積誤差を回避するなどの方法が考えられます。
どのように時間を管理するべきか、自身でも是非考えてみてください。
- 曲の開始前・開始時・開始後の
SongTimeを説明できる。 - 丸め誤差、フレームの更新間隔、時計の基準の違いを区別できる。