Unityでカウントダウンタイマーのアプリを作ろうとしていたんですけど、残り秒数の計算で想像以上にハマったので記録。
仕様としては、目標日時までの残り秒数が画面に表示されるだけのシンプルなアプリです。
①TimeSpanで計算すると0.5秒ズレる
初めは目標日時と現在日時との差分をとって、TimeSpanのTotalSeconds関数で取得すればいいと考えていました。
private double CalcSeconds(DateTime dtTarget,DateTime dtNow)
{
TimeSpan tsDiff = dtTarget - dtNow;
return tsDiff.TotalSeconds;
}
コードはこんな感じ。
引数はそれぞれ目標日時、現在日時が格納されています。
試しにこれで起動してみたら、PCの時計で秒が変わる瞬間と残り秒数が変更される瞬間が一致しない。
例えば8時10分0秒を目標日時にした場合、
8時9分30秒になった瞬間:残り秒数31秒(本来は30秒)
8時9分30秒500ミリ秒:残り秒数30秒
8時9分31秒になった瞬間:残り秒数30秒(本来は29秒)
8時9分31秒500ミリ秒:残り秒数29秒
のように、どうにも0.5秒ずつズレていました。
おそらくミリ秒の四捨五入がされているのだと思いますが、正確な原因はわかりません。
ともかくこの動きはカウントダウンタイマーとして良くないので、別の方法を模索しました。
②日数、時間数、分数、秒数から計算
TimeSpanには日時の差分が日、時間、分、秒、ミリ秒ごとに格納されています。
なのでそれらの情報から力技で秒数を求めました。
private double CalcSeconds(DateTime dtTarget,DateTime dtNow)
{
TimeSpan tsDiff = dtTarget - dtNow;
//目標日時まではミリ秒を切り上げ、目標日時を過ぎたら切り捨てするための補正
int iKiriage = 0;
if (tsDiff.Milliseconds > 0)
{
iKiriage = 1;
}
return tsDiff.Days * 24 * 60 * 60 + tsDiff.Hours * 60 * 60 + tsDiff.Minutes * 60 + tsDiff.Seconds + iKiriage;
}
ここでもミリ秒に苦しめられました。
真ん中あたりに謎の補正が入っていますが、苦労の跡を察して頂ければ…。
単純に計算するだけだと以下のようになります。
現在8時9分50秒000ミリ秒:残り10秒000ミリ秒⇒10秒
現在8時9分50秒300ミリ秒:残り9秒700ミリ秒⇒9秒
おわかりでしょうか。
残り9秒と表示されてほしいタイミングは8時9分51秒になった瞬間です。
つまりミリ秒を切り上げてほしいわけです。
そこでミリ秒が0以外のときは秒数+1するというロジックを考えました。
するとどうなるか。
目標日時の8時10分0秒000ミリ秒のときはうまくいきますが、8時10分0秒001ミリ秒になった瞬間に切り上げられて残り1秒と表示されてしまいます。
ここでようやく気付きました。
目標日時より前の場合はミリ秒を切り上げ、目標日時を過ぎたらミリ秒を切り捨てる必要があると。
目標日時を過ぎたらミリ秒はマイナスになるので、ミリ秒が0より大きい場合、つまり目標日時がまだ到達していない限りは切り上げるといった補正を入れることでようやくカウントダウンタイマーらしい動きをさせることができました。
後から考えれば単純な話ですけど、かなり脳が混乱しました…。
③拡張メソッドを使えば瞬殺
上のコードを実装し終わった後に便利な拡張メソッドがあるのを見つけました。
この拡張メソッドを使ってミリ秒を切り捨てたところ、①のTimeSpanのやり方で問題なく動きました。
私の苦労は一体…。